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

1.17      albertel    1: # The LearningOnline Network with CAPA
1.13      albertel    2: # The LON-CAPA Grading handler
1.17      albertel    3: #
1.746   ! raeburn     4: # $Id: grades.pm,v 1.745 2017/12/18 23:51:24 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.646     raeburn    43: use Apache::Constants qw(:common :http);
1.167     sakharuk   44: use Apache::lonlocal;
1.386     raeburn    45: use Apache::lonenc;
1.622     www        46: use Apache::lonstathelpers;
1.639     www        47: use Apache::lonquickgrades;
1.657     raeburn    48: use Apache::bridgetask();
1.170     albertel   49: use String::Similarity;
1.359     www        50: use LONCAPA;
                     51: 
1.315     bowersj2   52: use POSIX qw(floor);
1.87      www        53: 
1.435     foxr       54: 
1.513     foxr       55: 
1.435     foxr       56: my %perm=();
1.674     raeburn    57: my %old_essays=();
1.447     foxr       58: 
1.513     foxr       59: #  These variables are used to recover from ssi errors
                     60: 
                     61: my $ssi_retries = 5;
                     62: my $ssi_error;
                     63: my $ssi_error_resource;
                     64: my $ssi_error_message;
                     65: 
                     66: 
                     67: sub ssi_with_retries {
                     68:     my ($resource, $retries, %form) = @_;
                     69:     my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form);
                     70:     if ($response->is_error) {
                     71: 	$ssi_error          = 1;
                     72: 	$ssi_error_resource = $resource;
                     73: 	$ssi_error_message  = $response->code . " " . $response->message;
                     74:     }
                     75: 
                     76:     return $content;
                     77: 
                     78: }
                     79: #
                     80: #  Prodcuces an ssi retry failure error message to the user:
                     81: #
                     82: 
                     83: sub ssi_print_error {
                     84:     my ($r) = @_;
1.516     raeburn    85:     my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk');
                     86:     $r->print('
                     87: <br />
                     88: <h2>'.&mt('An unrecoverable network error occurred:').'</h2>
                     89: <p>
                     90: '.&mt('Unable to retrieve a resource from a server:').'<br />
                     91: '.&mt('Resource:').' '.$ssi_error_resource.'<br />
                     92: '.&mt('Error:').' '.$ssi_error_message.'
                     93: </p>
                     94: <p>'.
                     95: &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 />'.
                     96: &mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
                     97: '</p>');
                     98:     return;
1.513     foxr       99: }
                    100: 
1.44      ng        101: #
1.146     albertel  102: # --- Retrieve the parts from the metadata file.---
1.598     www       103: # Returns an array of everything that the resources stores away
                    104: #
                    105: 
1.44      ng        106: sub getpartlist {
1.582     raeburn   107:     my ($symb,$errorref) = @_;
1.439     albertel  108: 
                    109:     my $navmap   = Apache::lonnavmaps::navmap->new();
1.582     raeburn   110:     unless (ref($navmap)) {
                    111:         if (ref($errorref)) { 
                    112:             $$errorref = 'navmap';
                    113:             return;
                    114:         }
                    115:     }
1.439     albertel  116:     my $res      = $navmap->getBySymb($symb);
                    117:     my $partlist = $res->parts();
                    118:     my $url      = $res->src();
1.745     raeburn   119:     my $toolsymb;
                    120:     if ($url =~ /ext\.tool$/) {
                    121:         $toolsymb = $symb;
                    122:     }
                    123:     my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys',$toolsymb));
1.439     albertel  124: 
1.146     albertel  125:     my @stores;
1.439     albertel  126:     foreach my $part (@{ $partlist }) {
1.146     albertel  127: 	foreach my $key (@metakeys) {
                    128: 	    if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
                    129: 	}
                    130:     }
                    131:     return @stores;
1.2       albertel  132: }
                    133: 
1.129     ng        134: #--- Format fullname, username:domain if different for display
                    135: #--- Use anywhere where the student names are listed
                    136: sub nameUserString {
                    137:     my ($type,$fullname,$uname,$udom) = @_;
                    138:     if ($type eq 'header') {
1.485     albertel  139: 	return '<b>&nbsp;'.&mt('Fullname').'&nbsp;</b><span class="LC_internal_info">('.&mt('Username').')</span>';
1.129     ng        140:     } else {
1.398     albertel  141: 	return '&nbsp;'.$fullname.'<span class="LC_internal_info">&nbsp;('.$uname.
                    142: 	    ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')</span>';
1.129     ng        143:     }
                    144: }
                    145: 
1.44      ng        146: #--- Get the partlist and the response type for a given problem. ---
                    147: #--- Indicate if a response type is coded handgraded or not. ---
1.623     www       148: #--- Sets response_error pointer to "1" if navmaps object broken ---
1.39      ng        149: sub response_type {
1.582     raeburn   150:     my ($symb,$response_error) = @_;
1.377     albertel  151: 
                    152:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn   153:     unless (ref($navmap)) {
                    154:         if (ref($response_error)) {
                    155:             $$response_error = 1;
                    156:         }
                    157:         return;
                    158:     }
1.377     albertel  159:     my $res = $navmap->getBySymb($symb);
1.593     raeburn   160:     unless (ref($res)) {
                    161:         $$response_error = 1;
                    162:         return;
                    163:     }
1.377     albertel  164:     my $partlist = $res->parts();
1.392     albertel  165:     my %vPart = 
                    166: 	map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
1.377     albertel  167:     my (%response_types,%handgrade);
                    168:     foreach my $part (@{ $partlist }) {
1.392     albertel  169: 	next if (%vPart && !exists($vPart{$part}));
                    170: 
1.377     albertel  171: 	my @types = $res->responseType($part);
                    172: 	my @ids = $res->responseIds($part);
                    173: 	for (my $i=0; $i < scalar(@ids); $i++) {
                    174: 	    $response_types{$part}{$ids[$i]} = $types[$i];
                    175: 	    $handgrade{$part.'_'.$ids[$i]} = 
                    176: 		&Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
                    177: 				     '.handgrade',$symb);
1.41      ng        178: 	}
                    179:     }
1.377     albertel  180:     return ($partlist,\%handgrade,\%response_types);
1.39      ng        181: }
                    182: 
1.375     albertel  183: sub flatten_responseType {
                    184:     my ($responseType) = @_;
                    185:     my @part_response_id =
                    186: 	map { 
                    187: 	    my $part = $_;
                    188: 	    map {
                    189: 		[$part,$_]
                    190: 		} sort(keys(%{ $responseType->{$part} }));
                    191: 	} sort(keys(%$responseType));
                    192:     return @part_response_id;
                    193: }
                    194: 
1.207     albertel  195: sub get_display_part {
1.324     albertel  196:     my ($partID,$symb)=@_;
1.207     albertel  197:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
                    198:     if (defined($display) and $display ne '') {
1.577     bisitz    199:         $display.= ' (<span class="LC_internal_info">'
                    200:                   .&mt('Part ID: [_1]',$partID).'</span>)';
1.207     albertel  201:     } else {
                    202: 	$display=$partID;
                    203:     }
                    204:     return $display;
                    205: }
1.269     raeburn   206: 
1.434     albertel  207: sub reset_caches {
                    208:     &reset_analyze_cache();
                    209:     &reset_perm();
1.674     raeburn   210:     &reset_old_essays();
1.434     albertel  211: }
                    212: 
                    213: {
                    214:     my %analyze_cache;
1.557     raeburn   215:     my %analyze_cache_formkeys;
1.148     albertel  216: 
1.434     albertel  217:     sub reset_analyze_cache {
                    218: 	undef(%analyze_cache);
1.557     raeburn   219:         undef(%analyze_cache_formkeys);
1.434     albertel  220:     }
                    221: 
                    222:     sub get_analyze {
1.649     raeburn   223: 	my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_;
1.434     albertel  224: 	my $key = "$symb\0$uname\0$udom";
1.640     raeburn   225:         if ($type eq 'randomizetry') {
                    226:             if ($trial ne '') {
                    227:                 $key .= "\0".$trial;
                    228:             }
                    229:         }
1.557     raeburn   230: 	if (exists($analyze_cache{$key})) {
                    231:             my $getupdate = 0;
                    232:             if (ref($add_to_hash) eq 'HASH') {
                    233:                 foreach my $item (keys(%{$add_to_hash})) {
                    234:                     if (ref($analyze_cache_formkeys{$key}) eq 'HASH') {
                    235:                         if (!exists($analyze_cache_formkeys{$key}{$item})) {
                    236:                             $getupdate = 1;
                    237:                             last;
                    238:                         }
                    239:                     } else {
                    240:                         $getupdate = 1;
                    241:                     }
                    242:                 }
                    243:             }
                    244:             if (!$getupdate) {
                    245:                 return $analyze_cache{$key};
                    246:             }
                    247:         }
1.434     albertel  248: 
                    249: 	my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
                    250: 	$url=&Apache::lonnet::clutter($url);
1.557     raeburn   251:         my %form = ('grade_target'      => 'analyze',
                    252:                     'grade_domain'      => $udom,
                    253:                     'grade_symb'        => $symb,
                    254:                     'grade_courseid'    =>  $env{'request.course.id'},
                    255:                     'grade_username'    => $uname,
                    256:                     'grade_noincrement' => $no_increment);
1.649     raeburn   257:         if ($bubbles_per_row ne '') {
                    258:             $form{'bubbles_per_row'} = $bubbles_per_row;
                    259:         }
1.640     raeburn   260:         if ($type eq 'randomizetry') {
                    261:             $form{'grade_questiontype'} = $type;
                    262:             if ($rndseed ne '') {
                    263:                 $form{'grade_rndseed'} = $rndseed;
                    264:             }
                    265:         }
1.557     raeburn   266:         if (ref($add_to_hash)) {
                    267:             %form = (%form,%{$add_to_hash});
1.640     raeburn   268:         }
1.557     raeburn   269: 	my $subresult=&ssi_with_retries($url, $ssi_retries,%form);
1.434     albertel  270: 	(undef,$subresult)=split(/_HASH_REF__/,$subresult,2);
                    271: 	my %analyze=&Apache::lonnet::str2hash($subresult);
1.557     raeburn   272:         if (ref($add_to_hash) eq 'HASH') {
                    273:             $analyze_cache_formkeys{$key} = $add_to_hash;
                    274:         } else {
                    275:             $analyze_cache_formkeys{$key} = {};
                    276:         }
1.434     albertel  277: 	return $analyze_cache{$key} = \%analyze;
                    278:     }
                    279: 
                    280:     sub get_order {
1.640     raeburn   281: 	my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_;
                    282: 	my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed);
1.434     albertel  283: 	return $analyze->{"$partid.$respid.shown"};
                    284:     }
                    285: 
                    286:     sub get_radiobutton_correct_foil {
1.640     raeburn   287: 	my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_;
                    288: 	my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed);
                    289:         my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed);
1.555     raeburn   290:         if (ref($foils) eq 'ARRAY') {
                    291: 	    foreach my $foil (@{$foils}) {
                    292: 	        if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') {
                    293: 		    return $foil;
                    294: 	        }
1.434     albertel  295: 	    }
                    296: 	}
                    297:     }
1.554     raeburn   298: 
                    299:     sub scantron_partids_tograde {
1.741     raeburn   300:         my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row,$scancode) = @_;
1.554     raeburn   301:         my (%analysis,@parts);
                    302:         if (ref($resource)) {
                    303:             my $symb = $resource->symb();
1.557     raeburn   304:             my $add_to_form;
                    305:             if ($check_for_randomlist) {
                    306:                 $add_to_form = { 'check_parts_withrandomlist' => 1,};
                    307:             }
1.741     raeburn   308:             if ($scancode) {
                    309:                 if (ref($add_to_form) eq 'HASH') {
                    310:                     $add_to_form->{'code_for_randomlist'} = $scancode;
                    311:                 } else {
                    312:                     $add_to_form = { 'code_for_randomlist' => $scancode,};
                    313:                 }
                    314:             }
1.649     raeburn   315:             my $analyze = 
                    316:                 &get_analyze($symb,$uname,$udom,undef,$add_to_form,
                    317:                              undef,undef,undef,$bubbles_per_row);
1.554     raeburn   318:             if (ref($analyze) eq 'HASH') {
                    319:                 %analysis = %{$analyze};
                    320:             }
                    321:             if (ref($analysis{'parts'}) eq 'ARRAY') {
                    322:                 foreach my $part (@{$analysis{'parts'}}) {
                    323:                     my ($id,$respid) = split(/\./,$part);
                    324:                     if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) {
                    325:                         push(@parts,$part);
                    326:                     }
                    327:                 }
                    328:             }
                    329:         }
                    330:         return (\%analysis,\@parts);
                    331:     }
                    332: 
1.148     albertel  333: }
1.434     albertel  334: 
1.118     ng        335: #--- Clean response type for display
1.335     albertel  336: #--- Currently filters option/rank/radiobutton/match/essay/Task
                    337: #        response types only.
1.118     ng        338: sub cleanRecord {
1.336     albertel  339:     my ($answer,$response,$symb,$partid,$respid,$record,$order,$version,
1.640     raeburn   340: 	$uname,$udom,$type,$trial,$rndseed) = @_;
1.398     albertel  341:     my $grayFont = '<span class="LC_internal_info">';
1.148     albertel  342:     if ($response =~ /^(option|rank)$/) {
                    343: 	my %answer=&Apache::lonnet::str2hash($answer);
1.720     kruse     344:         my @answer = %answer;
                    345:         %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
1.148     albertel  346: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
                    347: 	my ($toprow,$bottomrow);
                    348: 	foreach my $foil (@$order) {
                    349: 	    if ($grading{$foil} == 1) {
                    350: 		$toprow.='<td><b>'.$answer{$foil}.'&nbsp;</b></td>';
                    351: 	    } else {
                    352: 		$toprow.='<td><i>'.$answer{$foil}.'&nbsp;</i></td>';
                    353: 	    }
1.398     albertel  354: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.148     albertel  355: 	}
                    356: 	return '<blockquote><table border="1">'.
1.466     albertel  357: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    358: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.660     raeburn   359: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  360:     } elsif ($response eq 'match') {
                    361: 	my %answer=&Apache::lonnet::str2hash($answer);
1.720     kruse     362:         my @answer = %answer;
                    363:         %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
1.148     albertel  364: 	my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
                    365: 	my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"});
                    366: 	my ($toprow,$middlerow,$bottomrow);
                    367: 	foreach my $foil (@$order) {
                    368: 	    my $item=shift(@items);
                    369: 	    if ($grading{$foil} == 1) {
                    370: 		$toprow.='<td><b>'.$item.'&nbsp;</b></td>';
1.398     albertel  371: 		$middlerow.='<td><b>'.$grayFont.$answer{$foil}.'&nbsp;</span></b></td>';
1.148     albertel  372: 	    } else {
                    373: 		$toprow.='<td><i>'.$item.'&nbsp;</i></td>';
1.398     albertel  374: 		$middlerow.='<td><i>'.$grayFont.$answer{$foil}.'&nbsp;</span></i></td>';
1.148     albertel  375: 	    }
1.398     albertel  376: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.118     ng        377: 	}
1.126     ng        378: 	return '<blockquote><table border="1">'.
1.466     albertel  379: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    380: 	    '<tr valign="top"><td>'.$grayFont.&mt('Item ID').'</span></td>'.
1.148     albertel  381: 	    $middlerow.'</tr>'.
1.466     albertel  382: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.660     raeburn   383: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  384:     } elsif ($response eq 'radiobutton') {
                    385: 	my %answer=&Apache::lonnet::str2hash($answer);
1.720     kruse     386:         my @answer = %answer;
                    387:         %answer = map {&HTML::Entities::encode($_, '"<>&')}  @answer;
1.148     albertel  388: 	my ($toprow,$bottomrow);
1.434     albertel  389: 	my $correct = 
1.640     raeburn   390: 	    &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed);
1.434     albertel  391: 	foreach my $foil (@$order) {
1.148     albertel  392: 	    if (exists($answer{$foil})) {
1.434     albertel  393: 		if ($foil eq $correct) {
1.466     albertel  394: 		    $toprow.='<td><b>'.&mt('true').'</b></td>';
1.148     albertel  395: 		} else {
1.466     albertel  396: 		    $toprow.='<td><i>'.&mt('true').'</i></td>';
1.148     albertel  397: 		}
                    398: 	    } else {
1.466     albertel  399: 		$toprow.='<td>'.&mt('false').'</td>';
1.148     albertel  400: 	    }
1.398     albertel  401: 	    $bottomrow.='<td>'.$grayFont.$foil.'</span>&nbsp;</td>';
1.148     albertel  402: 	}
                    403: 	return '<blockquote><table border="1">'.
1.466     albertel  404: 	    '<tr valign="top"><td>'.&mt('Answer').'</td>'.$toprow.'</tr>'.
                    405: 	    '<tr valign="top"><td>'.$grayFont.&mt('Option ID').'</span></td>'.
1.660     raeburn   406: 	    $bottomrow.'</tr></table></blockquote>';
1.148     albertel  407:     } elsif ($response eq 'essay') {
1.257     albertel  408: 	if (! exists ($env{'form.'.$symb})) {
1.122     ng        409: 	    my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade',
1.257     albertel  410: 						  $env{'course.'.$env{'request.course.id'}.'.domain'},
                    411: 						  $env{'course.'.$env{'request.course.id'}.'.num'});
1.122     ng        412: 
1.257     albertel  413: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
                    414: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
                    415: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
                    416: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
                    417: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
                    418: 	    $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        419: 	}
1.730     kruse     420: 	return '<br /><br /><blockquote><tt>'.&keywords_highlight($answer).'</tt></blockquote>';
1.720     kruse     421: 
1.268     albertel  422:     } elsif ( $response eq 'organic') {
1.721     bisitz    423:         my $result=&mt('Smile representation: [_1]',
                    424:                            '"<tt>'.&HTML::Entities::encode($answer, '"<>&').'</tt>"');
1.268     albertel  425: 	my $jme=$record->{$version."resource.$partid.$respid.molecule"};
                    426: 	$result.=&Apache::chemresponse::jme_img($jme,$answer,400);
                    427: 	return $result;
1.335     albertel  428:     } elsif ( $response eq 'Task') {
                    429: 	if ( $answer eq 'SUBMITTED') {
                    430: 	    my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"};
1.336     albertel  431: 	    my $result = &Apache::bridgetask::file_list($files,$uname,$udom);
1.335     albertel  432: 	    return $result;
                    433: 	} elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) {
                    434: 	    my @matches = grep(/^\Q$version\E.*?\.instance$/,
                    435: 			       keys(%{$record}));
                    436: 	    return join('<br />',($version,@matches));
                    437: 			       
                    438: 			       
                    439: 	} else {
                    440: 	    my $result =
                    441: 		'<p>'
                    442: 		.&mt('Overall result: [_1]',
                    443: 		     $record->{$version."resource.$respid.$partid.status"})
                    444: 		.'</p>';
                    445: 	    
                    446: 	    $result .= '<ul>';
                    447: 	    my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
                    448: 			     keys(%{$record}));
                    449: 	    foreach my $grade (sort(@grade)) {
                    450: 		my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
                    451: 		$result.= '<li>'.&mt("Dimension: [_1], status [_2] ",
                    452: 				     $dim, $record->{$grade}).
                    453: 			  '</li>';
                    454: 	    }
                    455: 	    $result.='</ul>';
                    456: 	    return $result;
                    457: 	}
1.716     bisitz    458:     } elsif ( $response =~ m/(?:numerical|formula|custom)/) {
                    459:         # Respect multiple input fields, see Bug #5409
1.440     albertel  460: 	$answer = 
                    461: 	    &Apache::loncommon::format_previous_attempt_value('submission',
                    462: 							      $answer);
1.720     kruse     463: 	return $answer;
1.122     ng        464:     }
1.720     kruse     465:     return &HTML::Entities::encode($answer, '"<>&');
1.118     ng        466: }
                    467: 
                    468: #-- A couple of common js functions
                    469: sub commonJSfunctions {
                    470:     my $request = shift;
1.597     wenzelju  471:     $request->print(&Apache::lonhtmlcommon::scripttag(<<COMMONJSFUNCTIONS));
1.118     ng        472:     function radioSelection(radioButton) {
                    473: 	var selection=null;
                    474: 	if (radioButton.length > 1) {
                    475: 	    for (var i=0; i<radioButton.length; i++) {
                    476: 		if (radioButton[i].checked) {
                    477: 		    return radioButton[i].value;
                    478: 		}
                    479: 	    }
                    480: 	} else {
                    481: 	    if (radioButton.checked) return radioButton.value;
                    482: 	}
                    483: 	return selection;
                    484:     }
                    485: 
                    486:     function pullDownSelection(selectOne) {
                    487: 	var selection="";
                    488: 	if (selectOne.length > 1) {
                    489: 	    for (var i=0; i<selectOne.length; i++) {
                    490: 		if (selectOne[i].selected) {
                    491: 		    return selectOne[i].value;
                    492: 		}
                    493: 	    }
                    494: 	} else {
1.138     albertel  495:             // only one value it must be the selected one
                    496: 	    return selectOne.value;
1.118     ng        497: 	}
                    498:     }
                    499: COMMONJSFUNCTIONS
                    500: }
                    501: 
1.44      ng        502: #--- Dumps the class list with usernames,list of sections,
                    503: #--- section, ids and fullnames for each user.
                    504: sub getclasslist {
1.449     banghart  505:     my ($getsec,$filterlist,$getgroup) = @_;
1.291     albertel  506:     my @getsec;
1.450     banghart  507:     my @getgroup;
1.442     banghart  508:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.291     albertel  509:     if (!ref($getsec)) {
                    510: 	if ($getsec ne '' && $getsec ne 'all') {
                    511: 	    @getsec=($getsec);
                    512: 	}
                    513:     } else {
                    514: 	@getsec=@{$getsec};
                    515:     }
                    516:     if (grep(/^all$/,@getsec)) { undef(@getsec); }
1.450     banghart  517:     if (!ref($getgroup)) {
                    518: 	if ($getgroup ne '' && $getgroup ne 'all') {
                    519: 	    @getgroup=($getgroup);
                    520: 	}
                    521:     } else {
                    522: 	@getgroup=@{$getgroup};
                    523:     }
                    524:     if (grep(/^all$/,@getgroup)) { undef(@getgroup); }
1.291     albertel  525: 
1.449     banghart  526:     my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist();
1.49      albertel  527:     # Bail out if we were unable to get the classlist
1.56      matthew   528:     return if (! defined($classlist));
1.449     banghart  529:     &Apache::loncoursedata::get_group_memberships($classlist,$keylist);
1.56      matthew   530:     #
                    531:     my %sections;
                    532:     my %fullnames;
1.205     matthew   533:     foreach my $student (keys(%$classlist)) {
                    534:         my $end      = 
                    535:             $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
                    536:         my $start    = 
                    537:             $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
                    538:         my $id       = 
                    539:             $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
                    540:         my $section  = 
                    541:             $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
                    542:         my $fullname = 
                    543:             $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
                    544:         my $status   = 
                    545:             $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
1.449     banghart  546:         my $group   = 
                    547:             $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
1.76      ng        548: 	# filter students according to status selected
1.442     banghart  549: 	if ($filterlist && (!($stu_status =~ /Any/))) {
                    550: 	    if (!($stu_status =~ $status)) {
1.450     banghart  551: 		delete($classlist->{$student});
1.76      ng        552: 		next;
                    553: 	    }
                    554: 	}
1.450     banghart  555: 	# filter students according to groups selected
1.453     banghart  556: 	my @stu_groups = split(/,/,$group);
1.450     banghart  557: 	if (@getgroup) {
                    558: 	    my $exclude = 1;
1.454     banghart  559: 	    foreach my $grp (@getgroup) {
                    560: 	        foreach my $stu_group (@stu_groups) {
1.453     banghart  561: 	            if ($stu_group eq $grp) {
                    562: 	                $exclude = 0;
                    563:     	            } 
1.450     banghart  564: 	        }
1.453     banghart  565:     	        if (($grp eq 'none') && !$group) {
                    566:         	        $exclude = 0;
                    567:         	}
1.450     banghart  568: 	    }
                    569: 	    if ($exclude) {
                    570: 	        delete($classlist->{$student});
                    571: 	    }
                    572: 	}
1.205     matthew   573: 	$section = ($section ne '' ? $section : 'none');
1.106     albertel  574: 	if (&canview($section)) {
1.291     albertel  575: 	    if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
1.103     albertel  576: 		$sections{$section}++;
1.450     banghart  577: 		if ($classlist->{$student}) {
                    578: 		    $fullnames{$student}=$fullname;
                    579: 		}
1.103     albertel  580: 	    } else {
1.205     matthew   581: 		delete($classlist->{$student});
1.103     albertel  582: 	    }
                    583: 	} else {
1.205     matthew   584: 	    delete($classlist->{$student});
1.103     albertel  585: 	}
1.44      ng        586:     }
                    587:     my %seen = ();
1.56      matthew   588:     my @sections = sort(keys(%sections));
                    589:     return ($classlist,\@sections,\%fullnames);
1.44      ng        590: }
                    591: 
1.103     albertel  592: sub canmodify {
                    593:     my ($sec)=@_;
                    594:     if ($perm{'mgr'}) {
                    595: 	if (!defined($perm{'mgr_section'})) {
                    596: 	    # can modify whole class
                    597: 	    return 1;
                    598: 	} else {
                    599: 	    if ($sec eq $perm{'mgr_section'}) {
                    600: 		#can modify the requested section
                    601: 		return 1;
                    602: 	    } else {
                    603: 		# can't modify the request section
                    604: 		return 0;
                    605: 	    }
                    606: 	}
                    607:     }
                    608:     #can't modify
                    609:     return 0;
                    610: }
                    611: 
                    612: sub canview {
                    613:     my ($sec)=@_;
                    614:     if ($perm{'vgr'}) {
                    615: 	if (!defined($perm{'vgr_section'})) {
                    616: 	    # can modify whole class
                    617: 	    return 1;
                    618: 	} else {
                    619: 	    if ($sec eq $perm{'vgr_section'}) {
                    620: 		#can modify the requested section
                    621: 		return 1;
                    622: 	    } else {
                    623: 		# can't modify the request section
                    624: 		return 0;
                    625: 	    }
                    626: 	}
                    627:     }
                    628:     #can't modify
                    629:     return 0;
                    630: }
                    631: 
1.44      ng        632: #--- Retrieve the grade status of a student for all the parts
                    633: sub student_gradeStatus {
1.324     albertel  634:     my ($symb,$udom,$uname,$partlist) = @_;
1.257     albertel  635:     my %record     = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.44      ng        636:     my %partstatus = ();
                    637:     foreach (@$partlist) {
1.128     ng        638: 	my ($status,undef)   = split(/_/,$record{"resource.$_.solved"},2);
1.44      ng        639: 	$status              = 'nothing' if ($status eq '');
                    640: 	$partstatus{$_}      = $status;
                    641: 	my $subkey           = "resource.$_.submitted_by";
                    642: 	$partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
                    643:     }
                    644:     return %partstatus;
                    645: }
                    646: 
1.45      ng        647: # hidden form and javascript that calls the form
                    648: # Use by verifyscript and viewgrades
                    649: # Shows a student's view of problem and submission
                    650: sub jscriptNform {
1.324     albertel  651:     my ($symb) = @_;
1.442     banghart  652:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.597     wenzelju  653:     my $jscript= &Apache::lonhtmlcommon::scripttag(
1.45      ng        654: 	'    function viewOneStudent(user,domain) {'."\n".
                    655: 	'	document.onestudent.student.value = user;'."\n".
                    656: 	'	document.onestudent.userdom.value = domain;'."\n".
                    657: 	'	document.onestudent.submit();'."\n".
                    658: 	'    }'."\n".
1.597     wenzelju  659: 	"\n");
1.45      ng        660:     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
1.418     albertel  661: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.442     banghart  662: 	'<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
1.45      ng        663: 	'<input type="hidden" name="command" value="submission" />'."\n".
                    664: 	'<input type="hidden" name="student" value="" />'."\n".
                    665: 	'<input type="hidden" name="userdom" value="" />'."\n".
                    666: 	'</form>'."\n";
                    667:     return $jscript;
                    668: }
1.39      ng        669: 
1.447     foxr      670: 
                    671: 
1.315     bowersj2  672: # Given the score (as a number [0-1] and the weight) what is the final
                    673: # point value? This function will round to the nearest tenth, third,
                    674: # or quarter if one of those is within the tolerance of .00001.
1.316     albertel  675: sub compute_points {
1.315     bowersj2  676:     my ($score, $weight) = @_;
                    677:     
                    678:     my $tolerance = .00001;
                    679:     my $points = $score * $weight;
                    680: 
                    681:     # Check for nearness to 1/x.
                    682:     my $check_for_nearness = sub {
                    683:         my ($factor) = @_;
                    684:         my $num = ($points * $factor) + $tolerance;
                    685:         my $floored_num = floor($num);
1.316     albertel  686:         if ($num - $floored_num < 2 * $tolerance * $factor) {
1.315     bowersj2  687:             return $floored_num / $factor;
                    688:         }
                    689:         return $points;
                    690:     };
                    691: 
                    692:     $points = $check_for_nearness->(10);
                    693:     $points = $check_for_nearness->(3);
                    694:     $points = $check_for_nearness->(4);
                    695:     
                    696:     return $points;
                    697: }
                    698: 
1.44      ng        699: #------------------ End of general use routines --------------------
1.87      www       700: 
                    701: #
                    702: # Find most similar essay
                    703: #
                    704: 
                    705: sub most_similar {
1.674     raeburn   706:     my ($uname,$udom,$symb,$uessay)=@_;
                    707: 
                    708:     unless ($symb) { return ''; }
                    709: 
                    710:     unless (ref($old_essays{$symb}) eq 'HASH') { return ''; }
1.87      www       711: 
                    712: # ignore spaces and punctuation
                    713: 
                    714:     $uessay=~s/\W+/ /gs;
                    715: 
1.282     www       716: # ignore empty submissions (occuring when only files are sent)
                    717: 
1.598     www       718:     unless ($uessay=~/\w+/s) { return ''; }
1.282     www       719: 
1.87      www       720: # these will be returned. Do not care if not at least 50 percent similar
1.88      www       721:     my $limit=0.6;
1.87      www       722:     my $sname='';
                    723:     my $sdom='';
                    724:     my $scrsid='';
                    725:     my $sessay='';
                    726: # go through all essays ...
1.674     raeburn   727:     foreach my $tkey (keys(%{$old_essays{$symb}})) {
1.426     albertel  728: 	my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey));
1.87      www       729: # ... except the same student
1.426     albertel  730:         next if (($tname eq $uname) && ($tdom eq $udom));
1.674     raeburn   731: 	my $tessay=$old_essays{$symb}{$tkey};
1.426     albertel  732: 	$tessay=~s/\W+/ /gs;
1.87      www       733: # String similarity gives up if not even limit
1.426     albertel  734: 	my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit);
1.87      www       735: # Found one
1.426     albertel  736: 	if ($tsimilar>$limit) {
                    737: 	    $limit=$tsimilar;
                    738: 	    $sname=$tname;
                    739: 	    $sdom=$tdom;
                    740: 	    $scrsid=$tcrsid;
1.674     raeburn   741: 	    $sessay=$old_essays{$symb}{$tkey};
1.426     albertel  742: 	}
1.87      www       743:     }
1.88      www       744:     if ($limit>0.6) {
1.87      www       745:        return ($sname,$sdom,$scrsid,$sessay,$limit);
                    746:     } else {
                    747:        return ('','','','',0);
                    748:     }
                    749: }
                    750: 
1.44      ng        751: #-------------------------------------------------------------------
                    752: 
                    753: #------------------------------------ Receipt Verification Routines
1.45      ng        754: #
1.602     www       755: 
                    756: sub initialverifyreceipt {
1.608     www       757:    my ($request,$symb) = @_;
1.602     www       758:    &commonJSfunctions($request);
1.694     bisitz    759:    return '<form name="gradingMenu" action=""><input type="submit" value="'.&mt('Verify Receipt Number.').'" />'.
1.602     www       760:         &Apache::lonnet::recprefix($env{'request.course.id'}).
                    761:         '-<input type="text" name="receipt" size="4" />'.
1.603     www       762:         '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
                    763:         '<input type="hidden" name="command" value="verify" />'.
                    764:         "</form>\n";
1.602     www       765: }
                    766: 
1.44      ng        767: #--- Check whether a receipt number is valid.---
                    768: sub verifyreceipt {
1.608     www       769:     my ($request,$symb)  = @_;
1.44      ng        770: 
1.257     albertel  771:     my $courseid = $env{'request.course.id'};
1.184     www       772:     my $receipt  = &Apache::lonnet::recprefix($courseid).'-'.
1.257     albertel  773: 	$env{'form.receipt'};
1.44      ng        774:     $receipt     =~ s/[^\-\d]//g;
                    775: 
1.487     albertel  776:     my $title.=
                    777: 	'<h3><span class="LC_info">'.
1.605     www       778: 	&mt('Verifying Receipt Number [_1]',$receipt).
                    779: 	'</span></h3>'."\n";
1.44      ng        780: 
                    781:     my ($string,$contents,$matches) = ('','',0);
1.56      matthew   782:     my (undef,undef,$fullname) = &getclasslist('all','0');
1.177     albertel  783:     
                    784:     my $receiptparts=0;
1.390     albertel  785:     if ($env{"course.$courseid.receiptalg"} eq 'receipt2' ||
                    786: 	$env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; }
1.177     albertel  787:     my $parts=['0'];
1.582     raeburn   788:     if ($receiptparts) {
                    789:         my $res_error; 
                    790:         ($parts)=&response_type($symb,\$res_error);
                    791:         if ($res_error) {
                    792:             return &navmap_errormsg();
                    793:         } 
                    794:     }
1.486     albertel  795:     
                    796:     my $header = 
                    797: 	&Apache::loncommon::start_data_table().
                    798: 	&Apache::loncommon::start_data_table_header_row().
1.487     albertel  799: 	'<th>&nbsp;'.&mt('Fullname').'&nbsp;</th>'."\n".
                    800: 	'<th>&nbsp;'.&mt('Username').'&nbsp;</th>'."\n".
                    801: 	'<th>&nbsp;'.&mt('Domain').'&nbsp;</th>';
1.486     albertel  802:     if ($receiptparts) {
1.487     albertel  803: 	$header.='<th>&nbsp;'.&mt('Problem Part').'&nbsp;</th>';
1.486     albertel  804:     }
                    805:     $header.=
                    806: 	&Apache::loncommon::end_data_table_header_row();
                    807: 
1.294     albertel  808:     foreach (sort 
                    809: 	     {
                    810: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                    811: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                    812: 		 }
                    813: 		 return $a cmp $b;
                    814: 	     } (keys(%$fullname))) {
1.44      ng        815: 	my ($uname,$udom)=split(/\:/);
1.177     albertel  816: 	foreach my $part (@$parts) {
                    817: 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
1.486     albertel  818: 		$contents.=
                    819: 		    &Apache::loncommon::start_data_table_row().
                    820: 		    '<td>&nbsp;'."\n".
1.177     albertel  821: 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
1.417     albertel  822: 		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
1.177     albertel  823: 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
                    824: 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
                    825: 		if ($receiptparts) {
                    826: 		    $contents.='<td>&nbsp;'.$part.'&nbsp;</td>';
                    827: 		}
1.486     albertel  828: 		$contents.= 
                    829: 		    &Apache::loncommon::end_data_table_row()."\n";
1.177     albertel  830: 		
                    831: 		$matches++;
                    832: 	    }
1.44      ng        833: 	}
                    834:     }
                    835:     if ($matches == 0) {
1.584     bisitz    836:         $string = $title
                    837:                  .'<p class="LC_warning">'
                    838:                  .&mt('No match found for the above receipt number.')
                    839:                  .'</p>';
1.44      ng        840:     } else {
1.324     albertel  841: 	$string = &jscriptNform($symb).$title.
1.487     albertel  842: 	    '<p>'.
1.584     bisitz    843: 	    &mt('The above receipt number matches the following [quant,_1,student].',$matches).
1.487     albertel  844: 	    '</p>'.
1.486     albertel  845: 	    $header.
                    846: 	    $contents.
                    847: 	    &Apache::loncommon::end_data_table()."\n";
1.44      ng        848:     }
1.614     www       849:     return $string;
1.44      ng        850: }
                    851: 
                    852: #--- This is called by a number of programs.
                    853: #--- Called from the Grading Menu - View/Grade an individual student
                    854: #--- Also called directly when one clicks on the subm button 
                    855: #    on the problem page.
1.30      ng        856: sub listStudents {
1.617     www       857:     my ($request,$symb,$submitonly) = @_;
1.49      albertel  858: 
1.746   ! raeburn   859:     my $is_tool = ($symb =~ /ext\.tool$/);
1.257     albertel  860:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                    861:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                    862:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
1.449     banghart  863:     my $getgroup  = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
1.617     www       864:     unless ($submitonly) {
                    865:        $submitonly= $env{'form.submitonly'} eq '' ? 'all' : $env{'form.submitonly'};
                    866:     }
1.49      albertel  867: 
1.632     www       868:     my $result='';
1.623     www       869:     my $res_error;
                    870:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
1.49      albertel  871: 
1.736     damieng   872:     my %js_lt = &Apache::lonlocal::texthash (
1.559     raeburn   873: 		'multiple' => 'Please select a student or group of students before clicking on the Next button.',
                    874: 		'single'   => 'Please select the student before clicking on the Next button.',
                    875: 	     );
1.736     damieng   876:     &js_escape(\%js_lt);
1.597     wenzelju  877:     $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
1.110     ng        878:     function checkSelect(checkBox) {
                    879: 	var ctr=0;
                    880: 	var sense="";
                    881: 	if (checkBox.length > 1) {
                    882: 	    for (var i=0; i<checkBox.length; i++) {
                    883: 		if (checkBox[i].checked) {
                    884: 		    ctr++;
                    885: 		}
                    886: 	    }
1.736     damieng   887: 	    sense = '$js_lt{'multiple'}';
1.110     ng        888: 	} else {
                    889: 	    if (checkBox.checked) {
                    890: 		ctr = 1;
                    891: 	    }
1.736     damieng   892: 	    sense = '$js_lt{'single'}';
1.110     ng        893: 	}
                    894: 	if (ctr == 0) {
1.485     albertel  895: 	    alert(sense);
1.110     ng        896: 	    return false;
                    897: 	}
                    898: 	document.gradesub.submit();
                    899:     }
                    900: 
                    901:     function reLoadList(formname) {
1.112     ng        902: 	if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;}
1.110     ng        903: 	formname.command.value = 'submission';
                    904: 	formname.submit();
                    905:     }
1.45      ng        906: LISTJAVASCRIPT
                    907: 
1.118     ng        908:     &commonJSfunctions($request);
1.41      ng        909:     $request->print($result);
1.39      ng        910: 
1.154     albertel  911:     my $gradeTable='<form action="/adm/grades" method="post" name="gradesub">'.
1.598     www       912: 	"\n";
1.485     albertel  913: 	
1.561     bisitz    914:     $gradeTable .= &Apache::lonhtmlcommon::start_pick_box();
1.745     raeburn   915:     unless ($is_tool) {
                    916:         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
                    917:                       .'<label><input type="radio" name="vProb" value="no" checked="checked" /> '.&mt('no').' </label>'."\n"
                    918:                       .'<label><input type="radio" name="vProb" value="yes" /> '.&mt('one student').' </label>'."\n"
                    919:                       .'<label><input type="radio" name="vProb" value="all" /> '.&mt('all students').' </label><br />'."\n"
                    920:                       .&Apache::lonhtmlcommon::row_closure();
                    921:         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer'))
                    922:                       .'<label><input type="radio" name="vAns" value="no"  /> '.&mt('no').' </label>'."\n"
                    923:                       .'<label><input type="radio" name="vAns" value="yes" /> '.&mt('one student').' </label>'."\n"
                    924:                       .'<label><input type="radio" name="vAns" value="all" checked="checked" /> '.&mt('all students').' </label><br />'."\n"
                    925:                       .&Apache::lonhtmlcommon::row_closure();
                    926:     }
1.485     albertel  927: 
                    928:     my $submission_options;
1.442     banghart  929:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
                    930:     my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status;
1.257     albertel  931:     $env{'form.Status'} = $saveStatus;
1.745     raeburn   932:     my %optiontext;
                    933:     if ($is_tool) {
                    934:         %optiontext = &Apache::lonlocal::texthash (
                    935:                           lastonly => 'last transaction',
                    936:                           last     => 'last transaction with details',
                    937:                           datesub  => 'all transactions',
                    938:                           all      => 'all transactions with details',
                    939:                       );
                    940:     } else {
                    941:         %optiontext = &Apache::lonlocal::texthash (
                    942:                           lastonly => 'last submission',
                    943:                           last     => 'last submission with details',
                    944:                           datesub  => 'all submissions',
                    945:                           all      => 'all submissions with details',
                    946:                       );
                    947:     }
1.485     albertel  948:     $submission_options.=
1.592     bisitz    949:         '<span class="LC_nobreak">'.
1.624     www       950:         '<label><input type="radio" name="lastSub" value="lastonly" /> '.
1.745     raeburn   951:         $optiontext{'lastonly'}.' </label></span>'."\n".
1.592     bisitz    952:         '<span class="LC_nobreak">'.
                    953:         '<label><input type="radio" name="lastSub" value="last" /> '.
1.745     raeburn   954:         $optiontext{'last'}.' </label></span>'."\n".
1.592     bisitz    955:         '<span class="LC_nobreak">'.
1.628     www       956:         '<label><input type="radio" name="lastSub" value="datesub" checked="checked" /> '.
1.745     raeburn   957:         $optiontext{'datesub'}.'</label></span>'."\n".
1.592     bisitz    958:         '<span class="LC_nobreak">'.
                    959:         '<label><input type="radio" name="lastSub" value="all" /> '.
1.745     raeburn   960:         $optiontext{'all'}.'</label></span>';
                    961:     my $viewtitle;
                    962:     if ($is_tool) {
                    963:         $viewtitle = &mt('View Transactions');
                    964:     } else {
                    965:         $viewtitle = &mt('View Submissions');
                    966:     }
                    967:     $gradeTable .= &Apache::lonhtmlcommon::row_title($viewtitle)
1.561     bisitz    968:                   .$submission_options
                    969:                   .&Apache::lonhtmlcommon::row_closure();
                    970: 
1.745     raeburn   971:     my $closure;
                    972:     if (($is_tool) && (exists($env{'form.Status'}))) {
                    973:         $closure = 1;
                    974:     }
1.561     bisitz    975:     $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments'))
                    976:                   .'<select name="increment">'
                    977:                   .'<option value="1">'.&mt('Whole Points').'</option>'
                    978:                   .'<option value=".5">'.&mt('Half Points').'</option>'
                    979:                   .'<option value=".25">'.&mt('Quarter Points').'</option>'
                    980:                   .'<option value=".1">'.&mt('Tenths of a Point').'</option>'
                    981:                   .'</select>'
1.745     raeburn   982:                   .&Apache::lonhtmlcommon::row_closure($closure);
1.485     albertel  983: 
                    984:     $gradeTable .= 
1.432     banghart  985:         &build_section_inputs().
1.45      ng        986: 	'<input type="hidden" name="submitonly"  value="'.$submitonly.'" />'."\n".
1.418     albertel  987: 	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.110     ng        988: 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
                    989: 
1.618     www       990:     if (exists($env{'form.Status'})) {
1.561     bisitz    991: 	$gradeTable .= '<input type="hidden" name="Status" value="'.$stu_status.'" />'."\n";
1.124     ng        992:     } else {
1.745     raeburn   993:         if ($is_tool) {
                    994:             $closure = 1;
                    995:         }
1.561     bisitz    996:         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status'))
                    997:                       .&Apache::lonhtmlcommon::StatusOptions(
                    998:                            $saveStatus,undef,1,'javascript:reLoadList(this.form);')
1.745     raeburn   999:                       .&Apache::lonhtmlcommon::row_closure($closure);
1.124     ng       1000:     }
1.112     ng       1001: 
1.745     raeburn  1002:     unless ($is_tool) {
                   1003:         $closure = 1;
                   1004:         $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism'))
                   1005:                       .'<input type="checkbox" name="checkPlag" checked="checked" />'
                   1006:                       .&Apache::lonhtmlcommon::row_closure($closure);
                   1007:     }
                   1008:     $gradeTable .= &Apache::lonhtmlcommon::end_pick_box();
                   1009:     my $regrademsg;
                   1010:     if ($is_tool) {
                   1011:         $regrademsg =&mt("To view/grade/regrade, click on the check box(es) next to the student's name(s). Then click on the Next button.");
                   1012:     } else {
                   1013:         $regrademsg = &mt("To view/grade/regrade a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.");
                   1014:     }
1.561     bisitz   1015:     $gradeTable .= '<p>'
1.745     raeburn  1016:                   .$regrademsg."\n"
1.561     bisitz   1017:                   .'<input type="hidden" name="command" value="processGroup" />'
                   1018:                   .'</p>';
1.249     albertel 1019: 
                   1020: # checkall buttons
                   1021:     $gradeTable.=&check_script('gradesub', 'stuinfo');
1.110     ng       1022:     $gradeTable.='<input type="button" '."\n".
1.589     bisitz   1023:         'onclick="javascript:checkSelect(this.form.stuinfo);" '."\n".
                   1024:         'value="'.&mt('Next').' &rarr;" /> <br />'."\n";
1.249     albertel 1025:     $gradeTable.=&check_buttons();
1.450     banghart 1026:     my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup);
1.474     albertel 1027:     $gradeTable.= &Apache::loncommon::start_data_table().
                   1028: 	&Apache::loncommon::start_data_table_header_row();
1.110     ng       1029:     my $loop = 0;
                   1030:     while ($loop < 2) {
1.485     albertel 1031: 	$gradeTable.='<th>'.&mt('No.').'</th><th>'.&mt('Select').'</th>'.
                   1032: 	    '<th>'.&nameUserString('header').'&nbsp;'.&mt('Section/Group').'</th>';
1.618     www      1033: 	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
1.485     albertel 1034: 	    foreach my $part (sort(@$partlist)) {
                   1035: 		my $display_part=
                   1036: 		    &get_display_part((split(/_/,$part))[0],$symb);
                   1037: 		$gradeTable.=
                   1038: 		    '<th>'.&mt('Part: [_1] Status',$display_part).'</th>';
1.110     ng       1039: 	    }
1.301     albertel 1040: 	} elsif ($submitonly eq 'queued') {
1.474     albertel 1041: 	    $gradeTable.='<th>'.&mt('Queue Status').'&nbsp;</th>';
1.110     ng       1042: 	}
                   1043: 	$loop++;
1.126     ng       1044: #	$gradeTable.='<td></td>' if ($loop%2 ==1);
1.41      ng       1045:     }
1.474     albertel 1046:     $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n";
1.41      ng       1047: 
1.45      ng       1048:     my $ctr = 0;
1.294     albertel 1049:     foreach my $student (sort 
                   1050: 			 {
                   1051: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   1052: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   1053: 			     }
                   1054: 			     return $a cmp $b;
                   1055: 			 }
                   1056: 			 (keys(%$fullname))) {
1.41      ng       1057: 	my ($uname,$udom) = split(/:/,$student);
1.301     albertel 1058: 
1.110     ng       1059: 	my %status = ();
1.301     albertel 1060: 
                   1061: 	if ($submitonly eq 'queued') {
                   1062: 	    my %queue_status = 
                   1063: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   1064: 							$udom,$uname);
                   1065: 	    next if (!defined($queue_status{'gradingqueue'}));
                   1066: 	    $status{'gradingqueue'} = $queue_status{'gradingqueue'};
                   1067: 	}
                   1068: 
1.618     www      1069: 	if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
1.324     albertel 1070: 	    (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
1.145     albertel 1071: 	    my $submitted = 0;
1.164     albertel 1072: 	    my $graded = 0;
1.248     albertel 1073: 	    my $incorrect = 0;
1.110     ng       1074: 	    foreach (keys(%status)) {
1.145     albertel 1075: 		$submitted = 1 if ($status{$_} ne 'nothing');
1.248     albertel 1076: 		$graded = 1 if ($status{$_} =~ /^ungraded/);
                   1077: 		$incorrect = 1 if ($status{$_} =~ /^incorrect/);
                   1078: 		
1.110     ng       1079: 		my ($foo,$partid,$foo1) = split(/\./,$_);
                   1080: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
1.145     albertel 1081: 		    $submitted = 0;
1.150     albertel 1082: 		    my ($part)=split(/\./,$partid);
1.110     ng       1083: 		    $gradeTable.='<input type="hidden" name="'.
1.150     albertel 1084: 			$student.':'.$part.':submitted_by" value="'.
1.110     ng       1085: 			$status{'resource.'.$partid.'.submitted_by'}.'" />';
                   1086: 		}
1.41      ng       1087: 	    }
1.248     albertel 1088: 	    
1.156     albertel 1089: 	    next if (!$submitted && ($submitonly eq 'yes' ||
                   1090: 				     $submitonly eq 'incorrect' ||
                   1091: 				     $submitonly eq 'graded'));
1.248     albertel 1092: 	    next if (!$graded && ($submitonly eq 'graded'));
                   1093: 	    next if (!$incorrect && $submitonly eq 'incorrect');
1.41      ng       1094: 	}
1.34      ng       1095: 
1.45      ng       1096: 	$ctr++;
1.249     albertel 1097: 	my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
1.452     banghart 1098:         my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
1.104     albertel 1099: 	if ( $perm{'vgr'} eq 'F' ) {
1.474     albertel 1100: 	    if ($ctr%2 ==1) {
                   1101: 		$gradeTable.= &Apache::loncommon::start_data_table_row();
                   1102: 	    }
1.126     ng       1103: 	    $gradeTable.='<td align="right">'.$ctr.'&nbsp;</td>'.
1.563     bisitz   1104:                '<td align="center"><label><input type="checkbox" name="stuinfo" value="'.
1.249     albertel 1105:                $student.':'.$$fullname{$student}.':::SECTION'.$section.
                   1106: 	       ')&nbsp;" />&nbsp;&nbsp;</label></td>'."\n".'<td>'.
                   1107: 	       &nameUserString(undef,$$fullname{$student},$uname,$udom).
1.474     albertel 1108: 	       '&nbsp;'.$section.($group ne '' ?'/'.$group:'').'</td>'."\n";
1.110     ng       1109: 
1.618     www      1110: 	    if ($submitonly ne 'all') {
1.524     raeburn  1111: 		foreach (sort(keys(%status))) {
1.485     albertel 1112: 		    next if ($_ =~ /^resource.*?submitted_by$/);
                   1113: 		    $gradeTable.='<td align="center">&nbsp;'.&mt($status{$_}).'&nbsp;</td>'."\n";
1.110     ng       1114: 		}
1.41      ng       1115: 	    }
1.126     ng       1116: #	    $gradeTable.='<td></td>' if ($ctr%2 ==1);
1.474     albertel 1117: 	    if ($ctr%2 ==0) {
                   1118: 		$gradeTable.=&Apache::loncommon::end_data_table_row()."\n";
                   1119: 	    }
1.41      ng       1120: 	}
                   1121:     }
1.110     ng       1122:     if ($ctr%2 ==1) {
1.126     ng       1123: 	$gradeTable.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>';
1.618     www      1124: 	    if (($submitonly ne 'queued') && ($submitonly ne 'all')) {
1.110     ng       1125: 		foreach (@$partlist) {
                   1126: 		    $gradeTable.='<td>&nbsp;</td>';
                   1127: 		}
1.301     albertel 1128: 	    } elsif ($submitonly eq 'queued') {
                   1129: 		$gradeTable.='<td>&nbsp;</td>';
1.110     ng       1130: 	    }
1.474     albertel 1131: 	$gradeTable.=&Apache::loncommon::end_data_table_row();
1.110     ng       1132:     }
                   1133: 
1.474     albertel 1134:     $gradeTable.=&Apache::loncommon::end_data_table()."\n".
1.589     bisitz   1135:         '<input type="button" '.
                   1136:         'onclick="javascript:checkSelect(this.form.stuinfo);" '.
                   1137:         'value="'.&mt('Next').' &rarr;" /></form>'."\n";
1.45      ng       1138:     if ($ctr == 0) {
1.96      albertel 1139: 	my $num_students=(scalar(keys(%$fullname)));
                   1140: 	if ($num_students eq 0) {
1.485     albertel 1141: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.&mt('There are no students currently enrolled.').'</span>';
1.96      albertel 1142: 	} else {
1.171     albertel 1143: 	    my $submissions='submissions';
                   1144: 	    if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; }
                   1145: 	    if ($submitonly eq 'graded'   ) { $submissions = 'ungraded submissions'; }
1.301     albertel 1146: 	    if ($submitonly eq 'queued'   ) { $submissions = 'queued submissions'; }
1.398     albertel 1147: 	    $gradeTable='<br />&nbsp;<span class="LC_warning">'.
1.709     bisitz   1148: 		&mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')',
1.485     albertel 1149: 		    $num_students).
                   1150: 		'</span><br />';
1.96      albertel 1151: 	}
1.46      ng       1152:     } elsif ($ctr == 1) {
1.474     albertel 1153: 	$gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/;
1.45      ng       1154:     }
                   1155:     $request->print($gradeTable);
1.44      ng       1156:     return '';
1.10      ng       1157: }
                   1158: 
1.44      ng       1159: #---- Called from the listStudents routine
1.249     albertel 1160: 
                   1161: sub check_script {
                   1162:     my ($form, $type)=@_;
1.597     wenzelju 1163:     my $chkallscript= &Apache::lonhtmlcommon::scripttag('
1.249     albertel 1164:     function checkall() {
                   1165:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1166:             ele = document.forms.'.$form.'.elements[i];
                   1167:             if (ele.name == "'.$type.'") {
                   1168:             document.forms.'.$form.'.elements[i].checked=true;
                   1169:                                        }
                   1170:         }
                   1171:     }
                   1172: 
                   1173:     function checksec() {
                   1174:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1175:             ele = document.forms.'.$form.'.elements[i];
                   1176:            string = document.forms.'.$form.'.chksec.value;
                   1177:            if
                   1178:           (ele.value.indexOf(":::SECTION"+string)>0) {
                   1179:               document.forms.'.$form.'.elements[i].checked=true;
                   1180:             }
                   1181:         }
                   1182:     }
                   1183: 
                   1184: 
                   1185:     function uncheckall() {
                   1186:         for (i=0; i<document.forms.'.$form.'.elements.length; i++) {
                   1187:             ele = document.forms.'.$form.'.elements[i];
                   1188:             if (ele.name == "'.$type.'") {
                   1189:             document.forms.'.$form.'.elements[i].checked=false;
                   1190:                                        }
                   1191:         }
                   1192:     }
                   1193: 
1.597     wenzelju 1194: '."\n");
1.249     albertel 1195:     return $chkallscript;
                   1196: }
                   1197: 
                   1198: sub check_buttons {
1.485     albertel 1199:     my $buttons.='<input type="button" onclick="checkall()" value="'.&mt('Check All').'" />';
                   1200:     $buttons.='<input type="button" onclick="uncheckall()" value="'.&mt('Uncheck All').'" />&nbsp;';
                   1201:     $buttons.='<input type="button" onclick="checksec()" value="'.&mt('Check Section/Group').'" />';
1.249     albertel 1202:     $buttons.='<input type="text" size="5" name="chksec" />&nbsp;';
                   1203:     return $buttons;
                   1204: }
                   1205: 
1.44      ng       1206: #     Displays the submissions for one student or a group of students
1.34      ng       1207: sub processGroup {
1.619     www      1208:     my ($request,$symb)  = @_;
1.41      ng       1209:     my $ctr        = 0;
1.155     albertel 1210:     my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo');
1.41      ng       1211:     my $total      = scalar(@stuchecked)-1;
1.45      ng       1212: 
1.396     banghart 1213:     foreach my $student (@stuchecked) {
                   1214: 	my ($uname,$udom,$fullname) = split(/:/,$student);
1.257     albertel 1215: 	$env{'form.student'}        = $uname;
                   1216: 	$env{'form.userdom'}        = $udom;
                   1217: 	$env{'form.fullname'}       = $fullname;
1.619     www      1218: 	&submission($request,$ctr,$total,$symb);
1.41      ng       1219: 	$ctr++;
                   1220:     }
                   1221:     return '';
1.35      ng       1222: }
1.34      ng       1223: 
1.44      ng       1224: #------------------------------------------------------------------------------------
                   1225: #
                   1226: #-------------------------- Next few routines handles grading by student, essentially
                   1227: #                           handles essay response type problem/part
                   1228: #
                   1229: #--- Javascript to handle the submission page functionality ---
                   1230: sub sub_page_js {
                   1231:     my $request = shift;
1.736     damieng  1232:     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
                   1233:     &js_escape(\$alertmsg);
1.597     wenzelju 1234:     $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
1.71      ng       1235:     function updateRadio(formname,id,weight) {
1.125     ng       1236: 	var gradeBox = formname["GD_BOX"+id];
                   1237: 	var radioButton = formname["RADVAL"+id];
                   1238: 	var oldpts = formname["oldpts"+id].value;
1.72      ng       1239: 	var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts;
1.71      ng       1240: 	gradeBox.value = pts;
                   1241: 	var resetbox = false;
                   1242: 	if (isNaN(pts) || pts < 0) {
1.539     riegler  1243: 	    alert("$alertmsg"+pts);
1.71      ng       1244: 	    for (var i=0; i<radioButton.length; i++) {
                   1245: 		if (radioButton[i].checked) {
                   1246: 		    gradeBox.value = i;
                   1247: 		    resetbox = true;
                   1248: 		}
                   1249: 	    }
                   1250: 	    if (!resetbox) {
                   1251: 		formtextbox.value = "";
                   1252: 	    }
                   1253: 	    return;
1.44      ng       1254: 	}
1.71      ng       1255: 
                   1256: 	if (pts > weight) {
                   1257: 	    var resp = confirm("You entered a value ("+pts+
                   1258: 			       ") greater than the weight for the part. Accept?");
                   1259: 	    if (resp == false) {
1.125     ng       1260: 		gradeBox.value = oldpts;
1.71      ng       1261: 		return;
                   1262: 	    }
1.44      ng       1263: 	}
1.13      albertel 1264: 
1.71      ng       1265: 	for (var i=0; i<radioButton.length; i++) {
                   1266: 	    radioButton[i].checked=false;
                   1267: 	    if (pts == i && pts != "") {
                   1268: 		radioButton[i].checked=true;
                   1269: 	    }
                   1270: 	}
                   1271: 	updateSelect(formname,id);
1.125     ng       1272: 	formname["stores"+id].value = "0";
1.41      ng       1273:     }
1.5       albertel 1274: 
1.72      ng       1275:     function writeBox(formname,id,pts) {
1.125     ng       1276: 	var gradeBox = formname["GD_BOX"+id];
1.71      ng       1277: 	if (checkSolved(formname,id) == 'update') {
                   1278: 	    gradeBox.value = pts;
                   1279: 	} else {
1.125     ng       1280: 	    var oldpts = formname["oldpts"+id].value;
1.72      ng       1281: 	    gradeBox.value = oldpts;
1.125     ng       1282: 	    var radioButton = formname["RADVAL"+id];
1.71      ng       1283: 	    for (var i=0; i<radioButton.length; i++) {
                   1284: 		radioButton[i].checked=false;
1.72      ng       1285: 		if (i == oldpts) {
1.71      ng       1286: 		    radioButton[i].checked=true;
                   1287: 		}
                   1288: 	    }
1.41      ng       1289: 	}
1.125     ng       1290: 	formname["stores"+id].value = "0";
1.71      ng       1291: 	updateSelect(formname,id);
                   1292: 	return;
1.41      ng       1293:     }
1.44      ng       1294: 
1.71      ng       1295:     function clearRadBox(formname,id) {
                   1296: 	if (checkSolved(formname,id) == 'noupdate') {
                   1297: 	    updateSelect(formname,id);
                   1298: 	    return;
                   1299: 	}
1.125     ng       1300: 	gradeSelect = formname["GD_SEL"+id];
1.71      ng       1301: 	for (var i=0; i<gradeSelect.length; i++) {
                   1302: 	    if (gradeSelect[i].selected) {
                   1303: 		var selectx=i;
                   1304: 	    }
                   1305: 	}
1.125     ng       1306: 	var stores = formname["stores"+id];
1.71      ng       1307: 	if (selectx == stores.value) { return };
1.125     ng       1308: 	var gradeBox = formname["GD_BOX"+id];
1.71      ng       1309: 	gradeBox.value = "";
1.125     ng       1310: 	var radioButton = formname["RADVAL"+id];
1.71      ng       1311: 	for (var i=0; i<radioButton.length; i++) {
                   1312: 	    radioButton[i].checked=false;
                   1313: 	}
                   1314: 	stores.value = selectx;
                   1315:     }
1.5       albertel 1316: 
1.71      ng       1317:     function checkSolved(formname,id) {
1.125     ng       1318: 	if (formname["solved"+id].value == "correct_by_student" && formname.overRideScore.value == 'no') {
1.118     ng       1319: 	    var reply = confirm("This problem has been graded correct by the computer. Do you want to change the score?");
                   1320: 	    if (!reply) {return "noupdate";}
1.120     ng       1321: 	    formname.overRideScore.value = 'yes';
1.41      ng       1322: 	}
1.71      ng       1323: 	return "update";
1.13      albertel 1324:     }
1.71      ng       1325: 
                   1326:     function updateSelect(formname,id) {
1.125     ng       1327: 	formname["GD_SEL"+id][0].selected = true;
1.71      ng       1328: 	return;
1.41      ng       1329:     }
1.33      ng       1330: 
1.121     ng       1331: //=========== Check that a point is assigned for all the parts  ============
1.71      ng       1332:     function checksubmit(formname,val,total,parttot) {
1.121     ng       1333: 	formname.gradeOpt.value = val;
1.71      ng       1334: 	if (val == "Save & Next") {
                   1335: 	    for (i=0;i<=total;i++) {
                   1336: 		for (j=0;j<parttot;j++) {
1.125     ng       1337: 		    var partid = formname["partid"+i+"_"+j].value;
1.127     ng       1338: 		    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1.125     ng       1339: 			var points = formname["GD_BOX"+i+"_"+partid].value;
1.71      ng       1340: 			if (points == "") {
1.125     ng       1341: 			    var name = formname["name"+i].value;
1.129     ng       1342: 			    var studentID = (name != '' ? name : formname["unamedom"+i].value);
                   1343: 			    var resp = confirm("You did not assign a score for "+studentID+
                   1344: 					       ", part "+partid+". Continue?");
1.71      ng       1345: 			    if (resp == false) {
1.125     ng       1346: 				formname["GD_BOX"+i+"_"+partid].focus();
1.71      ng       1347: 				return false;
                   1348: 			    }
                   1349: 			}
                   1350: 		    }
                   1351: 		}
                   1352: 	    }
                   1353: 	}
1.120     ng       1354: 	formname.submit();
                   1355:     }
                   1356: 
1.71      ng       1357: //======= Check that a score is assigned for all the problems (page/sequence grading only) =========
                   1358:     function checkSubmitPage(formname,total) {
                   1359: 	noscore = new Array(100);
                   1360: 	var ptr = 0;
                   1361: 	for (i=1;i<total;i++) {
1.125     ng       1362: 	    var partid = formname["q_"+i].value;
1.127     ng       1363: 	    if (formname["GD_SEL"+i+"_"+partid][0].selected) {
1.125     ng       1364: 		var points = formname["GD_BOX"+i+"_"+partid].value;
                   1365: 		var status = formname["solved"+i+"_"+partid].value;
1.71      ng       1366: 		if (points == "" && status != "correct_by_student") {
                   1367: 		    noscore[ptr] = i;
                   1368: 		    ptr++;
                   1369: 		}
                   1370: 	    }
                   1371: 	}
                   1372: 	if (ptr != 0) {
                   1373: 	    var sense = ptr == 1 ? ": " : "s: ";
                   1374: 	    var prolist = "";
                   1375: 	    if (ptr == 1) {
                   1376: 		prolist = noscore[0];
                   1377: 	    } else {
                   1378: 		var i = 0;
                   1379: 		while (i < ptr-1) {
                   1380: 		    prolist += noscore[i]+", ";
                   1381: 		    i++;
                   1382: 		}
                   1383: 		prolist += "and "+noscore[i];
                   1384: 	    }
                   1385: 	    var resp = confirm("You did not assign any score for the following problem"+sense+prolist+". Continue?");
                   1386: 	    if (resp == false) {
                   1387: 		return false;
                   1388: 	    }
                   1389: 	}
1.45      ng       1390: 
1.71      ng       1391: 	formname.submit();
                   1392:     }
                   1393: SUBJAVASCRIPT
                   1394: }
1.45      ng       1395: 
1.71      ng       1396: #--- javascript for essay type problem --
                   1397: sub sub_page_kw_js {
                   1398:     my $request = shift;
1.80      ng       1399:     my $iconpath = $request->dir_config('lonIconsURL');
1.118     ng       1400:     &commonJSfunctions($request);
1.350     albertel 1401: 
1.629     www      1402:     my $inner_js_msg_central= (<<INNERJS);
                   1403: <script type="text/javascript">
1.350     albertel 1404:     function checkInput() {
                   1405:       opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value);
                   1406:       var nmsg   = opener.document.SCORE.savemsgN.value;
                   1407:       var usrctr = document.msgcenter.usrctr.value;
                   1408:       var newval = opener.document.SCORE["newmsg"+usrctr];
                   1409:       newval.value = opener.checkEntities(document.msgcenter.newmsg.value);
                   1410: 
                   1411:       var msgchk = "";
                   1412:       if (document.msgcenter.subchk.checked) {
                   1413:          msgchk = "msgsub,";
                   1414:       }
                   1415:       var includemsg = 0;
                   1416:       for (var i=1; i<=nmsg; i++) {
                   1417:           var opnmsg = opener.document.SCORE["savemsg"+i];
                   1418:           var frmmsg = document.msgcenter["msg"+i];
                   1419:           opnmsg.value = opener.checkEntities(frmmsg.value);
                   1420:           var showflg = opener.document.SCORE["shownOnce"+i];
                   1421:           showflg.value = "1";
                   1422:           var chkbox = document.msgcenter["msgn"+i];
                   1423:           if (chkbox.checked) {
                   1424:              msgchk += "savemsg"+i+",";
                   1425:              includemsg = 1;
                   1426:           }
                   1427:       }
                   1428:       if (document.msgcenter.newmsgchk.checked) {
                   1429:          msgchk += "newmsg"+usrctr;
                   1430:          includemsg = 1;
                   1431:       }
                   1432:       imgformname = opener.document.SCORE["mailicon"+usrctr];
                   1433:       imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif");
                   1434:       var includemsg = opener.document.SCORE["includemsg"+usrctr];
                   1435:       includemsg.value = msgchk;
                   1436: 
                   1437:       self.close()
                   1438: 
                   1439:     }
1.629     www      1440: </script>
1.350     albertel 1441: INNERJS
                   1442: 
1.629     www      1443:     my $inner_js_highlight_central= (<<INNERJS);
                   1444: <script type="text/javascript">
1.351     albertel 1445:     function updateChoice(flag) {
                   1446:       opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr);
                   1447:       opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize);
                   1448:       opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle);
                   1449:       opener.document.SCORE.refresh.value = "on";
                   1450:       if (opener.document.SCORE.keywords.value!=""){
                   1451:          opener.document.SCORE.submit();
                   1452:       }
                   1453:       self.close()
                   1454:     }
1.629     www      1455: </script>
1.351     albertel 1456: INNERJS
                   1457: 
                   1458:     my $start_page_msg_central = 
                   1459:         &Apache::loncommon::start_page('Message Central',$inner_js_msg_central,
                   1460: 				       {'js_ready'  => 1,
                   1461: 					'only_body' => 1,
                   1462: 					'bgcolor'   =>'#FFFFFF',});
                   1463:     my $end_page_msg_central = 
                   1464: 	&Apache::loncommon::end_page({'js_ready' => 1});
                   1465: 
                   1466: 
                   1467:     my $start_page_highlight_central = 
                   1468:         &Apache::loncommon::start_page('Highlight Central',
                   1469: 				       $inner_js_highlight_central,
1.350     albertel 1470: 				       {'js_ready'  => 1,
                   1471: 					'only_body' => 1,
                   1472: 					'bgcolor'   =>'#FFFFFF',});
1.351     albertel 1473:     my $end_page_highlight_central = 
1.350     albertel 1474: 	&Apache::loncommon::end_page({'js_ready' => 1});
                   1475: 
1.219     www      1476:     my $docopen=&Apache::lonhtmlcommon::javascript_docopen();
1.236     albertel 1477:     $docopen=~s/^document\.//;
1.736     damieng  1478:     my %js_lt = &Apache::lonlocal::texthash(
1.652     raeburn  1479:                 keyw => 'Keywords list, separated by a space. Add/delete to list if desired.',
                   1480:                 plse => 'Please select a word or group of words from document and then click this link.',
                   1481:                 adds => 'Add selection to keyword list? Edit if desired.',
1.736     damieng  1482:                 col1 => 'red',
                   1483:                 col2 => 'green',
                   1484:                 col3 => 'blue',
                   1485:                 siz1 => 'normal',
                   1486:                 siz2 => '+1',
                   1487:                 siz3 => '+2',
                   1488:                 sty1 => 'normal',
                   1489:                 sty2 => 'italic',
                   1490:                 sty3 => 'bold',
                   1491:              );
                   1492:     my %html_js_lt = &Apache::lonlocal::texthash(
1.652     raeburn  1493:                 comp => 'Compose Message for: ',
                   1494:                 incl => 'Include',
1.656     raeburn  1495:                 type => 'Type',
1.652     raeburn  1496:                 subj => 'Subject',
                   1497:                 mesa => 'Message',
                   1498:                 new  => 'New',
                   1499:                 save => 'Save',
                   1500:                 canc => 'Cancel',
                   1501:                 kehi => 'Keyword Highlight Options',
                   1502:                 txtc => 'Text Color',
                   1503:                 font => 'Font Size',
1.656     raeburn  1504:                 fnst => 'Font Style',
1.652     raeburn  1505:              );
1.736     damieng  1506:     &js_escape(\%js_lt);
                   1507:     &html_escape(\%html_js_lt);
                   1508:     &js_escape(\%html_js_lt);
1.597     wenzelju 1509:     $request->print(&Apache::lonhtmlcommon::scripttag(<<SUBJAVASCRIPT));
1.45      ng       1510: 
1.44      ng       1511: //===================== Show list of keywords ====================
1.122     ng       1512:   function keywords(formname) {
1.736     damieng  1513:     var nret = prompt("$js_lt{'keyw'}",formname.keywords.value);
1.44      ng       1514:     if (nret==null) return;
1.122     ng       1515:     formname.keywords.value = nret;
1.44      ng       1516: 
1.122     ng       1517:     if (formname.keywords.value != "") {
1.128     ng       1518: 	formname.refresh.value = "on";
1.122     ng       1519: 	formname.submit();
1.44      ng       1520:     }
                   1521:     return;
                   1522:   }
                   1523: 
                   1524: //===================== Script to view submitted by ==================
                   1525:   function viewSubmitter(submitter) {
                   1526:     document.SCORE.refresh.value = "on";
                   1527:     document.SCORE.NCT.value = "1";
                   1528:     document.SCORE.unamedom0.value = submitter;
                   1529:     document.SCORE.submit();
                   1530:     return;
                   1531:   }
                   1532: 
                   1533: //===================== Script to add keyword(s) ==================
                   1534:   function getSel() {
                   1535:     if (document.getSelection) txt = document.getSelection();
                   1536:     else if (document.selection) txt = document.selection.createRange().text;
                   1537:     else return;
                   1538:     var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," ");
                   1539:     if (cleantxt=="") {
1.736     damieng  1540: 	alert("$js_lt{'plse'}");
1.44      ng       1541: 	return;
                   1542:     }
1.736     damieng  1543:     var nret = prompt("$js_lt{'adds'}",cleantxt);
1.44      ng       1544:     if (nret==null) return;
1.127     ng       1545:     document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret;
1.44      ng       1546:     if (document.SCORE.keywords.value != "") {
1.127     ng       1547: 	document.SCORE.refresh.value = "on";
1.44      ng       1548: 	document.SCORE.submit();
                   1549:     }
                   1550:     return;
                   1551:   }
                   1552: 
                   1553: //====================== Script for composing message ==============
1.80      ng       1554:    // preload images
                   1555:    img1 = new Image();
                   1556:    img1.src = "$iconpath/mailbkgrd.gif";
                   1557:    img2 = new Image();
                   1558:    img2.src = "$iconpath/mailto.gif";
                   1559: 
1.44      ng       1560:   function msgCenter(msgform,usrctr,fullname) {
                   1561:     var Nmsg  = msgform.savemsgN.value;
                   1562:     savedMsgHeader(Nmsg,usrctr,fullname);
                   1563:     var subject = msgform.msgsub.value;
1.127     ng       1564:     var msgchk = document.SCORE["includemsg"+usrctr].value;
1.44      ng       1565:     re = /msgsub/;
                   1566:     var shwsel = "";
                   1567:     if (re.test(msgchk)) { shwsel = "checked" }
1.123     ng       1568:     subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject);
                   1569:     displaySubject(checkEntities(subject),shwsel);
1.44      ng       1570:     for (var i=1; i<=Nmsg; i++) {
1.123     ng       1571: 	var testmsg = "savemsg"+i+",";
                   1572: 	re = new RegExp(testmsg,"g");
1.44      ng       1573: 	shwsel = "";
                   1574: 	if (re.test(msgchk)) { shwsel = "checked" }
1.125     ng       1575: 	var message = document.SCORE["savemsg"+i].value;
1.126     ng       1576: 	message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message);
1.123     ng       1577: 	displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages,
                   1578: 	                                   //any &lt; is already converted to <, etc. However, only once!!
1.44      ng       1579:     }
1.125     ng       1580:     newmsg = document.SCORE["newmsg"+usrctr].value;
1.44      ng       1581:     shwsel = "";
                   1582:     re = /newmsg/;
                   1583:     if (re.test(msgchk)) { shwsel = "checked" }
                   1584:     newMsg(newmsg,shwsel);
                   1585:     msgTail(); 
                   1586:     return;
                   1587:   }
                   1588: 
1.123     ng       1589:   function checkEntities(strx) {
                   1590:     if (strx.length == 0) return strx;
                   1591:     var orgStr = ["&", "<", ">", '"']; 
                   1592:     var newStr = ["&amp;", "&lt;", "&gt;", "&quot;"];
                   1593:     var counter = 0;
                   1594:     while (counter < 4) {
                   1595: 	strx = strReplace(strx,orgStr[counter],newStr[counter]);
                   1596: 	counter++;
                   1597:     }
                   1598:     return strx;
                   1599:   }
                   1600: 
                   1601:   function strReplace(strx, orgStr, newStr) {
                   1602:     return strx.split(orgStr).join(newStr);
                   1603:   }
                   1604: 
1.44      ng       1605:   function savedMsgHeader(Nmsg,usrctr,fullname) {
1.76      ng       1606:     var height = 70*Nmsg+250;
1.44      ng       1607:     if (height > 600) {
                   1608: 	height = 600;
                   1609:     }
1.118     ng       1610:     var xpos = (screen.width-600)/2;
                   1611:     xpos = (xpos < 0) ? '0' : xpos;
                   1612:     var ypos = (screen.height-height)/2-30;
                   1613:     ypos = (ypos < 0) ? '0' : ypos;
                   1614: 
1.668     www      1615:     pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height);
1.76      ng       1616:     pWin.focus();
                   1617:     pDoc = pWin.document;
1.219     www      1618:     pDoc.$docopen;
1.351     albertel 1619:     pDoc.write('$start_page_msg_central');
1.76      ng       1620: 
                   1621:     pDoc.write("<form action=\\"inactive\\" name=\\"msgcenter\\">");
                   1622:     pDoc.write("<input value=\\""+usrctr+"\\" name=\\"usrctr\\" type=\\"hidden\\">");
1.736     damieng  1623:     pDoc.write("<h1>&nbsp;$html_js_lt{'comp'}\"+fullname+\"<\\/h1>");
1.76      ng       1624: 
1.676     golterma 1625:     pDoc.write('<table style="border:1px solid black;"><tr>');
1.736     damieng  1626:     pDoc.write("<td><b>$html_js_lt{'incl'}<\\/b><\\/td><td><b>$html_js_lt{'type'}<\\/b><\\/td><td><b>$html_js_lt{'mesa'}<\\/td><\\/tr>");
1.44      ng       1627: }
                   1628:     function displaySubject(msg,shwsel) {
1.76      ng       1629:     pDoc = pWin.document;
1.676     golterma 1630:     pDoc.write("<tr>");
                   1631:     pDoc.write("<td align=\\"center\\"><input name=\\"subchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1.736     damieng  1632:     pDoc.write("<td>$html_js_lt{'subj'}<\\/td>");
1.676     golterma 1633:     pDoc.write("<td><input name=\\"msgsub\\" type=\\"text\\" value=\\""+msg+"\\"size=\\"40\\" maxlength=\\"80\\"><\\/td><\\/tr>");
1.44      ng       1634: }
                   1635: 
1.72      ng       1636:   function displaySavedMsg(ctr,msg,shwsel) {
1.76      ng       1637:     pDoc = pWin.document;
1.676     golterma 1638:     pDoc.write("<tr>");
                   1639:     pDoc.write("<td align=\\"center\\"><input name=\\"msgn"+ctr+"\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1.465     albertel 1640:     pDoc.write("<td align=\\"center\\">"+ctr+"<\\/td>");
                   1641:     pDoc.write("<td><textarea name=\\"msg"+ctr+"\\" cols=\\"60\\" rows=\\"3\\">"+msg+"<\\/textarea><\\/td><\\/tr>");
1.44      ng       1642: }
                   1643: 
                   1644:   function newMsg(newmsg,shwsel) {
1.76      ng       1645:     pDoc = pWin.document;
1.676     golterma 1646:     pDoc.write("<tr>");
                   1647:     pDoc.write("<td align=\\"center\\"><input name=\\"newmsgchk\\" type=\\"checkbox\\"" +shwsel+"><\\/td>");
1.736     damieng  1648:     pDoc.write("<td align=\\"center\\">$html_js_lt{'new'}<\\/td>");
1.465     albertel 1649:     pDoc.write("<td><textarea name=\\"newmsg\\" cols=\\"60\\" rows=\\"3\\" onchange=\\"javascript:this.form.newmsgchk.checked=true\\" >"+newmsg+"<\\/textarea><\\/td><\\/tr>");
1.44      ng       1650: }
                   1651: 
                   1652:   function msgTail() {
1.76      ng       1653:     pDoc = pWin.document;
1.676     golterma 1654:     //pDoc.write("<\\/table>");
1.465     albertel 1655:     pDoc.write("<\\/td><\\/tr><\\/table>&nbsp;");
1.736     damieng  1656:     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:checkInput()\\">&nbsp;&nbsp;");
                   1657:     pDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\"><br /><br />");
1.465     albertel 1658:     pDoc.write("<\\/form>");
1.351     albertel 1659:     pDoc.write('$end_page_msg_central');
1.128     ng       1660:     pDoc.close();
1.44      ng       1661: }
                   1662: 
                   1663: //====================== Script for keyword highlight options ==============
                   1664:   function kwhighlight() {
                   1665:     var kwclr    = document.SCORE.kwclr.value;
                   1666:     var kwsize   = document.SCORE.kwsize.value;
                   1667:     var kwstyle  = document.SCORE.kwstyle.value;
                   1668:     var redsel = "";
                   1669:     var grnsel = "";
                   1670:     var blusel = "";
1.736     damieng  1671:     var txtcol1 = "$js_lt{'col1'}";
                   1672:     var txtcol2 = "$js_lt{'col2'}";
                   1673:     var txtcol3 = "$js_lt{'col3'}";
                   1674:     var txtsiz1 = "$js_lt{'siz1'}";
                   1675:     var txtsiz2 = "$js_lt{'siz2'}";
                   1676:     var txtsiz3 = "$js_lt{'siz3'}";
                   1677:     var txtsty1 = "$js_lt{'sty1'}";
                   1678:     var txtsty2 = "$js_lt{'sty2'}";
                   1679:     var txtsty3 = "$js_lt{'sty3'}";
1.718     bisitz   1680:     if (kwclr=="red")   {var redsel="checked='checked'"};
                   1681:     if (kwclr=="green") {var grnsel="checked='checked'"};
                   1682:     if (kwclr=="blue")  {var blusel="checked='checked'"};
1.44      ng       1683:     var sznsel = "";
                   1684:     var sz1sel = "";
                   1685:     var sz2sel = "";
1.718     bisitz   1686:     if (kwsize=="0")  {var sznsel="checked='checked'"};
                   1687:     if (kwsize=="+1") {var sz1sel="checked='checked'"};
                   1688:     if (kwsize=="+2") {var sz2sel="checked='checked'"};
1.44      ng       1689:     var synsel = "";
                   1690:     var syisel = "";
                   1691:     var sybsel = "";
1.718     bisitz   1692:     if (kwstyle=="")    {var synsel="checked='checked'"};
                   1693:     if (kwstyle=="<i>") {var syisel="checked='checked'"};
                   1694:     if (kwstyle=="<b>") {var sybsel="checked='checked'"};
1.44      ng       1695:     highlightCentral();
1.718     bisitz   1696:     highlightbody('red',txtcol1,redsel,'0',txtsiz1,sznsel,'',txtsty1,synsel);
                   1697:     highlightbody('green',txtcol2,grnsel,'+1',txtsiz2,sz1sel,'<i>',txtsty2,syisel);
                   1698:     highlightbody('blue',txtcol3,blusel,'+2',txtsiz3,sz2sel,'<b>',txtsty3,sybsel);
1.44      ng       1699:     highlightend();
                   1700:     return;
                   1701:   }
                   1702: 
                   1703:   function highlightCentral() {
1.76      ng       1704: //    if (window.hwdWin) window.hwdWin.close();
1.118     ng       1705:     var xpos = (screen.width-400)/2;
                   1706:     xpos = (xpos < 0) ? '0' : xpos;
                   1707:     var ypos = (screen.height-330)/2-30;
                   1708:     ypos = (ypos < 0) ? '0' : ypos;
                   1709: 
1.206     albertel 1710:     hwdWin = window.open('', 'KeywordHighlightCentral', 'resizeable=yes,toolbar=no,location=no,scrollbars=no,width=400,height=300,screenx='+xpos+',screeny='+ypos);
1.76      ng       1711:     hwdWin.focus();
                   1712:     var hDoc = hwdWin.document;
1.219     www      1713:     hDoc.$docopen;
1.351     albertel 1714:     hDoc.write('$start_page_highlight_central');
1.76      ng       1715:     hDoc.write("<form action=\\"inactive\\" name=\\"hlCenter\\">");
1.736     damieng  1716:     hDoc.write("<h1>$html_js_lt{'kehi'}<\\/h1>");
1.76      ng       1717: 
1.718     bisitz   1718:     hDoc.write('<table border="0" width="100%"><tr style="background-color:#A1D676">');
1.736     damieng  1719:     hDoc.write("<th>$html_js_lt{'txtc'}<\\/th><th>$html_js_lt{'font'}<\\/th><th>$html_js_lt{'fnst'}<\\/th><\\/tr>");
1.44      ng       1720:   }
                   1721: 
                   1722:   function highlightbody(clrval,clrtxt,clrsel,szval,sztxt,szsel,syval,sytxt,sysel) { 
1.76      ng       1723:     var hDoc = hwdWin.document;
1.718     bisitz   1724:     hDoc.write("<tr>");
1.76      ng       1725:     hDoc.write("<td align=\\"left\\">");
1.718     bisitz   1726:     hDoc.write("<input name=\\"kwdclr\\" type=\\"radio\\" value=\\""+clrval+"\\" "+clrsel+" \\/>&nbsp;"+clrtxt+"<\\/td>");
1.76      ng       1727:     hDoc.write("<td align=\\"left\\">");
1.718     bisitz   1728:     hDoc.write("<input name=\\"kwdsize\\" type=\\"radio\\" value=\\""+szval+"\\" "+szsel+" \\/>&nbsp;"+sztxt+"<\\/td>");
1.76      ng       1729:     hDoc.write("<td align=\\"left\\">");
1.718     bisitz   1730:     hDoc.write("<input name=\\"kwdstyle\\" type=\\"radio\\" value=\\""+syval+"\\" "+sysel+" \\/>&nbsp;"+sytxt+"<\\/td>");
1.465     albertel 1731:     hDoc.write("<\\/tr>");
1.44      ng       1732:   }
                   1733: 
                   1734:   function highlightend() { 
1.76      ng       1735:     var hDoc = hwdWin.document;
1.718     bisitz   1736:     hDoc.write("<\\/table><br \\/>");
1.736     damieng  1737:     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'save'}\\" onclick=\\"javascript:updateChoice(1)\\" \\/>&nbsp;&nbsp;");
                   1738:     hDoc.write("<input type=\\"button\\" value=\\"$html_js_lt{'canc'}\\" onclick=\\"self.close()\\" \\/><br /><br />");
1.465     albertel 1739:     hDoc.write("<\\/form>");
1.351     albertel 1740:     hDoc.write('$end_page_highlight_central');
1.128     ng       1741:     hDoc.close();
1.44      ng       1742:   }
                   1743: 
                   1744: SUBJAVASCRIPT
                   1745: }
                   1746: 
1.349     albertel 1747: sub get_increment {
1.348     bowersj2 1748:     my $increment = $env{'form.increment'};
                   1749:     if ($increment != 1 && $increment != .5 && $increment != .25 &&
                   1750:         $increment != .1) {
                   1751:         $increment = 1;
                   1752:     }
                   1753:     return $increment;
                   1754: }
                   1755: 
1.585     bisitz   1756: sub gradeBox_start {
                   1757:     return (
                   1758:         &Apache::loncommon::start_data_table()
                   1759:        .&Apache::loncommon::start_data_table_header_row()
                   1760:        .'<th>'.&mt('Part').'</th>'
                   1761:        .'<th>'.&mt('Points').'</th>'
                   1762:        .'<th>&nbsp;</th>'
                   1763:        .'<th>'.&mt('Assign Grade').'</th>'
                   1764:        .'<th>'.&mt('Weight').'</th>'
                   1765:        .'<th>'.&mt('Grade Status').'</th>'
                   1766:        .&Apache::loncommon::end_data_table_header_row()
                   1767:     );
                   1768: }
                   1769: 
                   1770: sub gradeBox_end {
                   1771:     return (
                   1772:         &Apache::loncommon::end_data_table()
                   1773:     );
                   1774: }
1.71      ng       1775: #--- displays the grading box, used in essay type problem and grading by page/sequence
                   1776: sub gradeBox {
1.322     albertel 1777:     my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
1.381     albertel 1778:     my $checkIcon = '<img alt="'.&mt('Check Mark').
1.485     albertel 1779: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
1.71      ng       1780:     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
1.466     albertel 1781:     my $wgtmsg = ($wgt > 0) ? &mt('(problem weight)') 
                   1782:                            : '<span class="LC_info">'.&mt('problem weight assigned by computer').'</span>';
1.71      ng       1783:     $wgt       = ($wgt > 0 ? $wgt : '1');
                   1784:     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
1.320     albertel 1785: 		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
1.695     bisitz   1786:     my $data_WGT='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
1.466     albertel 1787:     my $display_part= &get_display_part($partid,$symb);
1.270     albertel 1788:     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
                   1789: 				       [$partid]);
                   1790:     my $aggtries = $$record{'resource.'.$partid.'.tries'};
1.269     raeburn  1791:     if ($last_resets{$partid}) {
                   1792:         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
                   1793:     }
1.695     bisitz   1794:     my $result=&Apache::loncommon::start_data_table_row();
1.71      ng       1795:     my $ctr = 0;
1.348     bowersj2 1796:     my $thisweight = 0;
1.349     albertel 1797:     my $increment = &get_increment();
1.485     albertel 1798: 
                   1799:     my $radio.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
1.348     bowersj2 1800:     while ($thisweight<=$wgt) {
1.532     bisitz   1801: 	$radio.= '<td><span class="LC_nobreak"><label><input type="radio" name="RADVAL'.$counter.'_'.$partid.'" '.
1.589     bisitz   1802:         'onclick="javascript:writeBox(this.form,\''.$counter.'_'.$partid.'\','.
1.348     bowersj2 1803: 	    $thisweight.')" value="'.$thisweight.'" '.
1.401     albertel 1804: 	    ($score eq $thisweight ? 'checked="checked"':'').' /> '.$thisweight."</label></span></td>\n";
1.485     albertel 1805: 	$radio.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
1.348     bowersj2 1806:         $thisweight += $increment;
1.71      ng       1807: 	$ctr++;
                   1808:     }
1.485     albertel 1809:     $radio.='</tr></table>';
                   1810: 
                   1811:     my $line.='<input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
1.71      ng       1812: 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
1.589     bisitz   1813: 	'onchange="javascript:updateRadio(this.form,\''.$counter.'_'.$partid.'\','.
1.71      ng       1814: 	$wgt.')" /></td>'."\n";
1.485     albertel 1815:     $line.='<td>/'.$wgt.' '.$wgtmsg.
1.71      ng       1816: 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
1.585     bisitz   1817: 	' </td>'."\n";
                   1818:     $line.='<td><select name="GD_SEL'.$counter.'_'.$partid.'" '.
1.589     bisitz   1819: 	'onchange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
1.71      ng       1820:     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
1.485     albertel 1821: 	$line.='<option></option>'.
                   1822: 	    '<option value="excused" selected="selected">'.&mt('excused').'</option>';
1.71      ng       1823:     } else {
1.485     albertel 1824: 	$line.='<option selected="selected"></option>'.
                   1825: 	    '<option value="excused" >'.&mt('excused').'</option>';
1.71      ng       1826:     }
1.485     albertel 1827:     $line.='<option value="reset status">'.&mt('reset status').'</option></select>'."\n";
                   1828: 
                   1829: 
                   1830:     $result .= 
1.695     bisitz   1831: 	    '<td>'.$data_WGT.$display_part.'</td><td>'.$radio.'</td><td>'.&mt('or').'</td><td>'.$line.'</td>';
1.585     bisitz   1832:     $result.=&Apache::loncommon::end_data_table_row();
1.695     bisitz   1833:     $result.=&Apache::loncommon::start_data_table_row().'<td colspan="6">';
1.71      ng       1834:     $result.='<input type="hidden" name="stores'.$counter.'_'.$partid.'" value="" />'."\n".
                   1835: 	'<input type="hidden" name="oldpts'.$counter.'_'.$partid.'" value="'.$score.'" />'."\n".
                   1836: 	'<input type="hidden" name="solved'.$counter.'_'.$partid.'" value="'.
1.269     raeburn  1837: 	$$record{'resource.'.$partid.'.solved'}.'" />'."\n".
                   1838:         '<input type="hidden" name="totaltries'.$counter.'_'.$partid.'" value="'.
                   1839:         $$record{'resource.'.$partid.'.tries'}.'" />'."\n".
                   1840:         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
                   1841:         $aggtries.'" />'."\n";
1.582     raeburn  1842:     my $res_error;
                   1843:     $result.=&handback_box($symb,$uname,$udom,$counter,$partid,$record,\$res_error);
1.695     bisitz   1844:     $result.='</td>'.&Apache::loncommon::end_data_table_row();
1.582     raeburn  1845:     if ($res_error) {
                   1846:         return &navmap_errormsg();
                   1847:     }
1.318     banghart 1848:     return $result;
                   1849: }
1.322     albertel 1850: 
                   1851: sub handback_box {
1.623     www      1852:     my ($symb,$uname,$udom,$counter,$partid,$record,$res_error_pointer) = @_;
                   1853:     my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error_pointer);
1.323     banghart 1854:     my (@respids);
1.652     raeburn  1855:     my @part_response_id = &flatten_responseType($responseType);
1.375     albertel 1856:     foreach my $part_response_id (@part_response_id) {
                   1857:     	my ($part,$resp) = @{ $part_response_id };
1.323     banghart 1858:         if ($part eq $partid) {
1.375     albertel 1859:             push(@respids,$resp);
1.323     banghart 1860:         }
                   1861:     }
1.318     banghart 1862:     my $result;
1.323     banghart 1863:     foreach my $respid (@respids) {
1.322     albertel 1864: 	my $prefix = $counter.'_'.$partid.'_'.$respid.'_';
                   1865: 	my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
                   1866: 	next if (!@$files);
1.654     raeburn  1867: 	my $file_counter = 0;
1.313     banghart 1868: 	foreach my $file (@$files) {
1.368     banghart 1869: 	    if ($file =~ /\/portfolio\//) {
1.654     raeburn  1870:                 $file_counter++;
1.368     banghart 1871:     	        my ($file_path, $file_disp) = ($file =~ m|(.+/)(.+)$|);
1.729     raeburn  1872:     	        my ($name,$version,$ext) = &Apache::lonnet::file_name_version_ext($file_disp);
1.368     banghart 1873:     	        $file_disp = "$name.$ext";
                   1874:     	        $file = $file_path.$file_disp;
                   1875:     	        $result.=&mt('Return commented version of [_1] to student.',
                   1876:     			 '<span class="LC_filename">'.$file_disp.'</span>');
                   1877:     	        $result.='<input type="file"   name="'.$prefix.'returndoc'.$file_counter.'" />'."\n";
1.654     raeburn  1878:     	        $result.='<input type="hidden" name="'.$prefix.'origdoc'.$file_counter.'" value="'.$file.'" /><br />'."\n";
1.368     banghart 1879: 	    }
1.322     albertel 1880: 	}
1.654     raeburn  1881:         if ($file_counter) {
                   1882:             $result .= '<input type="hidden" name="'.$prefix.'countreturndoc" value="'.$file_counter.'" />'."\n".
                   1883:                        '<span class="LC_info">'.
                   1884:                        '('.&mt('File(s) will be uploaded when you click on Save &amp; Next below.',$file_counter).')</span><br /><br />';
                   1885:         }
1.313     banghart 1886:     }
1.318     banghart 1887:     return $result;    
1.71      ng       1888: }
1.44      ng       1889: 
1.58      albertel 1890: sub show_problem {
1.382     albertel 1891:     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode,$form) = @_;
1.144     albertel 1892:     my $rendered;
1.382     albertel 1893:     my %form = ((ref($form) eq 'HASH')? %{$form} : ());
1.329     albertel 1894:     &Apache::lonxml::remember_problem_counter();
1.144     albertel 1895:     if ($mode eq 'both' or $mode eq 'text') {
                   1896: 	$rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom,
1.382     albertel 1897: 						       $env{'request.course.id'},
                   1898: 						       undef,\%form);
1.144     albertel 1899:     }
1.58      albertel 1900:     if ($removeform) {
                   1901: 	$rendered=~s|<form(.*?)>||g;
                   1902: 	$rendered=~s|</form>||g;
1.374     albertel 1903: 	$rendered=~s|(<input[^>]*name\s*=\s*"?)(\w+)("?)|$1would_have_been_$2$3|g;
1.58      albertel 1904:     }
1.144     albertel 1905:     my $companswer;
                   1906:     if ($mode eq 'both' or $mode eq 'answer') {
1.329     albertel 1907: 	&Apache::lonxml::restore_problem_counter();
1.382     albertel 1908: 	$companswer=
                   1909: 	    &Apache::loncommon::get_student_answers($symb,$uname,$udom,
                   1910: 						    $env{'request.course.id'},
                   1911: 						    %form);
1.144     albertel 1912:     }
1.58      albertel 1913:     if ($removeform) {
                   1914: 	$companswer=~s|<form(.*?)>||g;
                   1915: 	$companswer=~s|</form>||g;
1.144     albertel 1916: 	$companswer=~s|name="submit"|name="would_have_been_submit"|g;
1.58      albertel 1917:     }
1.671     raeburn  1918:     my $renderheading = &mt('View of the problem');
                   1919:     my $answerheading = &mt('Correct answer');
                   1920:     if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
                   1921:         my $stu_fullname = $env{'form.fullname'};
                   1922:         if ($stu_fullname eq '') {
                   1923:             $stu_fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
                   1924:         }
                   1925:         my $forwhom = &nameUserString(undef,$stu_fullname,$uname,$udom);
                   1926:         if ($forwhom ne '') {
                   1927:             $renderheading = &mt('View of the problem for[_1]',$forwhom);
                   1928:             $answerheading = &mt('Correct answer for[_1]',$forwhom);
                   1929:         }
                   1930:     }
1.468     albertel 1931:     $rendered=
1.588     bisitz   1932:         '<div class="LC_Box">'
1.671     raeburn  1933:        .'<h3 class="LC_hcell">'.$renderheading.'</h3>'
1.588     bisitz   1934:        .$rendered
                   1935:        .'</div>';
1.468     albertel 1936:     $companswer=
1.588     bisitz   1937:         '<div class="LC_Box">'
1.671     raeburn  1938:        .'<h3 class="LC_hcell">'.$answerheading.'</h3>'
1.588     bisitz   1939:        .$companswer
                   1940:        .'</div>';
1.468     albertel 1941:     my $result;
1.144     albertel 1942:     if ($mode eq 'both') {
1.588     bisitz   1943:         $result=$rendered.$companswer;
1.144     albertel 1944:     } elsif ($mode eq 'text') {
1.588     bisitz   1945:         $result=$rendered;
1.144     albertel 1946:     } elsif ($mode eq 'answer') {
1.588     bisitz   1947:         $result=$companswer;
1.144     albertel 1948:     }
1.71      ng       1949:     return $result;
1.58      albertel 1950: }
1.397     albertel 1951: 
1.396     banghart 1952: sub files_exist {
                   1953:     my ($r, $symb) = @_;
                   1954:     my @students = &Apache::loncommon::get_env_multiple('form.stuinfo');
1.397     albertel 1955: 
1.396     banghart 1956:     foreach my $student (@students) {
                   1957:         my ($uname,$udom,$fullname) = split(/:/,$student);
1.397     albertel 1958:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   1959: 					      $udom,$uname);
1.396     banghart 1960:         my ($string,$timestamp)= &get_last_submission(\%record);
1.397     albertel 1961:         foreach my $submission (@$string) {
                   1962:             my ($partid,$respid) =
                   1963: 		($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
                   1964:             my $files=&get_submitted_files($udom,$uname,$partid,$respid,
                   1965: 					   \%record);
                   1966:             return 1 if (@$files);
1.396     banghart 1967:         }
                   1968:     }
1.397     albertel 1969:     return 0;
1.396     banghart 1970: }
1.397     albertel 1971: 
1.394     banghart 1972: sub download_all_link {
                   1973:     my ($r,$symb) = @_;
1.621     www      1974:     unless (&files_exist($r, $symb)) {
                   1975:        $r->print(&mt('There are currently no submitted documents.'));
                   1976:        return;
                   1977:     }
                   1978: 
1.395     albertel 1979:     my $all_students = 
                   1980: 	join("\n", &Apache::loncommon::get_env_multiple('form.stuinfo'));
                   1981: 
                   1982:     my $parts =
                   1983: 	join("\n",&Apache::loncommon::get_env_multiple('form.vPart'));
                   1984: 
1.394     banghart 1985:     my $identifier = &Apache::loncommon::get_cgi_id();
1.514     raeburn  1986:     &Apache::lonnet::appenv({'cgi.'.$identifier.'.students' => $all_students,
                   1987:                              'cgi.'.$identifier.'.symb' => $symb,
                   1988:                              'cgi.'.$identifier.'.parts' => $parts,});
1.395     albertel 1989:     $r->print('<a href="/cgi-bin/multidownload.pl?'.$identifier.'">'.
                   1990: 	      &mt('Download All Submitted Documents').'</a>');
1.621     www      1991:     return;
                   1992: }
                   1993: 
                   1994: sub submit_download_link {
                   1995:     my ($request,$symb) = @_;
                   1996:     if (!$symb) { return ''; }
                   1997: #FIXME: Figure out which type of problem this is and provide appropriate download
                   1998:     &download_all_link($request,$symb);
1.394     banghart 1999: }
1.395     albertel 2000: 
1.432     banghart 2001: sub build_section_inputs {
                   2002:     my $section_inputs;
                   2003:     if ($env{'form.section'} eq '') {
                   2004:         $section_inputs .= '<input type="hidden" name="section" value="all" />'."\n";
                   2005:     } else {
                   2006:         my @sections = &Apache::loncommon::get_env_multiple('form.section');
1.434     albertel 2007:         foreach my $section (@sections) {
1.432     banghart 2008:             $section_inputs .= '<input type="hidden" name="section" value="'.$section.'" />'."\n";
                   2009:         }
                   2010:     }
                   2011:     return $section_inputs;
                   2012: }
                   2013: 
1.44      ng       2014: # --------------------------- show submissions of a student, option to grade 
                   2015: sub submission {
1.608     www      2016:     my ($request,$counter,$total,$symb) = @_;
1.257     albertel 2017:     my ($uname,$udom)     = ($env{'form.student'},$env{'form.userdom'});
                   2018:     $udom = ($udom eq '' ? $env{'user.domain'} : $udom); #has form.userdom changed for a student?
                   2019:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
                   2020:     $env{'form.fullname'} = &Apache::loncommon::plainname($uname,$udom,'lastname') if $env{'form.fullname'} eq '';
1.608     www      2021: 
1.605     www      2022:     my $probtitle=&Apache::lonnet::gettitle($symb); 
1.324     albertel 2023:     if ($symb eq '') { $request->print("Unable to handle ambiguous references:."); return ''; }
1.746   ! raeburn  2024:     my $is_tool = ($symb =~ /ext\.tool$/);
1.104     albertel 2025: 
                   2026:     if (!&canview($usec)) {
1.712     bisitz   2027:         $request->print(
                   2028:             '<span class="LC_warning">'.
1.713     bisitz   2029:             &mt('Unable to view requested student.').
1.712     bisitz   2030:             ' '.&mt('([_1] in section [_2] in course id [_3])',
                   2031:                         $uname.':'.$udom,$usec,$env{'request.course.id'}).
                   2032:             '</span>');
1.104     albertel 2033: 	return;
                   2034:     }
                   2035: 
1.257     albertel 2036:     if (!$env{'form.lastSub'}) { $env{'form.lastSub'} = 'datesub'; }
1.745     raeburn  2037:     unless ($is_tool) { 
                   2038:         if (!$env{'form.vProb'}) { $env{'form.vProb'} = 'yes'; }
                   2039:         if (!$env{'form.vAns'}) { $env{'form.vAns'} = 'yes'; }
                   2040:     }
1.257     albertel 2041:     my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
1.381     albertel 2042:     my $checkIcon = '<img alt="'.&mt('Check Mark').
                   2043: 	'" src="'.$request->dir_config('lonIconsURL').
1.122     ng       2044: 	'/check.gif" height="16" border="0" />';
1.41      ng       2045: 
                   2046:     # header info
                   2047:     if ($counter == 0) {
                   2048: 	&sub_page_js($request);
1.621     www      2049: 	&sub_page_kw_js($request);
1.118     ng       2050: 
1.44      ng       2051: 	# option to display problem, only once else it cause problems 
                   2052:         # with the form later since the problem has a form.
1.257     albertel 2053: 	if ($env{'form.vProb'} eq 'yes' or $env{'form.vAns'} eq 'yes') {
1.144     albertel 2054: 	    my $mode;
1.257     albertel 2055: 	    if ($env{'form.vProb'} eq 'yes' && $env{'form.vAns'} eq 'yes') {
1.144     albertel 2056: 		$mode='both';
1.257     albertel 2057: 	    } elsif ($env{'form.vProb'} eq 'yes') {
1.144     albertel 2058: 		$mode='text';
1.257     albertel 2059: 	    } elsif ($env{'form.vAns'} eq 'yes') {
1.144     albertel 2060: 		$mode='answer';
                   2061: 	    }
1.329     albertel 2062: 	    &Apache::lonxml::clear_problem_counter();
1.144     albertel 2063: 	    $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode));
1.41      ng       2064: 	}
1.441     www      2065: 
1.704     raeburn  2066: 	# kwclr is the only variable that is guaranteed not to be blank 
1.44      ng       2067:         # if this subroutine has been called once.
1.41      ng       2068: 	my %keyhash = ();
1.624     www      2069: #	if ($env{'form.kwclr'} eq '' && $env{'form.handgrade'} eq 'yes') {
                   2070:         if (1) {
1.41      ng       2071: 	    %keyhash = &Apache::lonnet::dump('nohist_handgrade',
1.257     albertel 2072: 					     $env{'course.'.$env{'request.course.id'}.'.domain'},
                   2073: 					     $env{'course.'.$env{'request.course.id'}.'.num'});
1.41      ng       2074: 
1.257     albertel 2075: 	    my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
                   2076: 	    $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : '';
                   2077: 	    $env{'form.kwclr'}    = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red';
                   2078: 	    $env{'form.kwsize'}   = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0';
                   2079: 	    $env{'form.kwstyle'}  = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : '';
                   2080: 	    $env{'form.msgsub'}   = $keyhash{$symb.'_subject'} ne '' ? 
1.605     www      2081: 		$keyhash{$symb.'_subject'} : $probtitle;
1.257     albertel 2082: 	    $env{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0';
1.41      ng       2083: 	}
1.257     albertel 2084: 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
1.442     banghart 2085: 	my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.303     banghart 2086: 	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
1.41      ng       2087: 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
1.442     banghart 2088: 			'<input type="hidden" name="Status"     value="'.$stu_status.'" />'."\n".
1.120     ng       2089: 			'<input type="hidden" name="overRideScore" value="'.$overRideScore.'" />'."\n".
1.41      ng       2090: 			'<input type="hidden" name="refresh"    value="off" />'."\n".
1.120     ng       2091: 			'<input type="hidden" name="studentNo"  value="" />'."\n".
                   2092: 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
1.418     albertel 2093: 			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.257     albertel 2094: 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
                   2095: 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
                   2096: 			'<input type="hidden" name="lastSub"    value="'.$env{'form.lastSub'}.'" />'."\n".
1.432     banghart 2097: 			&build_section_inputs().
1.326     albertel 2098: 			'<input type="hidden" name="submitonly" value="'.$env{'form.submitonly'}.'" />'."\n".
1.41      ng       2099: 			'<input type="hidden" name="NCT"'.
1.257     albertel 2100: 			' value="'.($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : $total+1).'" />'."\n");
1.624     www      2101: #	if ($env{'form.handgrade'} eq 'yes') {
                   2102:         if (1) {
1.257     albertel 2103: 	    $request->print('<input type="hidden" name="keywords" value="'.$env{'form.keywords'}.'" />'."\n".
                   2104: 			    '<input type="hidden" name="kwclr"    value="'.$env{'form.kwclr'}.'" />'."\n".
                   2105: 			    '<input type="hidden" name="kwsize"   value="'.$env{'form.kwsize'}.'" />'."\n".
                   2106: 			    '<input type="hidden" name="kwstyle"  value="'.$env{'form.kwstyle'}.'" />'."\n".
                   2107: 			    '<input type="hidden" name="msgsub"   value="'.$env{'form.msgsub'}.'" />'."\n".
1.123     ng       2108: 			    '<input type="hidden" name="shownSub" value="0" />'."\n".
1.257     albertel 2109: 			    '<input type="hidden" name="savemsgN" value="'.$env{'form.savemsgN'}.'" />'."\n");
1.154     albertel 2110: 	    foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
                   2111: 		$request->print('<input type="hidden" name="vPart" value="'.$partid.'" />'."\n");
                   2112: 	    }
1.123     ng       2113: 	}
1.41      ng       2114: 	
                   2115: 	my ($cts,$prnmsg) = (1,'');
1.257     albertel 2116: 	while ($cts <= $env{'form.savemsgN'}) {
1.41      ng       2117: 	    $prnmsg.='<input type="hidden" name="savemsg'.$cts.'" value="'.
1.123     ng       2118: 		(!exists($keyhash{$symb.'_savemsg'.$cts}) ? 
1.257     albertel 2119: 		 &Apache::lonfeedback::clear_out_html($env{'form.savemsg'.$cts}) :
1.80      ng       2120: 		 &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})).
1.123     ng       2121: 		'" />'."\n".
                   2122: 		'<input type="hidden" name="shownOnce'.$cts.'" value="0" />'."\n";
1.41      ng       2123: 	    $cts++;
                   2124: 	}
                   2125: 	$request->print($prnmsg);
1.32      ng       2126: 
1.624     www      2127: #	if ($env{'form.handgrade'} eq 'yes') {
1.745     raeburn  2128:         unless ($is_tool) {
1.652     raeburn  2129: 
                   2130:             my %lt = &Apache::lonlocal::texthash(
1.719     bisitz   2131:                           keyh => 'Keyword Highlighting for Essays',
1.652     raeburn  2132:                           keyw => 'Keyword Options',
1.655     raeburn  2133:                           list => 'List',
1.652     raeburn  2134:                           past => 'Paste Selection to List',
1.661     www      2135:                           high => 'Highlight Attribute',
1.652     raeburn  2136:                      );    
1.88      www      2137: #
                   2138: # Print out the keyword options line
                   2139: #
1.718     bisitz   2140: 	    $request->print(
                   2141:                 '<div class="LC_columnSection">'
                   2142:                .'<fieldset><legend>'.$lt{'keyh'}.'</legend>'
                   2143:                .&Apache::lonhtmlcommon::funclist_from_array(
                   2144:                     ['<a href="javascript:keywords(document.SCORE);" target="_self">'.$lt{'list'}.'</a>',
                   2145:                      '<a href="#" onmousedown="javascript:getSel(); return false"
                   2146:  class="page">'.$lt{'past'}.'</a>',
                   2147:                      '<a href="javascript:kwhighlight();" target="_self">'.$lt{'high'}.'</a>'],
                   2148:                     {legend => $lt{'keyw'}})
                   2149:                .'</fieldset></div>'
                   2150:             );
                   2151: 
1.88      www      2152: #
                   2153: # Load the other essays for similarity check
                   2154: #
1.324     albertel 2155:             my (undef,undef,$essayurl) = &Apache::lonnet::decode_symb($symb);
1.384     albertel 2156: 	    my ($adom,$aname,$apath)=($essayurl=~/^($LONCAPA::domain_re)\/($LONCAPA::username_re)\/(.*)$/);
1.359     www      2157: 	    $apath=&escape($apath);
1.88      www      2158: 	    $apath=~s/\W/\_/gs;
1.674     raeburn  2159:             &init_old_essays($symb,$apath,$adom,$aname);
1.41      ng       2160:         }
                   2161:     }
1.44      ng       2162: 
1.441     www      2163: # This is where output for one specific student would start
1.592     bisitz   2164:     my $add_class = ($counter%2) ? ' LC_grade_show_user_odd_row' : '';
                   2165:     $request->print(
                   2166:         "\n\n"
                   2167:        .'<div class="LC_grade_show_user'.$add_class.'">'
                   2168:        .'<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).'</h2>'
                   2169:        ."\n"
                   2170:     );
1.441     www      2171: 
1.592     bisitz   2172:     # Show additional functions if allowed
                   2173:     if ($perm{'vgr'}) {
                   2174:         $request->print(
                   2175:             &Apache::loncommon::track_student_link(
1.708     bisitz   2176:                 'View recent activity',
1.592     bisitz   2177:                 $uname,$udom,'check')
                   2178:            .' '
                   2179:         );
                   2180:     }
                   2181:     if ($perm{'opa'}) {
                   2182:         $request->print(
                   2183:             &Apache::loncommon::pprmlink(
                   2184:                 &mt('Set/Change parameters'),
                   2185:                 $uname,$udom,$symb,'check'));
                   2186:     }
                   2187: 
                   2188:     # Show Problem
1.257     albertel 2189:     if ($env{'form.vProb'} eq 'all' or $env{'form.vAns'} eq 'all') {
1.144     albertel 2190: 	my $mode;
1.257     albertel 2191: 	if ($env{'form.vProb'} eq 'all' && $env{'form.vAns'} eq 'all') {
1.144     albertel 2192: 	    $mode='both';
1.257     albertel 2193: 	} elsif ($env{'form.vProb'} eq 'all' ) {
1.144     albertel 2194: 	    $mode='text';
1.257     albertel 2195: 	} elsif ($env{'form.vAns'} eq 'all') {
1.144     albertel 2196: 	    $mode='answer';
                   2197: 	}
1.329     albertel 2198: 	&Apache::lonxml::clear_problem_counter();
1.475     albertel 2199: 	$request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,{'request.prefix' => 'ctr'.$counter}));
1.58      albertel 2200:     }
1.144     albertel 2201: 
1.257     albertel 2202:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.582     raeburn  2203:     my $res_error;
                   2204:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   2205:     if ($res_error) {
                   2206:         $request->print(&navmap_errormsg());
                   2207:         return;
                   2208:     }
1.41      ng       2209: 
1.44      ng       2210:     # Display student info
1.41      ng       2211:     $request->print(($counter == 0 ? '' : '<br />'));
1.590     bisitz   2212: 
1.745     raeburn  2213:     my $boxtitle = &mt('Submissions');
                   2214:     if ($is_tool) {
                   2215:         $boxtitle = &mt('Transactions')
                   2216:     }
1.590     bisitz   2217:     my $result='<div class="LC_Box">'
1.745     raeburn  2218:               .'<h3 class="LC_hcell">'.$boxtitle.'</h3>';
1.45      ng       2219:     $result.='<input type="hidden" name="name'.$counter.
1.588     bisitz   2220:              '" value="'.$env{'form.fullname'}.'" />'."\n";
1.624     www      2221: #    if ($env{'form.handgrade'} eq 'no') {
1.745     raeburn  2222:     unless ($is_tool) {
1.588     bisitz   2223:         $result.='<p class="LC_info">'
                   2224:                 .&mt('Part(s) graded correct by the computer is marked with a [_1] symbol.',$checkIcon)
                   2225:                 ."</p>\n";
1.469     albertel 2226:     }
                   2227: 
1.118     ng       2228:     # If any part of the problem is an essay-response (handgraded), then check for collaborators
1.464     albertel 2229:     my $fullname;
                   2230:     my $col_fullnames = [];
1.624     www      2231: #    if ($env{'form.handgrade'} eq 'yes') {
1.745     raeburn  2232:     unless ($is_tool) {
1.464     albertel 2233: 	(my $sub_result,$fullname,$col_fullnames)=
                   2234: 	    &check_collaborators($symb,$uname,$udom,\%record,$handgrade,
                   2235: 				 $counter);
                   2236: 	$result.=$sub_result;
1.41      ng       2237:     }
1.44      ng       2238:     $request->print($result."\n");
1.702     kruse    2239:     
1.44      ng       2240:     # print student answer/submission
1.588     bisitz   2241:     # Options are (1) Handgraded submission only
1.44      ng       2242:     #             (2) Last submission, includes submission that is not handgraded 
                   2243:     #                  (for multi-response type part)
                   2244:     #             (3) Last submission plus the parts info
                   2245:     #             (4) The whole record for this student
1.702     kruse    2246:     
1.745     raeburn  2247:     my ($string,$timestamp)= &get_last_submission(\%record,$is_tool);
1.468     albertel 2248: 	
1.702     kruse    2249:     my $lastsubonly;
1.468     albertel 2250: 
1.702     kruse    2251:     if ($$timestamp eq '') {
                   2252:         $lastsubonly.='<div class="LC_grade_submissions_body">'.$$string[0].'</div>'; 
1.745     raeburn  2253:     } elsif ($is_tool) {
                   2254:         $lastsubonly =
                   2255:             '<div class="LC_grade_submissions_body">'
                   2256:            .'<b>'.&mt('Date Grade Passed Back:').'</b> '.$$timestamp."</div>\n";
1.702     kruse    2257:     } else {
                   2258:         $lastsubonly =
                   2259:             '<div class="LC_grade_submissions_body">'
                   2260:            .'<b>'.&mt('Date Submitted:').'</b> '.$$timestamp."\n";
                   2261: 
                   2262: 	my %seenparts;
                   2263: 	my @part_response_id = &flatten_responseType($responseType);
                   2264: 	foreach my $part (@part_response_id) {
                   2265: 	    next if ($env{'form.lastSub'} eq 'hdgrade' 
1.393     albertel 2266: 			 && $$handgrade{$$part[0].'_'.$$part[1]} ne 'yes');
                   2267: 
1.702     kruse    2268: 	    my ($partid,$respid) = @{ $part };
                   2269: 	    my $display_part=&get_display_part($partid,$symb);
                   2270: 	    if ($env{"form.$uname:$udom:$partid:submitted_by"}) {
                   2271: 		if (exists($seenparts{$partid})) { next; }
                   2272: 		$seenparts{$partid}=1;
                   2273:                 $request->print(
                   2274:                     '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2275:                     ' <b>'.&mt('Collaborative submission by: [_1]',
                   2276:                                '<a href="javascript:viewSubmitter(\''.
                   2277:                                $env{"form.$uname:$udom:$partid:submitted_by"}.
                   2278:                                '\');" target="_self">'.
                   2279:                                $$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a>').
                   2280:                     '<br />');
                   2281: 		next;
                   2282: 		}
                   2283: 	    my $responsetype = $responseType->{$partid}->{$respid};
                   2284: 	    if (!exists($record{"resource.$partid.$respid.submission"})) {
                   2285:                 $lastsubonly.="\n".'<div class="LC_grade_submission_part">'.
                   2286:                     '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2287:                     ' <span class="LC_internal_info">'.
                   2288:                     '('.&mt('Response ID: [_1]',$respid).')'.
                   2289:                     '</span>&nbsp; &nbsp;'.
                   2290: 	       	    '<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br /><br /></div>';
                   2291: 		next;
                   2292: 	    }
                   2293: 	    foreach my $submission (@$string) {
                   2294: 		my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
                   2295: 		if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
1.724     raeburn  2296: 		my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
1.702     kruse    2297: 		# Similarity check
                   2298:                 my $similar='';
                   2299:                 my ($type,$trial,$rndseed);
                   2300:                 if ($hide eq 'rand') {
                   2301:                     $type = 'randomizetry';
                   2302:                     $trial = $record{"resource.$partid.tries"};
1.733     raeburn  2303:                     $rndseed = $record{"resource.$partid.rndseed"};
1.702     kruse    2304:                 }
                   2305: 	        if ($env{'form.checkPlag'}) {
                   2306:     		    my ($oname,$odom,$ocrsid,$oessay,$osim)=
                   2307: 		        &most_similar($uname,$udom,$symb,$subval);
                   2308: 		    if ($osim) {
                   2309: 			$osim=int($osim*100.0);
                   2310: 			my %old_course_desc = 
                   2311: 			    &Apache::lonnet::coursedescription($ocrsid,
                   2312: 							{'one_time' => 1});
                   2313: 
                   2314:                         if ($hide eq 'anon') {
                   2315:                             $similar='<hr /><span class="LC_warning">'.&mt("Essay was found to be similar to another essay submitted for this assignment.").'<br />'.
                   2316:                                      &mt('As the current submission is for an anonymous survey, no other details are available.').'</span><hr />';
                   2317:                         } else {
                   2318: 			    $similar="<hr /><h3><span class=\"LC_warning\">".
                   2319: 				&mt('Essay is [_1]% similar to an essay by [_2] in course [_3] (course id [_4]:[_5])',
                   2320: 				    $osim,
                   2321: 				    &Apache::loncommon::plainname($oname,$odom).' ('.$oname.':'.$odom.')',
1.596     raeburn  2322: 				        $old_course_desc{'description'},
                   2323: 				        $old_course_desc{'num'},
                   2324: 				        $old_course_desc{'domain'}).
                   2325: 				    '</span></h3><blockquote><i>'.
                   2326: 				    &keywords_highlight($oessay).
                   2327: 				    '</i></blockquote><hr />';
1.702     kruse    2328:                         }
                   2329: 	            }
                   2330: 		}
                   2331: 		my $order=&get_order($partid,$respid,$symb,$uname,$udom,
                   2332:                                      undef,$type,$trial,$rndseed);
                   2333:                 if ($env{'form.lastSub'} eq 'lastonly' || $env{'form.lastSub'} eq 'datesub' || $env{'form.lastSub'} =~ /^(last|all)$/ || ($env{'form.lastSub'} eq 'hdgrade' && 
1.377     albertel 2334: 			 $$handgrade{$$part[0].'_'.$$part[1]} eq 'yes')) {
1.702     kruse    2335: 		    my $display_part=&get_display_part($partid,$symb);
                   2336:                     $lastsubonly.='<div class="LC_grade_submission_part">'.
                   2337:                         '<b>'.&mt('Part: [_1]',$display_part).'</b>'.
                   2338:                         ' <span class="LC_internal_info">'.
                   2339:                         '('.&mt('Response ID: [_1]',$respid).')'.
                   2340:                         '</span>&nbsp; &nbsp;';
                   2341: 		    my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
                   2342:                         
                   2343: 		    if (@$files) {
                   2344:                         if ($hide eq 'anon') {
                   2345:                             $lastsubonly.='<br />'.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
                   2346:                         } else {
                   2347:                             $lastsubonly.='<br /><br />'.'<b>'.&mt('Submitted Files:').'</b>'
                   2348:                                         .'<br /><span class="LC_warning">';
                   2349:                             if(@$files == 1) {
                   2350:                                 $lastsubonly .= &mt('Like all files provided by users, this file may contain viruses!');
1.596     raeburn  2351:                             } else {
1.702     kruse    2352:                                 $lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
                   2353:                             }
                   2354:                             $lastsubonly .= '</span>';                         
                   2355:                             foreach my $file (@$files) {
                   2356:                                 &Apache::lonnet::allowuploaded('/adm/grades',$file);
                   2357:                                 $lastsubonly.='<br /><a href="'.$file.'?rawmode=1" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0" alt="" /> '.$file.'</a>';
1.596     raeburn  2358:                             }
                   2359:                         }
1.702     kruse    2360: 			$lastsubonly.='<br />';
                   2361:                     }
                   2362:                     if ($hide eq 'anon') {
                   2363:                         $lastsubonly.='<br /><b>'.&mt('Anonymous Survey').'</b>'; 
                   2364:                     } else {
1.724     raeburn  2365:              	        $lastsubonly.='<br /><b>'.&mt('Submitted Answer:').' </b>';
                   2366:                         if ($draft) {
                   2367:                             $lastsubonly.= ' <span class="LC_warning">'.&mt('Draft Copy').'</span>';
                   2368:                         }
                   2369:                         $subval =
1.702     kruse    2370: 			    &cleanRecord($subval,$responsetype,$symb,$partid,
                   2371: 					 $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
1.724     raeburn  2372:                         if ($responsetype eq 'essay') {
                   2373:                             $subval =~ s{\n}{<br />}g;
                   2374:                         }
                   2375:                         $lastsubonly.=$subval."\n";
1.702     kruse    2376:                     }
                   2377: 	            if ($similar) {$lastsubonly.="<br /><br />$similar\n";}
                   2378: 		    $lastsubonly.='</div>';
1.41      ng       2379: 		}
1.702     kruse    2380:             }
1.151     albertel 2381: 	}
1.702     kruse    2382: 	$lastsubonly.='</div>'."\n"; # End: LC_grade_submissions_body
                   2383:     }
                   2384:     $request->print($lastsubonly);
                   2385:     if ($env{'form.lastSub'} eq 'datesub') {
1.623     www      2386:         my ($parts,$handgrade,$responseType) = &response_type($symb,\$res_error);
1.148     albertel 2387: 	$request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
1.720     kruse    2388:   
1.702     kruse    2389:     } 
                   2390:     if ($env{'form.lastSub'} =~ /^(last|all)$/) {
1.726     raeburn  2391:         my $identifier = (&canmodify($usec)? $counter : '');
1.702     kruse    2392:         $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
1.257     albertel 2393: 								 $env{'request.course.id'},
1.44      ng       2394: 								 $last,'.submission',
1.726     raeburn  2395: 								 'Apache::grades::keywords_highlight',
                   2396:                                                                  $usec,$identifier));
1.41      ng       2397:     }
1.121     ng       2398:     $request->print('<input type="hidden" name="unamedom'.$counter.'" value="'.$uname.':'
                   2399: 	.$udom.'" />'."\n");
1.44      ng       2400:     # return if view submission with no grading option
1.618     www      2401:     if (!&canmodify($usec)) {
1.633     www      2402: 	$request->print('<p><span class="LC_warning">'.&mt('No grading privileges').'</span></p></div>');
1.41      ng       2403: 	return;
1.180     albertel 2404:     } else {
1.468     albertel 2405: 	$request->print('</div>'."\n");
1.41      ng       2406:     }
1.33      ng       2407: 
1.121     ng       2408:     # essay grading message center
1.624     www      2409: #    if ($env{'form.handgrade'} eq 'yes') {
                   2410:     if (1) {
1.468     albertel 2411: 	my $result='<div class="LC_grade_message_center">';
                   2412:     
                   2413: 	$result.='<div class="LC_grade_message_center_header">'.
                   2414: 	    &mt('Send Message').'</div><div class="LC_grade_message_center_body">';
1.257     albertel 2415: 	my ($lastname,$givenn) = split(/,/,$env{'form.fullname'});
1.118     ng       2416: 	my $msgfor = $givenn.' '.$lastname;
1.464     albertel 2417: 	if (scalar(@$col_fullnames) > 0) {
                   2418: 	    my $lastone = pop(@$col_fullnames);
                   2419: 	    $msgfor .= ', '.(join ', ',@$col_fullnames).' and '.$lastone.'.';
1.118     ng       2420: 	}
                   2421: 	$msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript
1.468     albertel 2422: 	$result.='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
1.121     ng       2423: 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
                   2424: 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
1.417     albertel 2425: 	    ',\''.$msgfor.'\');" target="_self">'.
1.695     bisitz   2426: 	    &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).'</a><label> ('.
1.350     albertel 2427: 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
1.695     bisitz   2428: 	    ' <img src="'.$request->dir_config('lonIconsURL').
                   2429: 	    '/mailbkgrd.gif" width="14" height="10" alt="" name="mailicon'.$counter.'" />'."\n".
1.298     www      2430: 	    '<br />&nbsp;('.
1.468     albertel 2431: 	    &mt('Message will be sent when you click on Save &amp; Next below.').")\n";
                   2432: 	$result.='</div></div>';
1.121     ng       2433: 	$request->print($result);
1.118     ng       2434:     }
1.41      ng       2435: 
                   2436:     my %seen = ();
                   2437:     my @partlist;
1.129     ng       2438:     my @gradePartRespid;
1.745     raeburn  2439:     my @part_response_id;
                   2440:     if ($is_tool) {
                   2441:         @part_response_id = ([0,'']);
                   2442:     } else {
                   2443:         @part_response_id = &flatten_responseType($responseType);
                   2444:     }
1.585     bisitz   2445:     $request->print(
1.588     bisitz   2446:         '<div class="LC_Box">'
                   2447:        .'<h3 class="LC_hcell">'.&mt('Assign Grades').'</h3>'
1.585     bisitz   2448:     );
1.592     bisitz   2449:     $request->print(&gradeBox_start());
1.375     albertel 2450:     foreach my $part_response_id (@part_response_id) {
                   2451:     	my ($partid,$respid) = @{ $part_response_id };
                   2452: 	my $part_resp = join('_',@{ $part_response_id });
1.322     albertel 2453: 	next if ($seen{$partid} > 0);
1.41      ng       2454: 	$seen{$partid}++;
1.393     albertel 2455: 	next if ($$handgrade{$part_resp} ne 'yes' 
                   2456: 		 && $env{'form.lastSub'} eq 'hdgrade');
1.524     raeburn  2457: 	push(@partlist,$partid);
                   2458: 	push(@gradePartRespid,$partid.'.'.$respid);
1.322     albertel 2459: 	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
1.41      ng       2460:     }
1.585     bisitz   2461:     $request->print(&gradeBox_end()); # </div>
                   2462:     $request->print('</div>');
1.468     albertel 2463: 
                   2464:     $request->print('<div class="LC_grade_info_links">');
                   2465:     $request->print('</div>');
                   2466: 
1.45      ng       2467:     $result='<input type="hidden" name="partlist'.$counter.
                   2468: 	'" value="'.(join ":",@partlist).'" />'."\n";
1.129     ng       2469:     $result.='<input type="hidden" name="gradePartRespid'.
                   2470: 	'" value="'.(join ":",@gradePartRespid).'" />'."\n" if ($counter == 0);
1.45      ng       2471:     my $ctr = 0;
                   2472:     while ($ctr < scalar(@partlist)) {
                   2473: 	$result.='<input type="hidden" name="partid'.$counter.'_'.$ctr.'" value="'.
                   2474: 	    $partlist[$ctr].'" />'."\n";
                   2475: 	$ctr++;
                   2476:     }
1.468     albertel 2477:     $request->print($result.''."\n");
1.41      ng       2478: 
1.441     www      2479: # Done with printing info for one student
                   2480: 
1.468     albertel 2481:     $request->print('</div>');#LC_grade_show_user
1.441     www      2482: 
                   2483: 
1.41      ng       2484:     # print end of form
                   2485:     if ($counter == $total) {
1.592     bisitz   2486:         my $endform='<br /><hr /><table border="0"><tr><td>'."\n";
1.485     albertel 2487: 	$endform.='<input type="button" value="'.&mt('Save &amp; Next').'" '.
1.589     bisitz   2488: 	    'onclick="javascript:checksubmit(this.form,\'Save & Next\','.
1.417     albertel 2489: 	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
1.119     ng       2490: 	my $ntstu ='<select name="NTSTU">'.
                   2491: 	    '<option>1</option><option>2</option>'.
                   2492: 	    '<option>3</option><option>5</option>'.
                   2493: 	    '<option>7</option><option>10</option></select>'."\n";
1.257     albertel 2494: 	my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
1.401     albertel 2495: 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
1.578     raeburn  2496:         $endform.=&mt('[_1]student(s)',$ntstu);
1.485     albertel 2497: 	$endform.='&nbsp;&nbsp;<input type="button" value="'.&mt('Previous').'" '.
1.589     bisitz   2498: 	    'onclick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
1.485     albertel 2499: 	    '<input type="button" value="'.&mt('Next').'" '.
1.589     bisitz   2500: 	    'onclick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
1.592     bisitz   2501:         $endform.='<span class="LC_warning">'.
                   2502:                   &mt('(Next and Previous (student) do not save the scores.)').
                   2503:                   '</span>'."\n" ;
1.349     albertel 2504:         $endform.="<input type='hidden' value='".&get_increment().
1.348     bowersj2 2505:             "' name='increment' />";
1.485     albertel 2506: 	$endform.='</td></tr></table></form>';
1.41      ng       2507: 	$request->print($endform);
                   2508:     }
                   2509:     return '';
1.38      ng       2510: }
                   2511: 
1.464     albertel 2512: sub check_collaborators {
                   2513:     my ($symb,$uname,$udom,$record,$handgrade,$counter) = @_;
                   2514:     my ($result,@col_fullnames);
                   2515:     my ($classlist,undef,$fullname) = &getclasslist('all','0');
                   2516:     foreach my $part (keys(%$handgrade)) {
                   2517: 	my $ncol = &Apache::lonnet::EXT('resource.'.$part.
                   2518: 					'.maxcollaborators',
                   2519: 					$symb,$udom,$uname);
                   2520: 	next if ($ncol <= 0);
                   2521: 	$part =~ s/\_/\./g;
                   2522: 	next if ($record->{'resource.'.$part.'.collaborators'} eq '');
                   2523: 	my (@good_collaborators, @bad_collaborators);
                   2524: 	foreach my $possible_collaborator
1.630     www      2525: 	    (split(/[,;\s]+/,$record->{'resource.'.$part.'.collaborators'})) { 
1.464     albertel 2526: 	    $possible_collaborator =~ s/[\$\^\(\)]//g;
                   2527: 	    next if ($possible_collaborator eq '');
1.631     www      2528: 	    my ($co_name,$co_dom) = split(/:/,$possible_collaborator);
1.464     albertel 2529: 	    $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i);
                   2530: 	    next if ($co_name eq $uname && $co_dom eq $udom);
                   2531: 	    # Doing this grep allows 'fuzzy' specification
                   2532: 	    my @matches = grep(/^\Q$co_name\E:\Q$co_dom\E$/i, 
                   2533: 			       keys(%$classlist));
                   2534: 	    if (! scalar(@matches)) {
                   2535: 		push(@bad_collaborators, $possible_collaborator);
                   2536: 	    } else {
                   2537: 		push(@good_collaborators, @matches);
                   2538: 	    }
                   2539: 	}
                   2540: 	if (scalar(@good_collaborators) != 0) {
1.630     www      2541: 	    $result.='<br />'.&mt('Collaborators:').'<ol>';
1.464     albertel 2542: 	    foreach my $name (@good_collaborators) {
                   2543: 		my ($lastname,$givenn) = split(/,/,$$fullname{$name});
                   2544: 		push(@col_fullnames, $givenn.' '.$lastname);
1.630     www      2545: 		$result.='<li>'.$fullname->{$name}.'</li>';
1.464     albertel 2546: 	    }
1.630     www      2547: 	    $result.='</ol><br />'."\n";
1.466     albertel 2548: 	    my ($part)=split(/\./,$part);
1.464     albertel 2549: 	    $result.='<input type="hidden" name="collaborator'.$counter.
                   2550: 		'" value="'.$part.':'.(join ':',@good_collaborators).'" />'.
                   2551: 		"\n";
                   2552: 	}
                   2553: 	if (scalar(@bad_collaborators) > 0) {
1.466     albertel 2554: 	    $result.='<div class="LC_warning">';
1.464     albertel 2555: 	    $result.=&mt('This student has submitted [quant,_1,invalid collaborator]: [_2]',scalar(@bad_collaborators),join(', ',@bad_collaborators));
                   2556: 	    $result .= '</div>';
                   2557: 	}         
                   2558: 	if (scalar(@bad_collaborators > $ncol)) {
1.466     albertel 2559: 	    $result .= '<div class="LC_warning">';
1.464     albertel 2560: 	    $result .= &mt('This student has submitted too many '.
                   2561: 		'collaborators.  Maximum is [_1].',$ncol);
                   2562: 	    $result .= '</div>';
                   2563: 	}
                   2564:     }
                   2565:     return ($result,$fullname,\@col_fullnames);
                   2566: }
                   2567: 
1.44      ng       2568: #--- Retrieve the last submission for all the parts
1.38      ng       2569: sub get_last_submission {
1.745     raeburn  2570:     my ($returnhash,$is_tool)=@_;
1.596     raeburn  2571:     my (@string,$timestamp,%lasthidden);
1.119     ng       2572:     if ($$returnhash{'version'}) {
1.46      ng       2573: 	my %lasthash=();
                   2574: 	my ($version);
1.119     ng       2575: 	for ($version=1;$version<=$$returnhash{'version'};$version++) {
1.397     albertel 2576: 	    foreach my $key (sort(split(/\:/,
                   2577: 					$$returnhash{$version.':keys'}))) {
                   2578: 		$lasthash{$key}=$$returnhash{$version.':'.$key};
                   2579: 		$timestamp = 
1.545     raeburn  2580: 		    &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
1.46      ng       2581: 	    }
                   2582: 	}
1.640     raeburn  2583:         my (%typeparts,%randombytry);
1.596     raeburn  2584:         my $showsurv = 
                   2585:             &Apache::lonnet::allowed('vas',$env{'request.course.id'});
                   2586:         foreach my $key (sort(keys(%lasthash))) {
                   2587:             if ($key =~ /\.type$/) {
                   2588:                 if (($lasthash{$key} eq 'anonsurvey') || 
1.640     raeburn  2589:                     ($lasthash{$key} eq 'anonsurveycred') ||
                   2590:                     ($lasthash{$key} eq 'randomizetry')) {
1.596     raeburn  2591:                     my ($ign,@parts) = split(/\./,$key);
                   2592:                     pop(@parts);
1.641     raeburn  2593:                     my $id = join('.',@parts);
1.640     raeburn  2594:                     if ($lasthash{$key} eq 'randomizetry') {
                   2595:                         $randombytry{$ign.'.'.$id} = $lasthash{$key};
                   2596:                     } else {
                   2597:                         unless ($showsurv) {
                   2598:                             $typeparts{$ign.'.'.$id} = $lasthash{$key};
                   2599:                         }
1.596     raeburn  2600:                     }
                   2601:                     delete($lasthash{$key});
                   2602:                 }
                   2603:             }
                   2604:         }
                   2605:         my @hidden = keys(%typeparts);
1.640     raeburn  2606:         my @randomize = keys(%randombytry);
1.397     albertel 2607: 	foreach my $key (keys(%lasthash)) {
                   2608: 	    next if ($key !~ /\.submission$/);
1.596     raeburn  2609:             my $hide;
                   2610:             if (@hidden) {
                   2611:                 foreach my $id (@hidden) {
                   2612:                     if ($key =~ /^\Q$id\E/) {
1.640     raeburn  2613:                         $hide = 'anon';
1.596     raeburn  2614:                         last;
                   2615:                     }
                   2616:                 }
                   2617:             }
1.640     raeburn  2618:             unless ($hide) {
                   2619:                 if (@randomize) {
1.732     raeburn  2620:                     foreach my $id (@randomize) {
1.640     raeburn  2621:                         if ($key =~ /^\Q$id\E/) {
                   2622:                             $hide = 'rand';
                   2623:                             last;
                   2624:                         }
                   2625:                     }
                   2626:                 }
                   2627:             }
1.397     albertel 2628: 	    my ($partid,$foo) = split(/submission$/,$key);
1.724     raeburn  2629: 	    my $draft  = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? 1 : 0;
                   2630:             push(@string, join(':', $key, $hide, $draft, (
1.716     bisitz   2631:                 ref($lasthash{$key}) eq 'ARRAY' ?
                   2632:                     join(',', @{$lasthash{$key}}) : $lasthash{$key}) ));
1.41      ng       2633: 	}
                   2634:     }
1.397     albertel 2635:     if (!@string) {
1.745     raeburn  2636:         my $msg;
                   2637:         if ($is_tool) {
                   2638:             $msg = &mt('Nothing passed back - no attempts.');
                   2639:         } else {
                   2640:             $msg = &mt('Nothing submitted - no attempts.');
                   2641:         }
1.397     albertel 2642: 	$string[0] =
1.745     raeburn  2643: 	    '<span class="LC_warning">'.$msg.'</span>';
1.397     albertel 2644:     }
                   2645:     return (\@string,\$timestamp);
1.38      ng       2646: }
1.35      ng       2647: 
1.44      ng       2648: #--- High light keywords, with style choosen by user.
1.38      ng       2649: sub keywords_highlight {
1.44      ng       2650:     my $string    = shift;
1.257     albertel 2651:     my $size      = $env{'form.kwsize'} eq '0' ? '' : 'size='.$env{'form.kwsize'};
                   2652:     my $styleon   = $env{'form.kwstyle'} eq ''  ? '' : $env{'form.kwstyle'};
1.41      ng       2653:     (my $styleoff = $styleon) =~ s/\</\<\//;
1.257     albertel 2654:     my @keylist   = split(/[,\s+]/,$env{'form.keywords'});
1.398     albertel 2655:     foreach my $keyword (@keylist) {
                   2656: 	$string =~ s/\b\Q$keyword\E(\b|\.)/<font color\=$env{'form.kwclr'} $size\>$styleon$keyword$styleoff<\/font>/gi;
1.41      ng       2657:     }
                   2658:     return $string;
1.38      ng       2659: }
1.36      ng       2660: 
1.671     raeburn  2661: # For Tasks provide a mechanism to display previous version for one specific student
                   2662: 
                   2663: sub show_previous_task_version {
                   2664:     my ($request,$symb) = @_;
                   2665:     if ($symb eq '') {
1.717     bisitz   2666:         $request->print(
                   2667:             '<span class="LC_error">'.
                   2668:             &mt('Unable to handle ambiguous references.').
                   2669:             '</span>');
1.671     raeburn  2670:         return '';
                   2671:     }
                   2672:     my ($uname,$udom) = ($env{'form.student'},$env{'form.userdom'});
                   2673:     my $usec = &Apache::lonnet::getsection($udom,$uname,$env{'request.course.id'});
                   2674:     if (!&canview($usec)) {
1.712     bisitz   2675:         $request->print(
                   2676:             '<span class="LC_warning">'.
1.713     bisitz   2677:             &mt('Unable to view previous version for requested student.').
1.712     bisitz   2678:             ' '.&mt('([_1] in section [_2] in course id [_3])',
                   2679:                     $uname.':'.$udom,$usec,$env{'request.course.id'}).
                   2680:             '</span>');
1.671     raeburn  2681:         return;
                   2682:     }
                   2683:     my $mode = 'both';
                   2684:     my $isTask = ($symb =~/\.task$/);
                   2685:     if ($isTask) {
                   2686:         if ($env{'form.previousversion'} =~ /^\d+$/) {
                   2687:             if ($env{'form.fullname'} eq '') {
                   2688:                 $env{'form.fullname'} =
                   2689:                     &Apache::loncommon::plainname($uname,$udom,'lastname');
                   2690:             }
                   2691:             my $probtitle=&Apache::lonnet::gettitle($symb);
                   2692:             $request->print("\n\n".
                   2693:                             '<div class="LC_grade_show_user">'.
                   2694:                             '<h2>'.&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
                   2695:                             '</h2>'."\n");
                   2696:             &Apache::lonxml::clear_problem_counter();
                   2697:             $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode,
                   2698:                             {'previousversion' => $env{'form.previousversion'} }));
                   2699:             $request->print("\n</div>");
                   2700:         }
                   2701:     }
                   2702:     return;
                   2703: }
                   2704: 
                   2705: sub choose_task_version_form {
                   2706:     my ($symb,$uname,$udom,$nomenu) = @_;
                   2707:     my $isTask = ($symb =~/\.task$/);
                   2708:     my ($current,$version,$result,$js,$displayed,$rowtitle);
                   2709:     if ($isTask) {
                   2710:         my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   2711:                                               $udom,$uname);
                   2712:         if (($record{'resource.0.version'} eq '') ||
                   2713:             ($record{'resource.0.version'} < 2)) {
                   2714:             return ($record{'resource.0.version'},
                   2715:                     $record{'resource.0.version'},$result,$js);
                   2716:         } else {
                   2717:             $current = $record{'resource.0.version'};
                   2718:         }
                   2719:         if ($env{'form.previousversion'}) {
                   2720:             $displayed = $env{'form.previousversion'};
                   2721:             $rowtitle = &mt('Choose another version:')
                   2722:         } else {
                   2723:             $displayed = $current;
                   2724:             $rowtitle = &mt('Show earlier version:');
                   2725:         }
                   2726:         $result = '<div class="LC_left_float">';
                   2727:         my $list;
                   2728:         my $numversions = 0;
                   2729:         for (my $i=1; $i<=$record{'resource.0.version'}; $i++) {
                   2730:             if ($i == $current) {
                   2731:                 if (!$env{'form.previousversion'} || $nomenu) {
                   2732:                     next;
                   2733:                 } else {
                   2734:                     $list .= '<option value="'.$i.'">'.&mt('Current').'</option>'."\n";
                   2735:                     $numversions ++;
                   2736:                 }
                   2737:             } elsif (defined($record{'resource.'.$i.'.0.status'})) {
                   2738:                 unless ($i == $env{'form.previousversion'}) {
                   2739:                     $numversions ++;
                   2740:                 }
                   2741:                 $list .= '<option value="'.$i.'">'.$i.'</option>'."\n";
                   2742:             }
                   2743:         }
                   2744:         if ($numversions) {
                   2745:             $symb = &HTML::Entities::encode($symb,'<>"&');
                   2746:             $result .=
                   2747:                 '<form name="getprev" method="post" action=""'.
                   2748:                 ' onsubmit="return previousVersion('."'$uname','$udom','$symb','$displayed'".');">'.
                   2749:                 &Apache::loncommon::start_data_table().
                   2750:                 &Apache::loncommon::start_data_table_row().
                   2751:                 '<th align="left">'.$rowtitle.'</th>'.
                   2752:                 '<td><select name="version">'.
                   2753:                 '<option>'.&mt('Select').'</option>'.
                   2754:                 $list.
                   2755:                 '</select></td>'.
                   2756:                 &Apache::loncommon::end_data_table_row();
                   2757:             unless ($nomenu) {
                   2758:                 $result .= &Apache::loncommon::start_data_table_row().
                   2759:                 '<th align="left">'.&mt('Open in new window').'</th>'.
                   2760:                 '<td><span class="LC_nobreak">'.
                   2761:                 '<label><input type="radio" name="prevwin" value="1" />'.
                   2762:                 &mt('Yes').'</label>'.
                   2763:                 '<label><input type="radio" name="prevwin" value="0" checked="checked" />'.&mt('No').'</label>'.
                   2764:                 '</span></td>'.
                   2765:                 &Apache::loncommon::end_data_table_row();
                   2766:             }
                   2767:             $result .=
                   2768:                 &Apache::loncommon::start_data_table_row().
                   2769:                 '<th align="left">&nbsp;</th>'.
                   2770:                 '<td>'.
                   2771:                 '<input type="submit" name="prevsub" value="'.&mt('Display').'" />'.
                   2772:                 '</td>'.
                   2773:                 &Apache::loncommon::end_data_table_row().
                   2774:                 &Apache::loncommon::end_data_table().
                   2775:                 '</form>';
                   2776:             $js = &previous_display_javascript($nomenu,$current);
                   2777:         } elsif ($displayed && $nomenu) {
                   2778:             $result .= '<a href="javascript:window.close()">'.&mt('Close window').'</a>';
                   2779:         } else {
                   2780:             $result .= &mt('No previous versions to show for this student');
                   2781:         }
                   2782:         $result .= '</div>';
                   2783:     }
                   2784:     return ($current,$displayed,$result,$js);
                   2785: }
                   2786: 
                   2787: sub previous_display_javascript {
                   2788:     my ($nomenu,$current) = @_;
                   2789:     my $js = <<"JSONE";
                   2790: <script type="text/javascript">
                   2791: // <![CDATA[
                   2792: function previousVersion(uname,udom,symb) {
                   2793:     var current = '$current';
                   2794:     var version = document.getprev.version.options[document.getprev.version.selectedIndex].value;
                   2795:     var prevstr = new RegExp("^\\\\d+\$");
                   2796:     if (!prevstr.test(version)) {
                   2797:         return false;
                   2798:     }
                   2799:     var url = '';
                   2800:     if (version == current) {
                   2801:         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=submission';
                   2802:     } else {
                   2803:         url = '/adm/grades?student='+uname+'&userdom='+udom+'&symb='+symb+'&command=versionsub&previousversion='+version;
                   2804:     }
                   2805: JSONE
                   2806:     if ($nomenu) {
                   2807:         $js .= <<"JSTWO";
                   2808:     document.location.href = url;
                   2809: JSTWO
                   2810:     } else {
                   2811:         $js .= <<"JSTHREE";
                   2812:     var newwin = 0;
                   2813:     for (var i=0; i<document.getprev.prevwin.length; i++) {
                   2814:         if (document.getprev.prevwin[i].checked == true) {
                   2815:             newwin = document.getprev.prevwin[i].value;
                   2816:         }
                   2817:     }
                   2818:     if (newwin == 1) {
                   2819:         var options = 'height=600,width=800,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no';
                   2820:         url = url+'&inhibitmenu=yes';
                   2821:         if (typeof(previousWin) == 'undefined' || previousWin.closed) {
                   2822:             previousWin = window.open(url,'',options,1);
                   2823:         } else {
                   2824:             previousWin.location.href = url;
                   2825:         }
                   2826:         previousWin.focus();
                   2827:         return false;
                   2828:     } else {
                   2829:         document.location.href = url;
                   2830:         return false;
                   2831:     }
                   2832: JSTHREE
                   2833:     }
                   2834:     $js .= <<"ENDJS";
                   2835:     return false;
                   2836: }
                   2837: // ]]>
                   2838: </script>
                   2839: ENDJS
                   2840: 
                   2841: }
                   2842: 
1.44      ng       2843: #--- Called from submission routine
1.38      ng       2844: sub processHandGrade {
1.608     www      2845:     my ($request,$symb) = @_;
1.324     albertel 2846:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.257     albertel 2847:     my $button = $env{'form.gradeOpt'};
                   2848:     my $ngrade = $env{'form.NCT'};
                   2849:     my $ntstu  = $env{'form.NTSTU'};
1.301     albertel 2850:     my $cdom   = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   2851:     my $cnum   = $env{'course.'.$env{'request.course.id'}.'.num'};
                   2852: 
1.44      ng       2853:     if ($button eq 'Save & Next') {
                   2854: 	my $ctr = 0;
                   2855: 	while ($ctr < $ngrade) {
1.257     albertel 2856: 	    my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
1.726     raeburn  2857: 	    my ($errorflag,$pts,$wgt,$numhidden) = 
                   2858:                 &saveHandGrade($request,$symb,$uname,$udom,$ctr);
1.71      ng       2859: 	    if ($errorflag eq 'no_score') {
                   2860: 		$ctr++;
                   2861: 		next;
                   2862: 	    }
1.104     albertel 2863: 	    if ($errorflag eq 'not_allowed') {
1.721     bisitz   2864: 		$request->print(
                   2865:                     '<span class="LC_error">'
                   2866:                    .&mt('Not allowed to modify grades for [_1]',"$uname:$udom")
                   2867:                    .'</span>');
1.104     albertel 2868: 		$ctr++;
                   2869: 		next;
                   2870: 	    }
1.726     raeburn  2871:             if ($numhidden) {
                   2872:                 $request->print(
                   2873:                     '<span class="LC_info">'
                   2874:                    .&mt('For [_1]: [quant,_2,transaction] hidden',"$uname:$udom",$numhidden)
                   2875:                    .'</span><br />');
                   2876:             }
1.257     albertel 2877: 	    my $includemsg = $env{'form.includemsg'.$ctr};
1.44      ng       2878: 	    my ($subject,$message,$msgstatus) = ('','','');
1.418     albertel 2879: 	    my $restitle = &Apache::lonnet::gettitle($symb);
                   2880:             my ($feedurl,$showsymb) =
                   2881: 		&get_feedurl_and_symb($symb,$uname,$udom);
                   2882: 	    my $messagetail;
1.62      albertel 2883: 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
1.298     www      2884: 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
1.295     www      2885: 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
1.386     raeburn  2886: 		$subject.=' ['.$restitle.']';
1.44      ng       2887: 		my (@msgnum) = split(/,/,$includemsg);
                   2888: 		foreach (@msgnum) {
1.257     albertel 2889: 		    $message.=$env{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne '');
1.44      ng       2890: 		}
1.80      ng       2891: 		$message =&Apache::lonfeedback::clear_out_html($message);
1.298     www      2892: 		if ($env{'form.withgrades'.$ctr}) {
                   2893: 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
1.386     raeburn  2894: 		    $messagetail = " for <a href=\"".
1.605     www      2895: 		                   $feedurl."?symb=$showsymb\">$restitle</a>";
1.386     raeburn  2896: 		}
                   2897: 		$msgstatus = 
                   2898:                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
                   2899: 						     $message.$messagetail,
1.418     albertel 2900:                                                      undef,$feedurl,undef,
1.386     raeburn  2901:                                                      undef,undef,$showsymb,
                   2902:                                                      $restitle);
1.574     bisitz   2903: 		$request->print('<br />'.&mt('Sending message to [_1]',$uname.':'.$udom).': '.
1.652     raeburn  2904: 				$msgstatus.'<br />');
1.44      ng       2905: 	    }
1.257     albertel 2906: 	    if ($env{'form.collaborator'.$ctr}) {
1.155     albertel 2907: 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
1.150     albertel 2908: 		foreach my $collabstr (@collabstrs) {
                   2909: 		    my ($part,@collaborators) = split(/:/,$collabstr);
1.310     banghart 2910: 		    foreach my $collaborator (@collaborators) {
1.150     albertel 2911: 			my ($errorflag,$pts,$wgt) = 
1.324     albertel 2912: 			    &saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
1.257     albertel 2913: 					   $env{'form.unamedom'.$ctr},$part);
1.150     albertel 2914: 			if ($errorflag eq 'not_allowed') {
1.362     albertel 2915: 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
1.150     albertel 2916: 			    next;
1.418     albertel 2917: 			} elsif ($message ne '') {
                   2918: 			    my ($baseurl,$showsymb) = 
                   2919: 				&get_feedurl_and_symb($symb,$collaborator,
                   2920: 						      $udom);
                   2921: 			    if ($env{'form.withgrades'.$ctr}) {
                   2922: 				$messagetail = " for <a href=\"".
1.605     www      2923:                                     $baseurl."?symb=$showsymb\">$restitle</a>";
1.150     albertel 2924: 			    }
1.418     albertel 2925: 			    $msgstatus = 
                   2926: 				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
1.104     albertel 2927: 			}
1.44      ng       2928: 		    }
                   2929: 		}
                   2930: 	    }
                   2931: 	    $ctr++;
                   2932: 	}
                   2933:     }
                   2934: 
1.624     www      2935: #    if ($env{'form.handgrade'} eq 'yes') {
                   2936:     if (1) {
1.119     ng       2937: 	# Keywords sorted in alphabatical order
1.257     albertel 2938: 	my $loginuser = $env{'user.name'}.':'.$env{'user.domain'};
1.119     ng       2939: 	my %keyhash = ();
1.257     albertel 2940: 	$env{'form.keywords'}           =~ s/,\s{0,}|\s+/ /g;
                   2941: 	$env{'form.keywords'}           =~ s/^\s+|\s+$//;
                   2942: 	my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
                   2943: 	$env{'form.keywords'} = join(' ',@keywords);
                   2944: 	$keyhash{$symb.'_keywords'}     = $env{'form.keywords'};
                   2945: 	$keyhash{$symb.'_subject'}      = $env{'form.msgsub'};
                   2946: 	$keyhash{$loginuser.'_kwclr'}   = $env{'form.kwclr'};
                   2947: 	$keyhash{$loginuser.'_kwsize'}  = $env{'form.kwsize'};
                   2948: 	$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
1.119     ng       2949: 
                   2950: 	# message center - Order of message gets changed. Blank line is eliminated.
1.257     albertel 2951: 	# New messages are saved in env for the next student.
1.119     ng       2952: 	# All messages are saved in nohist_handgrade.db
                   2953: 	my ($ctr,$idx) = (1,1);
1.257     albertel 2954: 	while ($ctr <= $env{'form.savemsgN'}) {
                   2955: 	    if ($env{'form.savemsg'.$ctr} ne '') {
                   2956: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.savemsg'.$ctr};
1.119     ng       2957: 		$idx++;
                   2958: 	    }
                   2959: 	    $ctr++;
1.41      ng       2960: 	}
1.119     ng       2961: 	$ctr = 0;
                   2962: 	while ($ctr < $ngrade) {
1.257     albertel 2963: 	    if ($env{'form.newmsg'.$ctr} ne '') {
                   2964: 		$keyhash{$symb.'_savemsg'.$idx} = $env{'form.newmsg'.$ctr};
                   2965: 		$env{'form.savemsg'.$idx} = $env{'form.newmsg'.$ctr};
1.119     ng       2966: 		$idx++;
                   2967: 	    }
                   2968: 	    $ctr++;
1.41      ng       2969: 	}
1.257     albertel 2970: 	$env{'form.savemsgN'} = --$idx;
                   2971: 	$keyhash{$symb.'_savemsgN'} = $env{'form.savemsgN'};
1.119     ng       2972: 	my $putresult = &Apache::lonnet::put
1.301     albertel 2973: 	    ('nohist_handgrade',\%keyhash,$cdom,$cnum);
1.41      ng       2974:     }
1.44      ng       2975:     # Called by Save & Refresh from Highlight Attribute Window
1.257     albertel 2976:     my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
                   2977:     if ($env{'form.refresh'} eq 'on') {
1.86      ng       2978: 	my ($ctr,$total) = (0,0);
                   2979: 	while ($ctr < $ngrade) {
1.257     albertel 2980: 	    $total++ if  $env{'form.unamedom'.$ctr} ne '';
1.86      ng       2981: 	    $ctr++;
                   2982: 	}
1.257     albertel 2983: 	$env{'form.NTSTU'}=$ngrade;
1.86      ng       2984: 	$ctr = 0;
                   2985: 	while ($ctr < $total) {
1.257     albertel 2986: 	    my $processUser = $env{'form.unamedom'.$ctr};
                   2987: 	    ($env{'form.student'},$env{'form.userdom'}) = split(/:/,$processUser);
                   2988: 	    $env{'form.fullname'} = $$fullname{$processUser};
1.625     www      2989: 	    &submission($request,$ctr,$total-1,$symb);
1.41      ng       2990: 	    $ctr++;
                   2991: 	}
                   2992: 	return '';
                   2993:     }
1.36      ng       2994: 
1.44      ng       2995:     # Get the next/previous one or group of students
1.257     albertel 2996:     my $firststu = $env{'form.unamedom0'};
                   2997:     my $laststu = $env{'form.unamedom'.($ngrade-1)};
1.119     ng       2998:     my $ctr = 2;
1.41      ng       2999:     while ($laststu eq '') {
1.257     albertel 3000: 	$laststu  = $env{'form.unamedom'.($ngrade-$ctr)};
1.41      ng       3001: 	$ctr++;
                   3002: 	$laststu = $firststu if ($ctr > $ngrade);
                   3003:     }
1.44      ng       3004: 
1.41      ng       3005:     my (@parsedlist,@nextlist);
                   3006:     my ($nextflg) = 0;
1.524     raeburn  3007:     foreach my $item (sort 
1.294     albertel 3008: 	     {
                   3009: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   3010: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   3011: 		 }
                   3012: 		 return $a cmp $b;
                   3013: 	     } (keys(%$fullname))) {
1.605     www      3014: # FIXME: this is fishy, looks like the button label
1.41      ng       3015: 	if ($nextflg == 1 && $button =~ /Next$/) {
1.524     raeburn  3016: 	    push(@parsedlist,$item);
1.41      ng       3017: 	}
1.524     raeburn  3018: 	$nextflg = 1 if ($item eq $laststu);
1.41      ng       3019: 	if ($button eq 'Previous') {
1.524     raeburn  3020: 	    last if ($item eq $firststu);
                   3021: 	    push(@parsedlist,$item);
1.41      ng       3022: 	}
                   3023:     }
                   3024:     $ctr = 0;
1.605     www      3025: # FIXME: this is fishy, looks like the button label
1.41      ng       3026:     @parsedlist = reverse @parsedlist if ($button eq 'Previous');
1.582     raeburn  3027:     my $res_error;
                   3028:     my ($partlist) = &response_type($symb,\$res_error);
                   3029:     if ($res_error) {
                   3030:         $request->print(&navmap_errormsg());
                   3031:         return;
                   3032:     }
1.41      ng       3033:     foreach my $student (@parsedlist) {
1.257     albertel 3034: 	my $submitonly=$env{'form.submitonly'};
1.41      ng       3035: 	my ($uname,$udom) = split(/:/,$student);
1.301     albertel 3036: 	
                   3037: 	if ($submitonly eq 'queued') {
                   3038: 	    my %queue_status = 
                   3039: 		&Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   3040: 							$udom,$uname);
                   3041: 	    next if (!defined($queue_status{'gradingqueue'}));
                   3042: 	}
                   3043: 
1.156     albertel 3044: 	if ($submitonly =~ /^(yes|graded|incorrect)$/) {
1.257     albertel 3045: #	    my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
1.324     albertel 3046: 	    my %status=&student_gradeStatus($symb,$udom,$uname,$partlist);
1.145     albertel 3047: 	    my $submitted = 0;
1.248     albertel 3048: 	    my $ungraded = 0;
                   3049: 	    my $incorrect = 0;
1.524     raeburn  3050: 	    foreach my $item (keys(%status)) {
                   3051: 		$submitted = 1 if ($status{$item} ne 'nothing');
                   3052: 		$ungraded = 1 if ($status{$item} =~ /^ungraded/);
                   3053: 		$incorrect = 1 if ($status{$item} =~ /^incorrect/);
                   3054: 		my ($foo,$partid,$foo1) = split(/\./,$item);
1.145     albertel 3055: 		if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
                   3056: 		    $submitted = 0;
                   3057: 		}
1.41      ng       3058: 	    }
1.156     albertel 3059: 	    next if (!$submitted && ($submitonly eq 'yes' ||
                   3060: 				     $submitonly eq 'incorrect' ||
                   3061: 				     $submitonly eq 'graded'));
1.248     albertel 3062: 	    next if (!$ungraded && ($submitonly eq 'graded'));
                   3063: 	    next if (!$incorrect && $submitonly eq 'incorrect');
1.41      ng       3064: 	}
1.524     raeburn  3065: 	push(@nextlist,$student) if ($ctr < $ntstu);
1.129     ng       3066: 	last if ($ctr == $ntstu);
1.41      ng       3067: 	$ctr++;
                   3068:     }
1.36      ng       3069: 
1.41      ng       3070:     $ctr = 0;
                   3071:     my $total = scalar(@nextlist)-1;
1.39      ng       3072: 
1.524     raeburn  3073:     foreach (sort(@nextlist)) {
1.41      ng       3074: 	my ($uname,$udom,$submitter) = split(/:/);
1.257     albertel 3075: 	$env{'form.student'}  = $uname;
                   3076: 	$env{'form.userdom'}  = $udom;
                   3077: 	$env{'form.fullname'} = $$fullname{$_};
1.625     www      3078: 	&submission($request,$ctr,$total,$symb);
1.41      ng       3079: 	$ctr++;
                   3080:     }
                   3081:     if ($total < 0) {
1.653     raeburn  3082: 	my $the_end.='<p>'.&mt('[_1]Message:[_2] No more students for this section or class.','<b>','</b>').'</p>'."\n";
1.41      ng       3083: 	$request->print($the_end);
                   3084:     }
                   3085:     return '';
1.38      ng       3086: }
1.36      ng       3087: 
1.44      ng       3088: #---- Save the score and award for each student, if changed
1.38      ng       3089: sub saveHandGrade {
1.324     albertel 3090:     my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
1.342     banghart 3091:     my @version_parts;
1.104     albertel 3092:     my $usec = &Apache::lonnet::getsection($domain,$stuname,
1.257     albertel 3093: 					   $env{'request.course.id'});
1.104     albertel 3094:     if (!&canmodify($usec)) { return('not_allowed'); }
1.337     banghart 3095:     my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
1.251     banghart 3096:     my @parts_graded;
1.77      ng       3097:     my %newrecord  = ();
1.726     raeburn  3098:     my ($pts,$wgt,$totchg) = ('','',0);
1.269     raeburn  3099:     my %aggregate = ();
                   3100:     my $aggregateflag = 0;
1.726     raeburn  3101:     if ($env{'form.HIDE'.$newflg}) {
1.727     raeburn  3102:         my ($version,$parts) = split(/:/,$env{'form.HIDE'.$newflg},2);
1.728     raeburn  3103:         my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
1.726     raeburn  3104:         $totchg += $numchgs;
                   3105:     }
1.301     albertel 3106:     my @parts = split(/:/,$env{'form.partlist'.$newflg});
                   3107:     foreach my $new_part (@parts) {
1.337     banghart 3108: 	#collaborator ($submi may vary for different parts
1.259     banghart 3109: 	if ($submitter && $new_part ne $part) { next; }
                   3110: 	my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
1.125     ng       3111: 	if ($dropMenu eq 'excused') {
1.259     banghart 3112: 	    if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
                   3113: 		$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
                   3114: 		if (exists($record{'resource.'.$new_part.'.awarded'})) {
                   3115: 		    $newrecord{'resource.'.$new_part.'.awarded'} = '';
1.58      albertel 3116: 		}
1.364     banghart 3117: 	        $newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
1.58      albertel 3118: 	    }
1.125     ng       3119: 	} elsif ($dropMenu eq 'reset status'
1.259     banghart 3120: 		 && exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
1.524     raeburn  3121: 	    foreach my $key (keys(%record)) {
1.259     banghart 3122: 		if ($key=~/^resource\.\Q$new_part\E\./) { $newrecord{$key} = ''; }
1.197     albertel 3123: 	    }
1.259     banghart 3124: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
1.257     albertel 3125: 		"$env{'user.name'}:$env{'user.domain'}";
1.270     albertel 3126:             my $totaltries = $record{'resource.'.$part.'.tries'};
                   3127: 
                   3128:             my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
                   3129: 					       [$new_part]);
                   3130:             my $aggtries =$totaltries;
1.269     raeburn  3131:             if ($last_resets{$new_part}) {
1.270     albertel 3132:                 $aggtries = &get_num_tries(\%record,$last_resets{$new_part},
                   3133: 					   $new_part);
1.269     raeburn  3134:             }
1.270     albertel 3135: 
                   3136:             my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
1.269     raeburn  3137:             if ($aggtries > 0) {
1.327     albertel 3138:                 &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
1.269     raeburn  3139:                 $aggregateflag = 1;
                   3140:             }
1.125     ng       3141: 	} elsif ($dropMenu eq '') {
1.259     banghart 3142: 	    $pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ? 
                   3143: 		    $env{'form.GD_BOX'.$newflg.'_'.$new_part} : 
                   3144: 		    $env{'form.RADVAL'.$newflg.'_'.$new_part});
                   3145: 	    if ($pts eq '' && $env{'form.GD_SEL'.$newflg.'_'.$new_part} eq '') {
1.153     albertel 3146: 		next;
                   3147: 	    }
1.259     banghart 3148: 	    $wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 : 
                   3149: 		$env{'form.WGT'.$newflg.'_'.$new_part};
1.41      ng       3150: 	    my $partial= $pts/$wgt;
1.259     banghart 3151: 	    if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
1.153     albertel 3152: 		#do not update score for part if not changed.
1.346     banghart 3153:                 &handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
1.153     albertel 3154: 		next;
1.251     banghart 3155: 	    } else {
1.524     raeburn  3156: 	        push(@parts_graded,$new_part);
1.153     albertel 3157: 	    }
1.259     banghart 3158: 	    if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
                   3159: 		$newrecord{'resource.'.$new_part.'.awarded'}  = $partial;
1.153     albertel 3160: 	    }
1.259     banghart 3161: 	    my $reckey = 'resource.'.$new_part.'.solved';
1.41      ng       3162: 	    if ($partial == 0) {
1.153     albertel 3163: 		if ($record{$reckey} ne 'incorrect_by_override') {
                   3164: 		    $newrecord{$reckey} = 'incorrect_by_override';
                   3165: 		}
1.41      ng       3166: 	    } else {
1.153     albertel 3167: 		if ($record{$reckey} ne 'correct_by_override') {
                   3168: 		    $newrecord{$reckey} = 'correct_by_override';
                   3169: 		}
                   3170: 	    }	    
                   3171: 	    if ($submitter && 
1.259     banghart 3172: 		($record{'resource.'.$new_part.'.submitted_by'} ne $submitter)) {
                   3173: 		$newrecord{'resource.'.$new_part.'.submitted_by'} = $submitter;
1.41      ng       3174: 	    }
1.259     banghart 3175: 	    $newrecord{'resource.'.$new_part.'.regrader'}=
1.257     albertel 3176: 		"$env{'user.name'}:$env{'user.domain'}";
1.41      ng       3177: 	}
1.259     banghart 3178: 	# unless problem has been graded, set flag to version the submitted files
1.305     banghart 3179: 	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
                   3180: 	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
                   3181: 	        $dropMenu eq 'reset status')
                   3182: 	   {
1.524     raeburn  3183: 	    push(@version_parts,$new_part);
1.259     banghart 3184: 	}
1.41      ng       3185:     }
1.301     albertel 3186:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   3187:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   3188: 
1.344     albertel 3189:     if (%newrecord) {
                   3190:         if (@version_parts) {
1.364     banghart 3191:             my @changed_keys = &version_portfiles(\%record, \@parts_graded, 
                   3192:                                 $env{'request.course.id'}, $symb, $domain, $stuname, \@version_parts);
1.344     albertel 3193: 	    @newrecord{@changed_keys} = @record{@changed_keys};
1.367     albertel 3194: 	    foreach my $new_part (@version_parts) {
                   3195: 		&handback_files($request,$symb,$stuname,$domain,$newflg,
                   3196: 				$new_part,\%newrecord);
                   3197: 	    }
1.259     banghart 3198:         }
1.44      ng       3199: 	&Apache::lonnet::cstore(\%newrecord,$symb,
1.257     albertel 3200: 				$env{'request.course.id'},$domain,$stuname);
1.380     albertel 3201: 	&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
                   3202: 				     $cdom,$cnum,$domain,$stuname);
1.41      ng       3203:     }
1.269     raeburn  3204:     if ($aggregateflag) {
                   3205:         &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
1.301     albertel 3206: 			      $cdom,$cnum);
1.269     raeburn  3207:     }
1.726     raeburn  3208:     return ('',$pts,$wgt,$totchg);
                   3209: }
                   3210: 
                   3211: sub makehidden {
1.728     raeburn  3212:     my ($version,$parts,$record,$symb,$domain,$stuname,$tolog) = @_;
1.726     raeburn  3213:     return unless (ref($record) eq 'HASH');
                   3214:     my %modified;
                   3215:     my $numchanged = 0;
                   3216:     if (exists($record->{$version.':keys'})) {
                   3217:         my $partsregexp = $parts;
                   3218:         $partsregexp =~ s/,/|/g;
                   3219:         foreach my $key (split(/\:/,$record->{$version.':keys'})) {
                   3220:             if ($key =~ /^resource\.(?:$partsregexp)\.([^\.]+)$/) {
                   3221:                  my $item = $1;
                   3222:                  unless (($item eq 'solved') || ($item =~ /^award(|msg|ed)$/)) {
                   3223:                      $modified{$key} = $record->{$version.':'.$key};
                   3224:                  }
                   3225:             } elsif ($key =~ m{^(resource\.(?:$partsregexp)\.[^\.]+\.)(.+)$}) {
                   3226:                 $modified{$1.'hidden'.$2} = $record->{$version.':'.$key};
                   3227:             } elsif ($key =~ /^(ip|timestamp|host)$/) {
                   3228:                 $modified{$key} = $record->{$version.':'.$key};
                   3229:             }
                   3230:         }
                   3231:         if (keys(%modified)) {
                   3232:             if (&Apache::lonnet::putstore($env{'request.course.id'},$symb,$version,\%modified,
1.728     raeburn  3233:                                           $domain,$stuname,$tolog) eq 'ok') {
1.726     raeburn  3234:                 $numchanged ++;
                   3235:             }
                   3236:         }
                   3237:     }
                   3238:     return $numchanged;
1.36      ng       3239: }
1.322     albertel 3240: 
1.380     albertel 3241: sub check_and_remove_from_queue {
                   3242:     my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
                   3243:     my @ungraded_parts;
                   3244:     foreach my $part (@{$parts}) {
                   3245: 	if (    $record->{   'resource.'.$part.'.awarded'} eq ''
                   3246: 	     && $record->{   'resource.'.$part.'.solved' } ne 'excused'
                   3247: 	     && $newrecord->{'resource.'.$part.'.awarded'} eq ''
                   3248: 	     && $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
                   3249: 		) {
                   3250: 	    push(@ungraded_parts, $part);
                   3251: 	}
                   3252:     }
                   3253:     if ( !@ungraded_parts ) {
                   3254: 	&Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,
                   3255: 					       $cnum,$domain,$stuname);
                   3256:     }
                   3257: }
                   3258: 
1.337     banghart 3259: sub handback_files {
                   3260:     my ($request,$symb,$stuname,$domain,$newflg,$new_part,$newrecord) = @_;
1.517     raeburn  3261:     my $portfolio_root = '/userfiles/portfolio';
1.582     raeburn  3262:     my $res_error;
                   3263:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   3264:     if ($res_error) {
                   3265:         $request->print('<br />'.&navmap_errormsg().'<br />');
                   3266:         return;
                   3267:     }
1.654     raeburn  3268:     my @handedback;
                   3269:     my $file_msg;
1.375     albertel 3270:     my @part_response_id = &flatten_responseType($responseType);
                   3271:     foreach my $part_response_id (@part_response_id) {
                   3272:     	my ($part_id,$resp_id) = @{ $part_response_id };
                   3273: 	my $part_resp = join('_',@{ $part_response_id });
1.654     raeburn  3274:         if (($env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'} =~ /^\d+$/) & ($new_part eq $part_id)) {
                   3275:             for (my $counter=1; $counter<=$env{'form.'.$newflg.'_'.$part_resp.'_countreturndoc'}; $counter++) {
                   3276:                 # if multiple files are uploaded names will be 'returndoc2','returndoc3' 
                   3277:                 if ($env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter}) {
                   3278:                     my $fname=$env{'form.'.$newflg.'_'.$part_resp.'_returndoc'.$counter.'.filename'};
1.338     banghart 3279:                     my ($directory,$answer_file) = 
1.654     raeburn  3280:                         ($env{'form.'.$newflg.'_'.$part_resp.'_origdoc'.$counter} =~ /^(.*?)([^\/]*)$/);
1.338     banghart 3281:                     my ($answer_name,$answer_ver,$answer_ext) =
1.729     raeburn  3282: 		        &Apache::lonnet::file_name_version_ext($answer_file);
1.355     banghart 3283: 		    my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
1.517     raeburn  3284:                     my $getpropath = 1;
1.662     raeburn  3285:                     my ($dir_list,$listerror) = 
                   3286:                         &Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
                   3287:                                                  $domain,$stuname,$getpropath);
1.729     raeburn  3288: 		    my $version = &Apache::lonnet::get_next_version($answer_name,$answer_ext,$dir_list);
1.686     bisitz   3289:                     # fix filename
1.355     banghart 3290:                     my ($save_file_name) = (($directory.$answer_name.".$version.".$answer_ext) =~ /^.+\/${stuname}\/(.*)/);
                   3291:                     my $result=&Apache::lonnet::finishuserfileupload($stuname,$domain,
1.654     raeburn  3292:             	                                $newflg.'_'.$part_resp.'_returndoc'.$counter,
1.355     banghart 3293:             	                                $save_file_name);
1.337     banghart 3294:                     if ($result !~ m|^/uploaded/|) {
1.536     raeburn  3295:                         $request->print('<br /><span class="LC_error">'.
                   3296:                             &mt('An error occurred ([_1]) while trying to upload [_2].',
1.654     raeburn  3297:                                 $result,$newflg.'_'.$part_resp.'_returndoc'.$counter).
1.536     raeburn  3298:                                         '</span>');
1.356     banghart 3299:                     } else {
1.360     banghart 3300:                         # mark the file as read only
1.654     raeburn  3301:                         push(@handedback,$save_file_name);
1.367     albertel 3302: 			if (exists($$newrecord{"resource.$new_part.$resp_id.handback"})) {
                   3303: 			    $$newrecord{"resource.$new_part.$resp_id.handback"}.=',';
                   3304: 			}
                   3305:                         $$newrecord{"resource.$new_part.$resp_id.handback"} .= $save_file_name;
1.654     raeburn  3306: 			$file_msg.= '<span class="LC_filename"><a href="/uploaded/'."$domain/$stuname/".$save_file_name.'">'.$save_file_name."</a></span> <br />";
1.337     banghart 3307:                     }
1.686     bisitz   3308:                     $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 3309:                 }
                   3310:             }
                   3311:         }
1.654     raeburn  3312:     }
                   3313:     if (@handedback > 0) {
                   3314:         $request->print('<br />');
                   3315:         my @what = ($symb,$env{'request.course.id'},'handback');
                   3316:         &Apache::lonnet::mark_as_readonly($domain,$stuname,\@handedback,\@what);
                   3317:         my $user_lh = &Apache::loncommon::user_lang($stuname,$domain,$env{'request.course.id'});    
                   3318:         my ($subject,$message);
                   3319:         if (scalar(@handedback) == 1) {
                   3320:             $subject = &mt_user($user_lh,'File Handed Back by Instructor');
                   3321:             $message = &mt_user($user_lh,'A file has been returned that was originally submitted in response to: ');
                   3322:         } else {
                   3323:             $subject = &mt_user($user_lh,'Files Handed Back by Instructor');
                   3324:             $message = &mt_user($user_lh,'Files have been returned that were originally submitted in response to: ');
                   3325:         }
                   3326:         $message .= "<p><strong>".&Apache::lonnet::gettitle($symb)." </strong></p>";
                   3327:         $message .= &mt_user($user_lh,'The returned file(s) are named: [_1]',"<br />$file_msg <br />").
                   3328:                     &mt_user($user_lh,'The file(s) can be found in your [_1]portfolio[_2].','<a href="/adm/portfolio">','</a>');
                   3329:         my ($feedurl,$showsymb) =
                   3330:             &get_feedurl_and_symb($symb,$domain,$stuname);
                   3331:         my $restitle = &Apache::lonnet::gettitle($symb);
                   3332:         $subject .= ' '.&mt_user($user_lh,'(File Returned)').' ['.$restitle.']';
                   3333:         my $msgstatus =
                   3334:              &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject,
                   3335:                  $message,undef,$feedurl,undef,undef,undef,$showsymb,
                   3336:                  $restitle);
                   3337:         if ($msgstatus) {
                   3338:             $request->print(&mt('Notification message status: [_1]','<span class="LC_info">'.$msgstatus.'</span>').'<br />');
                   3339:         }
                   3340:     }
1.338     banghart 3341:     return;
1.337     banghart 3342: }
                   3343: 
1.418     albertel 3344: sub get_feedurl_and_symb {
                   3345:     my ($symb,$uname,$udom) = @_;
                   3346:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
                   3347:     $url = &Apache::lonnet::clutter($url);
                   3348:     my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
                   3349: 					$symb,$udom,$uname);
                   3350:     if ($encrypturl =~ /^yes$/i) {
                   3351: 	&Apache::lonenc::encrypted(\$url,1);
                   3352: 	&Apache::lonenc::encrypted(\$symb,1);
                   3353:     }
                   3354:     return ($url,$symb);
                   3355: }
                   3356: 
1.313     banghart 3357: sub get_submitted_files {
                   3358:     my ($udom,$uname,$partid,$respid,$record) = @_;
                   3359:     my @files;
                   3360:     if ($$record{"resource.$partid.$respid.portfiles"}) {
                   3361:         my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
                   3362:         foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
                   3363:     	    push(@files,$file_url.$file);
                   3364:         }
                   3365:     }
                   3366:     if ($$record{"resource.$partid.$respid.uploadedurl"}) {
                   3367:         push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
                   3368:     }
                   3369:     return (\@files);
                   3370: }
1.322     albertel 3371: 
1.269     raeburn  3372: # ----------- Provides number of tries since last reset.
                   3373: sub get_num_tries {
                   3374:     my ($record,$last_reset,$part) = @_;
                   3375:     my $timestamp = '';
                   3376:     my $num_tries = 0;
                   3377:     if ($$record{'version'}) {
                   3378:         for (my $version=$$record{'version'};$version>=1;$version--) {
                   3379:             if (exists($$record{$version.':resource.'.$part.'.solved'})) {
                   3380:                 $timestamp = $$record{$version.':timestamp'};
                   3381:                 if ($timestamp > $last_reset) {
                   3382:                     $num_tries ++;
                   3383:                 } else {
                   3384:                     last;
                   3385:                 }
                   3386:             }
                   3387:         }
                   3388:     }
                   3389:     return $num_tries;
                   3390: }
                   3391: 
                   3392: # ----------- Determine decrements required in aggregate totals 
                   3393: sub decrement_aggs {
                   3394:     my ($symb,$part,$aggregate,$aggtries,$totaltries,$solvedstatus) = @_;
                   3395:     my %decrement = (
                   3396:                         attempts => 0,
                   3397:                         users => 0,
                   3398:                         correct => 0
                   3399:                     );
                   3400:     $decrement{'attempts'} = $aggtries;
                   3401:     if ($solvedstatus =~ /^correct/) {
                   3402:         $decrement{'correct'} = 1;
                   3403:     }
                   3404:     if ($aggtries == $totaltries) {
                   3405:         $decrement{'users'} = 1;
                   3406:     }
1.524     raeburn  3407:     foreach my $type (keys(%decrement)) {
1.269     raeburn  3408:         $$aggregate{$symb."\0".$part."\0".$type} = -$decrement{$type};
                   3409:     }
                   3410:     return;
                   3411: }
                   3412: 
                   3413: # ----------- Determine timestamps for last reset of aggregate totals for parts  
                   3414: sub get_last_resets {
1.270     albertel 3415:     my ($symb,$courseid,$partids) =@_;
                   3416:     my %last_resets;
1.269     raeburn  3417:     my $cdom = $env{'course.'.$courseid.'.domain'};
                   3418:     my $cname = $env{'course.'.$courseid.'.num'};
1.271     albertel 3419:     my @keys;
                   3420:     foreach my $part (@{$partids}) {
                   3421: 	push(@keys,"$symb\0$part\0resettime");
                   3422:     }
                   3423:     my %results=&Apache::lonnet::get('nohist_resourcetracker',\@keys,
                   3424: 				     $cdom,$cname);
                   3425:     foreach my $part (@{$partids}) {
                   3426: 	$last_resets{$part}=$results{"$symb\0$part\0resettime"};
1.269     raeburn  3427:     }
1.270     albertel 3428:     return %last_resets;
1.269     raeburn  3429: }
                   3430: 
1.251     banghart 3431: # ----------- Handles creating versions for portfolio files as answers
                   3432: sub version_portfiles {
1.343     banghart 3433:     my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
1.263     banghart 3434:     my $version_parts = join('|',@$v_flag);
1.343     banghart 3435:     my @returned_keys;
1.255     banghart 3436:     my $parts = join('|', @$parts_graded);
1.277     albertel 3437:     foreach my $key (keys(%$record)) {
1.259     banghart 3438:         my $new_portfiles;
1.263     banghart 3439:         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
1.342     banghart 3440:             my @versioned_portfiles;
1.367     albertel 3441:             my @portfiles = split(/\s*,\s*/,$$record{$key});
1.729     raeburn  3442:             if (@portfiles) {
                   3443:                 &Apache::lonnet::portfiles_versioning($symb,$domain,$stu_name,\@portfiles,
                   3444:                                                       \@versioned_portfiles);
1.252     banghart 3445:             }
1.343     banghart 3446:             $$record{$key} = join(',',@versioned_portfiles);
                   3447:             push(@returned_keys,$key);
1.251     banghart 3448:         }
                   3449:     } 
1.343     banghart 3450:     return (@returned_keys);   
1.305     banghart 3451: }
                   3452: 
1.44      ng       3453: #--------------------------------------------------------------------------------------
                   3454: #
                   3455: #-------------------------- Next few routines handles grading by section or whole class
                   3456: #
                   3457: #--- Javascript to handle grading by section or whole class
1.42      ng       3458: sub viewgrades_js {
                   3459:     my ($request) = shift;
                   3460: 
1.539     riegler  3461:     my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = ');
1.736     damieng  3462:     &js_escape(\$alertmsg);
1.597     wenzelju 3463:     $request->print(&Apache::lonhtmlcommon::scripttag(<<VIEWJAVASCRIPT));
1.45      ng       3464:    function writePoint(partid,weight,point) {
1.125     ng       3465: 	var radioButton = document.classgrade["RADVAL_"+partid];
                   3466: 	var textbox = document.classgrade["TEXTVAL_"+partid];
1.42      ng       3467: 	if (point == "textval") {
1.125     ng       3468: 	    point = document.classgrade["TEXTVAL_"+partid].value;
1.109     matthew  3469: 	    if (isNaN(point) || parseFloat(point) < 0) {
1.539     riegler  3470: 		alert("$alertmsg"+parseFloat(point));
1.42      ng       3471: 		var resetbox = false;
                   3472: 		for (var i=0; i<radioButton.length; i++) {
                   3473: 		    if (radioButton[i].checked) {
                   3474: 			textbox.value = i;
                   3475: 			resetbox = true;
                   3476: 		    }
                   3477: 		}
                   3478: 		if (!resetbox) {
                   3479: 		    textbox.value = "";
                   3480: 		}
                   3481: 		return;
                   3482: 	    }
1.109     matthew  3483: 	    if (parseFloat(point) > parseFloat(weight)) {
                   3484: 		var resp = confirm("You entered a value ("+parseFloat(point)+
1.44      ng       3485: 				   ") greater than the weight for the part. Accept?");
                   3486: 		if (resp == false) {
                   3487: 		    textbox.value = "";
                   3488: 		    return;
                   3489: 		}
                   3490: 	    }
1.42      ng       3491: 	    for (var i=0; i<radioButton.length; i++) {
                   3492: 		radioButton[i].checked=false;
1.109     matthew  3493: 		if (parseFloat(point) == i) {
1.42      ng       3494: 		    radioButton[i].checked=true;
                   3495: 		}
                   3496: 	    }
1.41      ng       3497: 
1.42      ng       3498: 	} else {
1.125     ng       3499: 	    textbox.value = parseFloat(point);
1.42      ng       3500: 	}
1.41      ng       3501: 	for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3502: 	    var user = document.classgrade["ctr"+i].value;
1.289     albertel 3503: 	    user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3504: 	    var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3505: 	    var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3506: 	    var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.42      ng       3507: 	    if (saveval != "correct") {
                   3508: 		scorename.value = point;
1.43      ng       3509: 		if (selname[0].selected != true) {
                   3510: 		    selname[0].selected = true;
                   3511: 		}
1.42      ng       3512: 	    }
                   3513: 	}
1.125     ng       3514: 	document.classgrade["SELVAL_"+partid][0].selected = true;
1.42      ng       3515:     }
                   3516: 
                   3517:     function writeRadText(partid,weight) {
1.125     ng       3518: 	var selval   = document.classgrade["SELVAL_"+partid];
                   3519: 	var radioButton = document.classgrade["RADVAL_"+partid];
1.265     www      3520:         var override = document.classgrade["FORCE_"+partid].checked;
1.125     ng       3521: 	var textbox = document.classgrade["TEXTVAL_"+partid];
                   3522: 	if (selval[1].selected || selval[2].selected) {
1.42      ng       3523: 	    for (var i=0; i<radioButton.length; i++) {
                   3524: 		radioButton[i].checked=false;
                   3525: 
                   3526: 	    }
                   3527: 	    textbox.value = "";
                   3528: 
                   3529: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3530: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3531: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3532: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3533: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3534: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.265     www      3535: 		if ((saveval != "correct") || override) {
1.42      ng       3536: 		    scorename.value = "";
1.125     ng       3537: 		    if (selval[1].selected) {
                   3538: 			selname[1].selected = true;
                   3539: 		    } else {
                   3540: 			selname[2].selected = true;
                   3541: 			if (Number(document.classgrade["GD_"+user+"_"+partid+"_tries"].value)) 
                   3542: 			{document.classgrade["GD_"+user+"_"+partid+"_tries"].value = '0';}
                   3543: 		    }
1.42      ng       3544: 		}
                   3545: 	    }
1.43      ng       3546: 	} else {
                   3547: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3548: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3549: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3550: 		var scorename = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3551: 		var saveval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3552: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.265     www      3553: 		if ((saveval != "correct") || override) {
1.125     ng       3554: 		    scorename.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
1.43      ng       3555: 		    selname[0].selected = true;
                   3556: 		}
                   3557: 	    }
                   3558: 	}	    
1.42      ng       3559:     }
                   3560: 
                   3561:     function changeSelect(partid,user) {
1.125     ng       3562: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
                   3563: 	var textbox = document.classgrade["GD_"+user+'_'+partid+"_awarded"];
1.44      ng       3564: 	var point  = textbox.value;
1.125     ng       3565: 	var weight = document.classgrade["weight_"+partid].value;
1.44      ng       3566: 
1.109     matthew  3567: 	if (isNaN(point) || parseFloat(point) < 0) {
1.539     riegler  3568: 	    alert("$alertmsg"+parseFloat(point));
1.44      ng       3569: 	    textbox.value = "";
                   3570: 	    return;
                   3571: 	}
1.109     matthew  3572: 	if (parseFloat(point) > parseFloat(weight)) {
                   3573: 	    var resp = confirm("You entered a value ("+parseFloat(point)+
1.44      ng       3574: 			       ") greater than the weight of the part. Accept?");
                   3575: 	    if (resp == false) {
                   3576: 		textbox.value = "";
                   3577: 		return;
                   3578: 	    }
                   3579: 	}
1.42      ng       3580: 	selval[0].selected = true;
                   3581:     }
                   3582: 
                   3583:     function changeOneScore(partid,user) {
1.125     ng       3584: 	var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"];
                   3585: 	if (selval[1].selected || selval[2].selected) {
                   3586: 	    document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = "";
                   3587: 	    if (selval[2].selected) {
                   3588: 		document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0";
                   3589: 	    }
1.269     raeburn  3590:         }
1.42      ng       3591:     }
                   3592: 
                   3593:     function resetEntry(numpart) {
                   3594: 	for (ctpart=0;ctpart<numpart;ctpart++) {
1.125     ng       3595: 	    var partid = document.classgrade["partid_"+ctpart].value;
                   3596: 	    var radioButton = document.classgrade["RADVAL_"+partid];
                   3597: 	    var textbox = document.classgrade["TEXTVAL_"+partid];
                   3598: 	    var selval  = document.classgrade["SELVAL_"+partid];
1.42      ng       3599: 	    for (var i=0; i<radioButton.length; i++) {
                   3600: 		radioButton[i].checked=false;
                   3601: 
                   3602: 	    }
                   3603: 	    textbox.value = "";
                   3604: 	    selval[0].selected = true;
                   3605: 
                   3606: 	    for (i=0;i<document.classgrade.total.value;i++) {
1.125     ng       3607: 		var user = document.classgrade["ctr"+i].value;
1.289     albertel 3608: 		user = user.replace(new RegExp(':', 'g'),"_");
1.125     ng       3609: 		var resetscore = document.classgrade["GD_"+user+"_"+partid+"_awarded"];
                   3610: 		resetscore.value = document.classgrade["GD_"+user+"_"+partid+"_awarded_s"].value;
                   3611: 		var resettries = document.classgrade["GD_"+user+"_"+partid+"_tries"];
                   3612: 		resettries.value = document.classgrade["GD_"+user+"_"+partid+"_tries_s"].value;
                   3613: 		var saveselval   = document.classgrade["GD_"+user+"_"+partid+"_solved_s"].value;
                   3614: 		var selname   = document.classgrade["GD_"+user+"_"+partid+"_solved"];
1.42      ng       3615: 		if (saveselval == "excused") {
1.43      ng       3616: 		    if (selname[1].selected == false) { selname[1].selected = true;}
1.42      ng       3617: 		} else {
1.43      ng       3618: 		    if (selname[0].selected == false) {selname[0].selected = true};
1.42      ng       3619: 		}
                   3620: 	    }
1.41      ng       3621: 	}
1.42      ng       3622:     }
                   3623: 
1.41      ng       3624: VIEWJAVASCRIPT
1.42      ng       3625: }
                   3626: 
1.44      ng       3627: #--- show scores for a section or whole class w/ option to change/update a score
1.42      ng       3628: sub viewgrades {
1.608     www      3629:     my ($request,$symb) = @_;
1.745     raeburn  3630:     my ($is_tool,$toolsymb);
                   3631:     if ($symb =~ /ext\.tool$/) {
                   3632:         $is_tool = 1;
                   3633:         $toolsymb = $symb;
                   3634:     }
1.42      ng       3635:     &viewgrades_js($request);
1.41      ng       3636: 
1.168     albertel 3637:     #need to make sure we have the correct data for later EXT calls, 
                   3638:     #thus invalidate the cache
                   3639:     &Apache::lonnet::devalidatecourseresdata(
1.257     albertel 3640:                  $env{'course.'.$env{'request.course.id'}.'.num'},
                   3641:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
1.168     albertel 3642:     &Apache::lonnet::clear_EXT_cache_status();
                   3643: 
1.398     albertel 3644:     my $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>';
1.41      ng       3645: 
                   3646:     #view individual student submission form - called using Javascript viewOneStudent
1.324     albertel 3647:     $result.=&jscriptNform($symb);
1.41      ng       3648: 
1.44      ng       3649:     #beginning of class grading form
1.442     banghart 3650:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
1.41      ng       3651:     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
1.418     albertel 3652: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.38      ng       3653: 	'<input type="hidden" name="command" value="editgrades" />'."\n".
1.432     banghart 3654: 	&build_section_inputs().
1.442     banghart 3655: 	'<input type="hidden" name="Status" value="'.$env{'stu_status'}.'" />'."\n".
1.72      ng       3656: 
1.738     raeburn  3657:     #retrieve selected groups
                   3658:     my (@groups,$group_display);
                   3659:     @groups = &Apache::loncommon::get_env_multiple('form.group');
                   3660:     if (grep(/^all$/,@groups)) {
                   3661:         @groups = ('all');
                   3662:     } elsif (grep(/^none$/,@groups)) {
                   3663:         @groups = ('none');
                   3664:     } elsif (@groups > 0) {
                   3665:         $group_display = join(', ',@groups);
                   3666:     }
                   3667: 
                   3668:     my ($common_header,$specific_header,@sections,$section_display);
                   3669:     @sections = &Apache::loncommon::get_env_multiple('form.section');
                   3670:     if (grep(/^all$/,@sections)) {
                   3671:         @sections = ('all');
                   3672:         if ($group_display) {
                   3673:             $common_header = &mt('Assign Common Grade to Students in Group(s) [_1]',$group_display);
                   3674:             $specific_header = &mt('Assign Grade to Specific Students in Group(s) [_1]',$group_display);
                   3675:         } elsif (grep(/^none$/,@groups)) {
                   3676:             $common_header = &mt('Assign Common Grade to Students not assigned to any groups');
                   3677:             $specific_header = &mt('Assign Grade to Specific Students not assigned to any groups');
                   3678:         } else {
                   3679: 	    $common_header = &mt('Assign Common Grade to Class');
                   3680:             $specific_header = &mt('Assign Grade to Specific Students in Class');
                   3681:         }
                   3682:     } elsif (grep(/^none$/,@sections)) {
                   3683:         @sections = ('none');
                   3684:         if ($group_display) {
                   3685:             $common_header = &mt('Assign Common Grade to Students in no Section and in Group(s) [_1]',$group_display);
                   3686:             $specific_header = &mt('Assign Grade to Specific Students in no Section and in Group(s)',$group_display);
                   3687:         } elsif (grep(/^none$/,@groups)) {
                   3688:             $common_header = &mt('Assign Common Grade to Students in no Section and in no Group');
                   3689:             $specific_header = &mt('Assign Grade to Specific Students in no Section and in no Group');
                   3690:         } else {
                   3691:             $common_header = &mt('Assign Common Grade to Students in no Section');
                   3692: 	    $specific_header = &mt('Assign Grade to Specific Students in no Section');
                   3693:         }
                   3694:     } else {
                   3695:         $section_display = join (", ",@sections);
                   3696:         if ($group_display) {
                   3697:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1], and in Group(s) [_2]',
                   3698:                                  $section_display,$group_display);
                   3699:             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1], and in Group(s) [_2]',
                   3700:                                    $section_display,$group_display);
                   3701:         } elsif (grep(/^none$/,@groups)) {
                   3702:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1] and no Group',$section_display);
                   3703:             $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1] and no Group',$section_display);
                   3704:         } else {
                   3705:             $common_header = &mt('Assign Common Grade to Students in Section(s) [_1]',$section_display);
                   3706: 	    $specific_header = &mt('Assign Grade to Specific Students in Section(s) [_1]',$section_display);
                   3707:         }
                   3708:     }
                   3709:     my %submit_types = &substatus_options();
                   3710:     my $submission_status = $submit_types{$env{'form.submitonly'}};
                   3711: 
                   3712:     if ($env{'form.submitonly'} eq 'all') {
                   3713:         $result.= '<h3>'.$common_header.'</h3>';
                   3714:     } else {
1.745     raeburn  3715:         my $text;
                   3716:         if ($is_tool) {
                   3717:             $text = &mt('(transaction status: "[_1]")',$submission_status);
                   3718:         } else {
                   3719:             $text = &mt('(submission status: "[_1]")',$submission_status);
                   3720:         }
                   3721:         $result.= '<h3>'.$common_header.'&nbsp;'.$text.'</h3>';
1.52      albertel 3722:     }
1.738     raeburn  3723:     $result .= &Apache::loncommon::start_data_table();
1.44      ng       3724:     #radio buttons/text box for assigning points for a section or class.
                   3725:     #handles different parts of a problem
1.582     raeburn  3726:     my $res_error;
                   3727:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   3728:     if ($res_error) {
                   3729:         return &navmap_errormsg();
                   3730:     }
1.42      ng       3731:     my %weight = ();
                   3732:     my $ctsparts = 0;
1.45      ng       3733:     my %seen = ();
1.745     raeburn  3734:     my @part_response_id;
                   3735:     if ($is_tool) {
                   3736:         @part_response_id = ([0,'']);
                   3737:     } else {
                   3738:         @part_response_id = &flatten_responseType($responseType);
                   3739:     }
1.375     albertel 3740:     foreach my $part_response_id (@part_response_id) {
                   3741:     	my ($partid,$respid) = @{ $part_response_id };
                   3742: 	my $part_resp = join('_',@{ $part_response_id });
1.45      ng       3743: 	next if $seen{$partid};
                   3744: 	$seen{$partid}++;
1.744     raeburn  3745: #	my $handgrade=$$handgrade{$part_resp};
1.42      ng       3746: 	my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
                   3747: 	$weight{$partid} = $wgt eq '' ? '1' : $wgt;
                   3748: 
1.324     albertel 3749: 	my $display_part=&get_display_part($partid,$symb);
1.485     albertel 3750: 	my $radio.='<table border="0"><tr>';  
1.41      ng       3751: 	my $ctr = 0;
1.42      ng       3752: 	while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across
1.485     albertel 3753: 	    $radio.= '<td><label><input type="radio" name="RADVAL_'.$partid.'" '.
1.54      albertel 3754: 		'onclick="javascript:writePoint(\''.$partid.'\','.$weight{$partid}.
1.288     albertel 3755: 		','.$ctr.')" />'.$ctr."</label></td>\n";
1.41      ng       3756: 	    $result.=(($ctr+1)%10 == 0 ? '</tr><tr>' : '');
                   3757: 	    $ctr++;
                   3758: 	}
1.485     albertel 3759: 	$radio.='</tr></table>';
                   3760: 	my $line = '<input type="text" name="TEXTVAL_'.
1.589     bisitz   3761: 	    $partid.'" size="4" '.'onchange="javascript:writePoint(\''.
1.54      albertel 3762: 		$partid.'\','.$weight{$partid}.',\'textval\')" /> /'.
1.539     riegler  3763: 	    $weight{$partid}.' '.&mt('(problem weight)').'</td>'."\n";
1.701     bisitz   3764:         $line.= '<td><b>'.&mt('Grade Status').':</b>'.
                   3765:             '<select name="SELVAL_'.$partid.'" '.
                   3766:             'onchange="javascript:writeRadText(\''.$partid.'\','.
                   3767:                 $weight{$partid}.')"> '.
1.401     albertel 3768: 	    '<option selected="selected"> </option>'.
1.485     albertel 3769: 	    '<option value="excused">'.&mt('excused').'</option>'.
                   3770: 	    '<option value="reset status">'.&mt('reset status').'</option>'.
                   3771: 	    '</select></td>'.
                   3772:             '<td><label><input type="checkbox" name="FORCE_'.$partid.'" />'.&mt('Override "Correct"').'</label>';
                   3773: 	$line.='<input type="hidden" name="partid_'.
                   3774: 	    $ctsparts.'" value="'.$partid.'" />'."\n";
                   3775: 	$line.='<input type="hidden" name="weight_'.
                   3776: 	    $partid.'" value="'.$weight{$partid}.'" />'."\n";
                   3777: 
                   3778: 	$result.=
                   3779: 	    &Apache::loncommon::start_data_table_row()."\n".
1.577     bisitz   3780: 	    '<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 3781: 	    &Apache::loncommon::end_data_table_row()."\n";
1.42      ng       3782: 	$ctsparts++;
1.41      ng       3783:     }
1.474     albertel 3784:     $result.=&Apache::loncommon::end_data_table()."\n".
1.52      albertel 3785: 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
1.485     albertel 3786:     $result.='<input type="button" value="'.&mt('Revert to Default').'" '.
1.589     bisitz   3787: 	'onclick="javascript:resetEntry('.$ctsparts.');" />';
1.41      ng       3788: 
1.44      ng       3789:     #table listing all the students in a section/class
                   3790:     #header of table
1.738     raeburn  3791:     if ($env{'form.submitonly'} eq 'all') {
                   3792:         $result.= '<h3>'.$specific_header.'</h3>';
                   3793:     } else {
1.745     raeburn  3794:         my $text;
                   3795:         if ($is_tool) {
                   3796:             $text = &mt('(transaction status: "[_1]")',$submission_status);
                   3797:         } else {
                   3798:             $text = &mt('(submission status: "[_1]")',$submission_status);
                   3799:         }
                   3800:         $result.= '<h3>'.$specific_header.'&nbsp;'.$text.'</h3>';
1.738     raeburn  3801:     }
                   3802:     $result.= &Apache::loncommon::start_data_table().
1.560     raeburn  3803: 	      &Apache::loncommon::start_data_table_header_row().
                   3804: 	      '<th>'.&mt('No.').'</th>'.
                   3805: 	      '<th>'.&nameUserString('header')."</th>\n";
1.582     raeburn  3806:     my $partserror;
                   3807:     my (@parts) = sort(&getpartlist($symb,\$partserror));
                   3808:     if ($partserror) {
                   3809:         return &navmap_errormsg();
                   3810:     }
1.324     albertel 3811:     my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb);
1.269     raeburn  3812:     my @partids = ();
1.41      ng       3813:     foreach my $part (@parts) {
1.745     raeburn  3814: 	my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
1.539     riegler  3815:         my $narrowtext = &mt('Tries');
                   3816: 	$display =~ s|^Number of Attempts|$narrowtext <br />|; # makes the column narrower
1.745     raeburn  3817: 	if  (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name',$toolsymb); }
1.207     albertel 3818: 	my ($partid) = &split_part_type($part);
1.524     raeburn  3819:         push(@partids,$partid);
1.628     www      3820: #
                   3821: # FIXME: Looks like $display looks at English text
                   3822: #
1.324     albertel 3823: 	my $display_part=&get_display_part($partid,$symb);
1.41      ng       3824: 	if ($display =~ /^Partial Credit Factor/) {
1.485     albertel 3825: 	    $result.='<th>'.
1.697     bisitz   3826: 		&mt('Score Part: [_1][_2](weight = [_3])',
                   3827: 		    $display_part,'<br />',$weight{$partid}).'</th>'."\n";
1.41      ng       3828: 	    next;
1.485     albertel 3829: 	    
1.207     albertel 3830: 	} else {
1.485     albertel 3831: 	    if ($display =~ /Problem Status/) {
                   3832: 		my $grade_status_mt = &mt('Grade Status');
                   3833: 		$display =~ s{Problem Status}{$grade_status_mt<br />};
                   3834: 	    }
                   3835: 	    my $part_mt = &mt('Part:');
                   3836: 	    $display =~s{\[Part: \Q$partid\E\]}{$part_mt $display_part};
1.41      ng       3837: 	}
1.485     albertel 3838: 
1.474     albertel 3839: 	$result.='<th>'.$display.'</th>'."\n";
1.41      ng       3840:     }
1.474     albertel 3841:     $result.=&Apache::loncommon::end_data_table_header_row();
1.44      ng       3842: 
1.270     albertel 3843:     my %last_resets = 
                   3844: 	&get_last_resets($symb,$env{'request.course.id'},\@partids);
1.269     raeburn  3845: 
1.41      ng       3846:     #get info for each student
1.44      ng       3847:     #list all the students - with points and grade status
1.738     raeburn  3848:     my (undef,undef,$fullname) = &getclasslist(\@sections,'1',\@groups);
1.41      ng       3849:     my $ctr = 0;
1.294     albertel 3850:     foreach (sort 
                   3851: 	     {
                   3852: 		 if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   3853: 		     return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   3854: 		 }
                   3855: 		 return $a cmp $b;
                   3856: 	     } (keys(%$fullname))) {
1.324     albertel 3857: 	$result.=&viewstudentgrade($symb,$env{'request.course.id'},
1.745     raeburn  3858: 				   $_,$$fullname{$_},\@parts,\%weight,\$ctr,\%last_resets,$is_tool);
1.41      ng       3859:     }
1.474     albertel 3860:     $result.=&Apache::loncommon::end_data_table();
1.41      ng       3861:     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
1.485     albertel 3862:     $result.='<input type="button" value="'.&mt('Save').'" '.
1.589     bisitz   3863: 	'onclick="javascript:submit();" target="_self" /></form>'."\n";
1.738     raeburn  3864:     if ($ctr == 0) {
1.442     banghart 3865:         my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
1.738     raeburn  3866:         $result='<h3><span class="LC_info">'.&mt('Manual Grading').'</span></h3>'.
                   3867:                 '<span class="LC_warning">';
                   3868:         if ($env{'form.submitonly'} eq 'all') {
                   3869:             if (grep(/^all$/,@sections)) {
                   3870:                 if (grep(/^all$/,@groups)) {
                   3871:                     $result .= &mt('There are no students with enrollment status [_1] to modify or grade.',
                   3872:                                    $stu_status);
                   3873:                 } elsif (grep(/^none$/,@groups)) {
                   3874:                     $result .= &mt('There are no students with no group assigned and with enrollment status [_1] to modify or grade.',
                   3875:                                    $stu_status); 
                   3876:                 } else {
                   3877:                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] to modify or grade.',
                   3878:                                    $group_display,$stu_status);
                   3879:                 }
                   3880:             } elsif (grep(/^none$/,@sections)) {
                   3881:                 if (grep(/^all$/,@groups)) {
                   3882:                     $result .= &mt('There are no students in no section with enrollment status [_1] to modify or grade.',
                   3883:                                    $stu_status);
                   3884:                 } elsif (grep(/^none$/,@groups)) {
                   3885:                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] to modify or grade.',
                   3886:                                    $stu_status);
                   3887:                 } else {
                   3888:                     $result .= &mt('There are no students in no section in group(s) [_1] with enrollment status [_2] to modify or grade.',
                   3889:                                    $group_display,$stu_status);
                   3890:                 }
                   3891:             } else {
                   3892:                 if (grep(/^all$/,@groups)) {
                   3893:                     $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] to modify or grade.',
                   3894:                                    $section_display,$stu_status);
                   3895:                 } elsif (grep(/^none$/,@groups)) {
1.739     raeburn  3896:                     $result .= &mt('There are no students in section(s) [_1] and no group with enrollment status [_2] to modify or grade.',
1.738     raeburn  3897:                                    $section_display,$stu_status);
                   3898:                 } else {
                   3899:                     $result .= &mt('There are no students in section(s) [_1] and group(s) [_2] with enrollment status [_3] to modify or grade.',
                   3900:                                    $section_display,$group_display,$stu_status);
                   3901:                 }
                   3902:             }
                   3903:         } else {
                   3904:             if (grep(/^all$/,@sections)) {
                   3905:                 if (grep(/^all$/,@groups)) {
                   3906:                     $result .= &mt('There are no students with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3907:                                    $stu_status,$submission_status);
                   3908:                 } elsif (grep(/^none$/,@groups)) {
                   3909:                     $result .= &mt('There are no students with no group assigned with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3910:                                    $stu_status,$submission_status);
                   3911:                 } else {
                   3912:                     $result .= &mt('There are no students in group(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                   3913:                                    $group_display,$stu_status,$submission_status);
                   3914:                 }
                   3915:             } elsif (grep(/^none$/,@sections)) {
                   3916:                 if (grep(/^all$/,@groups)) {
                   3917:                     $result .= &mt('There are no students in no section with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3918:                                    $stu_status,$submission_status);
                   3919:                 } elsif (grep(/^none$/,@groups)) {
                   3920:                     $result .= &mt('There are no students in no section and no group with enrollment status [_1] and submission status "[_2]" to modify or grade.',
                   3921:                                    $stu_status,$submission_status);
                   3922:                 } else {
                   3923:                     $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.',
                   3924:                                    $group_display,$stu_status,$submission_status);
                   3925:                 }
                   3926:             } else {
                   3927:                 if (grep(/^all$/,@groups)) {
                   3928: 	            $result .= &mt('There are no students in section(s) [_1] with enrollment status [_2] and submission status "[_3]" to modify or grade.',
                   3929: 	                           $section_display,$stu_status,$submission_status);
                   3930:                 } elsif (grep(/^none$/,@groups)) {
                   3931:                     $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.',
                   3932:                                    $section_display,$stu_status,$submission_status);
                   3933:                 } else {
                   3934:                     $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.',
                   3935:                                    $section_display,$group_display,$stu_status,$submission_status);
                   3936:                 }
                   3937:             }
                   3938:         }
                   3939: 	$result .= '</span><br />';
1.96      albertel 3940:     }
1.41      ng       3941:     return $result;
                   3942: }
                   3943: 
1.738     raeburn  3944: #--- call by previous routine to display each student who satisfies submission filter. 
1.41      ng       3945: sub viewstudentgrade {
1.745     raeburn  3946:     my ($symb,$courseid,$student,$fullname,$parts,$weight,$ctr,$last_resets,$is_tool) = @_;
1.44      ng       3947:     my ($uname,$udom) = split(/:/,$student);
                   3948:     my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname);
1.738     raeburn  3949:     my $submitonly = $env{'form.submitonly'};
                   3950:     unless (($submitonly eq 'all') || ($submitonly eq 'queued')) {
                   3951:         my %partstatus = ();
                   3952:         if (ref($parts) eq 'ARRAY') {
                   3953:             foreach my $apart (@{$parts}) {
                   3954:                 my ($part,$type) = &split_part_type($apart);
                   3955:                 my ($status,undef) = split(/_/,$record{"resource.$part.solved"},2);
                   3956:                 $status = 'nothing' if ($status eq '');
                   3957:                 $partstatus{$part}      = $status;
                   3958:                 my $subkey = "resource.$part.submitted_by";
                   3959:                 $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
                   3960:             }
                   3961:             my $submitted = 0;
                   3962:             my $graded = 0;
                   3963:             my $incorrect = 0;
                   3964:             foreach my $key (keys(%partstatus)) {
                   3965:                 $submitted = 1 if ($partstatus{$key} ne 'nothing');
                   3966:                 $graded = 1 if ($partstatus{$key} =~ /^ungraded/);
                   3967:                 $incorrect = 1 if ($partstatus{$key} =~ /^incorrect/);
                   3968: 
                   3969:                 my $partid = (split(/\./,$key))[1];
                   3970:                 if ($partstatus{'resource.'.$partid.'.'.$key.'.submitted_by'} ne '') {
                   3971:                     $submitted = 0;
                   3972:                 }
                   3973:             }
                   3974:             return if (!$submitted && ($submitonly eq 'yes' ||
                   3975:                                        $submitonly eq 'incorrect' ||
                   3976:                                        $submitonly eq 'graded'));
                   3977:             return if (!$graded && ($submitonly eq 'graded'));
                   3978:             return if (!$incorrect && $submitonly eq 'incorrect');
                   3979:         }
                   3980:     }
                   3981:     if ($submitonly eq 'queued') {
                   3982:         my ($cdom,$cnum) = split(/_/,$courseid);
                   3983:         my %queue_status =
                   3984:             &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
                   3985:                                                     $udom,$uname);
                   3986:         return if (!defined($queue_status{'gradingqueue'}));
                   3987:     }
                   3988:     $$ctr++;
                   3989:     my %aggregates = ();
1.474     albertel 3990:     my $result=&Apache::loncommon::start_data_table_row().'<td align="right">'.
1.738     raeburn  3991: 	'<input type="hidden" name="ctr'.($$ctr-1).'" value="'.$student.'" />'.
                   3992: 	"\n".$$ctr.'&nbsp;</td><td>&nbsp;'.
1.44      ng       3993: 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
1.417     albertel 3994: 	'\');" target="_self">'.$fullname.'</a> '.
1.398     albertel 3995: 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
1.281     albertel 3996:     $student=~s/:/_/; # colon doen't work in javascript for names
1.63      albertel 3997:     foreach my $apart (@$parts) {
                   3998: 	my ($part,$type) = &split_part_type($apart);
1.41      ng       3999: 	my $score=$record{"resource.$part.$type"};
1.276     albertel 4000:         $result.='<td align="center">';
1.269     raeburn  4001:         my ($aggtries,$totaltries);
                   4002:         unless (exists($aggregates{$part})) {
1.270     albertel 4003: 	    $totaltries = $record{'resource.'.$part.'.tries'};
                   4004: 	    $aggtries = $totaltries;
1.269     raeburn  4005:             if ($$last_resets{$part}) {  
1.270     albertel 4006:                 $aggtries = &get_num_tries(\%record,$$last_resets{$part},
                   4007: 					   $part);
                   4008:             }
1.269     raeburn  4009:             $result.='<input type="hidden" name="'.
                   4010:                 'GD_'.$student.'_'.$part.'_aggtries" value="'.$aggtries.'" />'."\n";
                   4011:             $result.='<input type="hidden" name="'.
                   4012:                 'GD_'.$student.'_'.$part.'_totaltries" value="'.$totaltries.'" />'."\n";
                   4013:             $aggregates{$part} = 1;
                   4014:         }
1.41      ng       4015: 	if ($type eq 'awarded') {
1.320     albertel 4016: 	    my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
1.42      ng       4017: 	    $result.='<input type="hidden" name="'.
1.89      albertel 4018: 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
1.233     albertel 4019: 	    $result.='<input type="text" name="'.
1.89      albertel 4020: 		'GD_'.$student.'_'.$part.'_awarded" '.
1.589     bisitz   4021:                 'onchange="javascript:changeSelect(\''.$part.'\',\''.$student.
1.44      ng       4022: 		'\')" value="'.$pts.'" size="4" /></td>'."\n";
1.41      ng       4023: 	} elsif ($type eq 'solved') {
                   4024: 	    my ($status,$foo)=split(/_/,$score,2);
                   4025: 	    $status = 'nothing' if ($status eq '');
1.89      albertel 4026: 	    $result.='<input type="hidden" name="'.'GD_'.$student.'_'.
1.54      albertel 4027: 		$part.'_solved_s" value="'.$status.'" />'."\n";
1.233     albertel 4028: 	    $result.='&nbsp;<select name="'.
1.89      albertel 4029: 		'GD_'.$student.'_'.$part.'_solved" '.
1.589     bisitz   4030:                 'onchange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n";
1.485     albertel 4031: 	    $result.= (($status eq 'excused') ? '<option> </option><option selected="selected" value="excused">'.&mt('excused').'</option>' 
                   4032: 		: '<option selected="selected"> </option><option value="excused">'.&mt('excused').'</option>')."\n";
                   4033: 	    $result.='<option value="reset status">'.&mt('reset status').'</option>';
1.126     ng       4034: 	    $result.="</select>&nbsp;</td>\n";
1.122     ng       4035: 	} else {
                   4036: 	    $result.='<input type="hidden" name="'.
                   4037: 		'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'.
                   4038: 		    "\n";
1.233     albertel 4039: 	    $result.='<input type="text" name="'.
1.122     ng       4040: 		'GD_'.$student.'_'.$part.'_'.$type.'" '.
                   4041: 		'value="'.$score.'" size="4" /></td>'."\n";
1.41      ng       4042: 	}
                   4043:     }
1.474     albertel 4044:     $result.=&Apache::loncommon::end_data_table_row();
1.41      ng       4045:     return $result;
1.38      ng       4046: }
                   4047: 
1.44      ng       4048: #--- change scores for all the students in a section/class
                   4049: #    record does not get update if unchanged
1.38      ng       4050: sub editgrades {
1.608     www      4051:     my ($request,$symb) = @_;
1.745     raeburn  4052:     my $toolsymb;
                   4053:     if ($symb =~ /ext\.tool$/) {
                   4054:         $toolsymb = $symb;
                   4055:     }
1.41      ng       4056: 
1.433     banghart 4057:     my $section_display = join (", ",&Apache::loncommon::get_env_multiple('form.section'));
1.477     albertel 4058:     my $title='<h2>'.&mt('Current Grade Status').'</h2>';
1.433     banghart 4059:     $title.='<h4>'.&mt('<b>Section: </b>[_1]',$section_display).'</h4>'."\n";
1.126     ng       4060: 
1.477     albertel 4061:     my $result= &Apache::loncommon::start_data_table().
                   4062: 	&Apache::loncommon::start_data_table_header_row().
                   4063: 	'<th rowspan="2" valign="middle">'.&mt('No.').'</th>'.
                   4064: 	'<th rowspan="2" valign="middle">'.&nameUserString('header')."</th>\n";
1.43      ng       4065:     my %scoreptr = (
                   4066: 		    'correct'  =>'correct_by_override',
                   4067: 		    'incorrect'=>'incorrect_by_override',
                   4068: 		    'excused'  =>'excused',
                   4069: 		    'ungraded' =>'ungraded_attempted',
1.596     raeburn  4070:                     'credited' =>'credit_attempted',
1.43      ng       4071: 		    'nothing'  => '',
                   4072: 		    );
1.257     albertel 4073:     my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
1.34      ng       4074: 
1.44      ng       4075:     my (@partid);
                   4076:     my %weight = ();
1.54      albertel 4077:     my %columns = ();
1.44      ng       4078:     my ($i,$ctr,$count,$rec_update) = (0,0,0,0);
1.54      albertel 4079: 
1.582     raeburn  4080:     my $partserror;
                   4081:     my (@parts) = sort(&getpartlist($symb,\$partserror));
                   4082:     if ($partserror) {
                   4083:         return &navmap_errormsg();
                   4084:     }
1.54      albertel 4085:     my $header;
1.257     albertel 4086:     while ($ctr < $env{'form.totalparts'}) {
                   4087: 	my $partid = $env{'form.partid_'.$ctr};
1.524     raeburn  4088: 	push(@partid,$partid);
1.257     albertel 4089: 	$weight{$partid} = $env{'form.weight_'.$partid};
1.44      ng       4090: 	$ctr++;
1.54      albertel 4091:     }
1.324     albertel 4092:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.54      albertel 4093:     foreach my $partid (@partid) {
1.478     albertel 4094: 	$header .= '<th align="center">'.&mt('Old Score').'</th>'.
                   4095: 	    '<th align="center">'.&mt('New Score').'</th>';
1.54      albertel 4096: 	$columns{$partid}=2;
                   4097: 	foreach my $stores (@parts) {
                   4098: 	    my ($part,$type) = &split_part_type($stores);
                   4099: 	    if ($part !~ m/^\Q$partid\E/) { next;}
                   4100: 	    if ($type eq 'awarded' || $type eq 'solved') { next; }
1.745     raeburn  4101: 	    my $display=&Apache::lonnet::metadata($url,$stores.'.display',$toolsymb);
1.551     raeburn  4102: 	    $display =~ s/\[Part: \Q$part\E\]//;
1.539     riegler  4103:             my $narrowtext = &mt('Tries');
                   4104: 	    $display =~ s/Number of Attempts/$narrowtext/;
                   4105: 	    $header .= '<th align="center">'.&mt('Old').' '.$display.'</th>'.
                   4106: 		'<th align="center">'.&mt('New').' '.$display.'</th>';
1.54      albertel 4107: 	    $columns{$partid}+=2;
                   4108: 	}
                   4109:     }
                   4110:     foreach my $partid (@partid) {
1.324     albertel 4111: 	my $display_part=&get_display_part($partid,$symb);
1.478     albertel 4112: 	$result .= '<th colspan="'.$columns{$partid}.'" align="center">'.
                   4113: 	    &mt('Part: [_1] (Weight = [_2])',$display_part,$weight{$partid}).
                   4114: 	    '</th>';
1.54      albertel 4115: 
1.44      ng       4116:     }
1.477     albertel 4117:     $result .= &Apache::loncommon::end_data_table_header_row().
                   4118: 	&Apache::loncommon::start_data_table_header_row().
                   4119: 	$header.
                   4120: 	&Apache::loncommon::end_data_table_header_row();
                   4121:     my @noupdate;
1.126     ng       4122:     my ($updateCtr,$noupdateCtr) = (1,1);
1.257     albertel 4123:     for ($i=0; $i<$env{'form.total'}; $i++) {
1.93      albertel 4124: 	my $line;
1.257     albertel 4125: 	my $user = $env{'form.ctr'.$i};
1.281     albertel 4126: 	my ($uname,$udom)=split(/:/,$user);
1.44      ng       4127: 	my %newrecord;
                   4128: 	my $updateflag = 0;
1.281     albertel 4129: 	$line .= '<td>'.&nameUserString(undef,$$fullname{$user},$uname,$udom).'</td>';
1.108     albertel 4130: 	my $usec=$classlist->{"$uname:$udom"}[5];
1.105     albertel 4131: 	if (!&canmodify($usec)) {
1.126     ng       4132: 	    my $numcols=scalar(@partid)*4+2;
1.477     albertel 4133: 	    push(@noupdate,
1.478     albertel 4134: 		 $line."<td colspan=\"$numcols\"><span class=\"LC_warning\">".
                   4135: 		 &mt('Not allowed to modify student')."</span></td></tr>");
1.105     albertel 4136: 	    next;
                   4137: 	}
1.269     raeburn  4138:         my %aggregate = ();
                   4139:         my $aggregateflag = 0;
1.281     albertel 4140: 	$user=~s/:/_/; # colon doen't work in javascript for names
1.44      ng       4141: 	foreach (@partid) {
1.257     albertel 4142: 	    my $old_aw    = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
1.54      albertel 4143: 	    my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
                   4144: 	    my $old_part  = $old_aw eq '' ? '' : $old_part_pcr;
1.257     albertel 4145: 	    my $old_score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
                   4146: 	    my $awarded   = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
1.54      albertel 4147: 	    my $pcr       = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
                   4148: 	    my $partial   = $awarded eq '' ? '' : $pcr;
1.44      ng       4149: 	    my $score;
                   4150: 	    if ($partial eq '') {
1.257     albertel 4151: 		$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
1.44      ng       4152: 	    } elsif ($partial > 0) {
                   4153: 		$score = 'correct_by_override';
                   4154: 	    } elsif ($partial == 0) {
                   4155: 		$score = 'incorrect_by_override';
                   4156: 	    }
1.257     albertel 4157: 	    my $dropMenu = $env{'form.GD_'.$user.'_'.$_.'_solved'};
1.125     ng       4158: 	    $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused'));
                   4159: 
1.292     albertel 4160: 	    $newrecord{'resource.'.$_.'.regrader'}=
                   4161: 		"$env{'user.name'}:$env{'user.domain'}";
1.125     ng       4162: 	    if ($dropMenu eq 'reset status' &&
                   4163: 		$old_score ne '') { # ignore if no previous attempts => nothing to reset
1.299     albertel 4164: 		$newrecord{'resource.'.$_.'.tries'} = '';
1.125     ng       4165: 		$newrecord{'resource.'.$_.'.solved'} = '';
                   4166: 		$newrecord{'resource.'.$_.'.award'} = '';
1.299     albertel 4167: 		$newrecord{'resource.'.$_.'.awarded'} = '';
1.125     ng       4168: 		$updateflag = 1;
1.269     raeburn  4169:                 if ($env{'form.GD_'.$user.'_'.$_.'_aggtries'} > 0) {
                   4170:                     my $aggtries = $env{'form.GD_'.$user.'_'.$_.'_aggtries'};
                   4171:                     my $totaltries = $env{'form.GD_'.$user.'_'.$_.'_totaltries'};
                   4172:                     my $solvedstatus = $env{'form.GD_'.$user.'_'.$_.'_solved_s'};
                   4173:                     &decrement_aggs($symb,$_,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                   4174:                     $aggregateflag = 1;
                   4175:                 }
1.139     albertel 4176: 	    } elsif (!($old_part eq $partial && $old_score eq $score)) {
                   4177: 		$updateflag = 1;
                   4178: 		$newrecord{'resource.'.$_.'.awarded'}  = $partial if $partial ne '';
                   4179: 		$newrecord{'resource.'.$_.'.solved'}   = $score;
                   4180: 		$rec_update++;
1.125     ng       4181: 	    }
                   4182: 
1.93      albertel 4183: 	    $line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
1.44      ng       4184: 		'<td align="center">'.$awarded.
                   4185: 		($score eq 'excused' ? $score : '').'&nbsp;</td>';
1.5       albertel 4186: 
1.54      albertel 4187: 
                   4188: 	    my $partid=$_;
                   4189: 	    foreach my $stores (@parts) {
                   4190: 		my ($part,$type) = &split_part_type($stores);
                   4191: 		if ($part !~ m/^\Q$partid\E/) { next;}
                   4192: 		if ($type eq 'awarded' || $type eq 'solved') { next; }
1.257     albertel 4193: 		my $old_aw    = $env{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'};
                   4194: 		my $awarded   = $env{'form.GD_'.$user.'_'.$part.'_'.$type};
1.54      albertel 4195: 		if ($awarded ne '' && $awarded ne $old_aw) {
                   4196: 		    $newrecord{'resource.'.$part.'.'.$type}= $awarded;
1.257     albertel 4197: 		    $newrecord{'resource.'.$part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
1.54      albertel 4198: 		    $updateflag=1;
                   4199: 		}
1.93      albertel 4200: 		$line .= '<td align="center">'.$old_aw.'&nbsp;</td>'.
1.54      albertel 4201: 		    '<td align="center">'.$awarded.'&nbsp;</td>';
                   4202: 	    }
1.44      ng       4203: 	}
1.477     albertel 4204: 	$line.="\n";
1.301     albertel 4205: 
                   4206: 	my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   4207: 	my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
                   4208: 
1.44      ng       4209: 	if ($updateflag) {
                   4210: 	    $count++;
1.257     albertel 4211: 	    &Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
1.89      albertel 4212: 				    $udom,$uname);
1.301     albertel 4213: 
                   4214: 	    if (&Apache::bridgetask::in_queue('gradingqueue',$symb,$cdom,
                   4215: 					      $cnum,$udom,$uname)) {
                   4216: 		# need to figure out if should be in queue.
                   4217: 		my %record =  
                   4218: 		    &Apache::lonnet::restore($symb,$env{'request.course.id'},
                   4219: 					     $udom,$uname);
                   4220: 		my $all_graded = 1;
                   4221: 		my $none_graded = 1;
                   4222: 		foreach my $part (@parts) {
                   4223: 		    if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
                   4224: 			$all_graded = 0;
                   4225: 		    } else {
                   4226: 			$none_graded = 0;
                   4227: 		    }
                   4228: 		}
                   4229: 
                   4230: 		if ($all_graded || $none_graded) {
                   4231: 		    &Apache::bridgetask::remove_from_queue('gradingqueue',
                   4232: 							   $symb,$cdom,$cnum,
                   4233: 							   $udom,$uname);
                   4234: 		}
                   4235: 	    }
                   4236: 
1.477     albertel 4237: 	    $result.=&Apache::loncommon::start_data_table_row().
                   4238: 		'<td align="right">&nbsp;'.$updateCtr.'&nbsp;</td>'.$line.
                   4239: 		&Apache::loncommon::end_data_table_row();
1.126     ng       4240: 	    $updateCtr++;
1.93      albertel 4241: 	} else {
1.477     albertel 4242: 	    push(@noupdate,
                   4243: 		 '<td align="right">&nbsp;'.$noupdateCtr.'&nbsp;</td>'.$line);
1.126     ng       4244: 	    $noupdateCtr++;
1.44      ng       4245: 	}
1.269     raeburn  4246:         if ($aggregateflag) {
                   4247:             &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
1.301     albertel 4248: 				  $cdom,$cnum);
1.269     raeburn  4249:         }
1.93      albertel 4250:     }
1.477     albertel 4251:     if (@noupdate) {
1.126     ng       4252: #	my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3;
                   4253: 	my $numcols=scalar(@partid)*4+2;
1.477     albertel 4254: 	$result .= &Apache::loncommon::start_data_table_row('LC_empty_row').
1.478     albertel 4255: 	    '<td align="center" colspan="'.$numcols.'">'.
                   4256: 	    &mt('No Changes Occurred For the Students Below').
                   4257: 	    '</td>'.
1.477     albertel 4258: 	    &Apache::loncommon::end_data_table_row();
                   4259: 	foreach my $line (@noupdate) {
                   4260: 	    $result.=
                   4261: 		&Apache::loncommon::start_data_table_row().
                   4262: 		$line.
                   4263: 		&Apache::loncommon::end_data_table_row();
                   4264: 	}
1.44      ng       4265:     }
1.614     www      4266:     $result .= &Apache::loncommon::end_data_table();
1.478     albertel 4267:     my $msg = '<p><b>'.
                   4268: 	&mt('Number of records updated = [_1] for [quant,_2,student].',
                   4269: 	    $rec_update,$count).'</b><br />'.
                   4270: 	'<b>'.&mt('Total number of students = [_1]',$env{'form.total'}).
                   4271: 	'</b></p>';
1.44      ng       4272:     return $title.$msg.$result;
1.5       albertel 4273: }
1.54      albertel 4274: 
                   4275: sub split_part_type {
                   4276:     my ($partstr) = @_;
                   4277:     my ($temp,@allparts)=split(/_/,$partstr);
                   4278:     my $type=pop(@allparts);
1.439     albertel 4279:     my $part=join('_',@allparts);
1.54      albertel 4280:     return ($part,$type);
                   4281: }
                   4282: 
1.44      ng       4283: #------------- end of section for handling grading by section/class ---------
                   4284: #
                   4285: #----------------------------------------------------------------------------
                   4286: 
1.5       albertel 4287: 
1.44      ng       4288: #----------------------------------------------------------------------------
                   4289: #
                   4290: #-------------------------- Next few routines handles grading by csv upload
                   4291: #
                   4292: #--- Javascript to handle csv upload
1.27      albertel 4293: sub csvupload_javascript_reverse_associate {
1.743     raeburn  4294:     my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
1.246     albertel 4295:     my $error2=&mt('You need to specify at least one grading field');
1.736     damieng  4296:   &js_escape(\$error1);
                   4297:   &js_escape(\$error2);
1.27      albertel 4298:   return(<<ENDPICK);
                   4299:   function verify(vf) {
                   4300:     var foundsomething=0;
                   4301:     var founduname=0;
1.243     albertel 4302:     var foundID=0;
1.743     raeburn  4303:     var foundclicker=0;
1.27      albertel 4304:     for (i=0;i<=vf.nfields.value;i++) {
                   4305:       tw=eval('vf.f'+i+'.selectedIndex');
1.243     albertel 4306:       if (i==0 && tw!=0) { foundID=1; }
                   4307:       if (i==1 && tw!=0) { founduname=1; }
1.743     raeburn  4308:       if (i==2 && tw!=0) { foundclicker=1; }
                   4309:       if (i!=0 && i!=1 && i!=2 && i!=3 && tw!=0) { foundsomething=1; }
1.27      albertel 4310:     }
1.743     raeburn  4311:     if (founduname==0 && foundID==0 && foundclicker==0) {
1.246     albertel 4312: 	alert('$error1');
                   4313: 	return;
1.27      albertel 4314:     }
                   4315:     if (foundsomething==0) {
1.246     albertel 4316: 	alert('$error2');
                   4317: 	return;
1.27      albertel 4318:     }
                   4319:     vf.submit();
                   4320:   }
                   4321:   function flip(vf,tf) {
                   4322:     var nw=eval('vf.f'+tf+'.selectedIndex');
                   4323:     var i;
                   4324:     for (i=0;i<=vf.nfields.value;i++) {
                   4325:       //can not pick the same destination field for both name and domain
                   4326:       if (((i ==0)||(i ==1)) && 
                   4327:           ((tf==0)||(tf==1)) && 
                   4328:           (i!=tf) &&
                   4329:           (eval('vf.f'+i+'.selectedIndex')==nw)) {
                   4330:         eval('vf.f'+i+'.selectedIndex=0;')
                   4331:       }
                   4332:     }
                   4333:   }
                   4334: ENDPICK
                   4335: }
                   4336: 
                   4337: sub csvupload_javascript_forward_associate {
1.743     raeburn  4338:     my $error1=&mt('You need to specify the username, the student/employee ID, or the clicker ID');
1.246     albertel 4339:     my $error2=&mt('You need to specify at least one grading field');
1.736     damieng  4340:   &js_escape(\$error1);
                   4341:   &js_escape(\$error2);
1.27      albertel 4342:   return(<<ENDPICK);
                   4343:   function verify(vf) {
                   4344:     var foundsomething=0;
                   4345:     var founduname=0;
1.243     albertel 4346:     var foundID=0;
1.743     raeburn  4347:     var foundclicker=0;
1.27      albertel 4348:     for (i=0;i<=vf.nfields.value;i++) {
                   4349:       tw=eval('vf.f'+i+'.selectedIndex');
1.243     albertel 4350:       if (tw==1) { foundID=1; }
                   4351:       if (tw==2) { founduname=1; }
1.745     raeburn  4352:       if (tw==3) { foundclicker=1; }
1.743     raeburn  4353:       if (tw>4) { foundsomething=1; }
1.27      albertel 4354:     }
1.743     raeburn  4355:     if (founduname==0 && foundID==0 && Æ’oundclicker==0) {
1.246     albertel 4356: 	alert('$error1');
                   4357: 	return;
1.27      albertel 4358:     }
                   4359:     if (foundsomething==0) {
1.246     albertel 4360: 	alert('$error2');
                   4361: 	return;
1.27      albertel 4362:     }
                   4363:     vf.submit();
                   4364:   }
                   4365:   function flip(vf,tf) {
                   4366:     var nw=eval('vf.f'+tf+'.selectedIndex');
                   4367:     var i;
                   4368:     //can not pick the same destination field twice
                   4369:     for (i=0;i<=vf.nfields.value;i++) {
                   4370:       if ((i!=tf) && (eval('vf.f'+i+'.selectedIndex')==nw)) {
                   4371:         eval('vf.f'+i+'.selectedIndex=0;')
                   4372:       }
                   4373:     }
                   4374:   }
                   4375: ENDPICK
                   4376: }
                   4377: 
1.26      albertel 4378: sub csvuploadmap_header {
1.324     albertel 4379:     my ($request,$symb,$datatoken,$distotal)= @_;
1.41      ng       4380:     my $javascript;
1.257     albertel 4381:     if ($env{'form.upfile_associate'} eq 'reverse') {
1.41      ng       4382: 	$javascript=&csvupload_javascript_reverse_associate();
                   4383:     } else {
                   4384: 	$javascript=&csvupload_javascript_forward_associate();
                   4385:     }
1.45      ng       4386: 
1.418     albertel 4387:     $symb = &Apache::lonenc::check_encrypt($symb);
1.632     www      4388:     $request->print('<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">'.
                   4389:                     &mt('Total number of records found in file: [_1]',$distotal).'<hr />'.
                   4390:                     &mt('Associate entries from the uploaded file with as many fields as you can.'));
                   4391:     my $reverse=&mt("Reverse Association");
1.41      ng       4392:     $request->print(<<ENDPICK);
1.632     www      4393: <br />
                   4394: <input type="button" value="$reverse" onclick="javascript:this.form.associate.value='Reverse Association';submit(this.form);" />
1.26      albertel 4395: <input type="hidden" name="associate"  value="" />
                   4396: <input type="hidden" name="phase"      value="three" />
                   4397: <input type="hidden" name="datatoken"  value="$datatoken" />
1.257     albertel 4398: <input type="hidden" name="fileupload" value="$env{'form.fileupload'}" />
                   4399: <input type="hidden" name="upfiletype" value="$env{'form.upfiletype'}" />
1.26      albertel 4400: <input type="hidden" name="upfile_associate" 
1.257     albertel 4401:                                        value="$env{'form.upfile_associate'}" />
1.26      albertel 4402: <input type="hidden" name="symb"       value="$symb" />
1.246     albertel 4403: <input type="hidden" name="command"    value="csvuploadoptions" />
1.26      albertel 4404: <hr />
                   4405: ENDPICK
1.597     wenzelju 4406:     $request->print(&Apache::lonhtmlcommon::scripttag($javascript));
1.118     ng       4407:     return '';
1.26      albertel 4408: 
                   4409: }
                   4410: 
                   4411: sub csvupload_fields {
1.582     raeburn  4412:     my ($symb,$errorref) = @_;
1.745     raeburn  4413:     my $toolsymb;
                   4414:     if ($symb =~ /ext\.tool$/) {
                   4415:         $toolsymb = $symb;
                   4416:     }
1.582     raeburn  4417:     my (@parts) = &getpartlist($symb,$errorref);
                   4418:     if (ref($errorref)) {
                   4419:         if ($$errorref) {
                   4420:             return;
                   4421:         }
                   4422:     }
                   4423: 
1.556     weissno  4424:     my @fields=(['ID','Student/Employee ID'],
1.243     albertel 4425: 		['username','Student Username'],
1.743     raeburn  4426: 		['clicker','Clicker ID'],
1.243     albertel 4427: 		['domain','Student Domain']);
1.324     albertel 4428:     my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
1.41      ng       4429:     foreach my $part (sort(@parts)) {
                   4430: 	my @datum;
1.745     raeburn  4431: 	my $display=&Apache::lonnet::metadata($url,$part.'.display',$toolsymb);
1.41      ng       4432: 	my $name=$part;
1.745     raeburn  4433: 	if (!$display) { $display = $name; }
1.41      ng       4434: 	@datum=($name,$display);
1.244     albertel 4435: 	if ($name=~/^stores_(.*)_awarded/) {
                   4436: 	    push(@fields,['stores_'.$1.'_points',"Points [Part: $1]"]);
                   4437: 	}
1.41      ng       4438: 	push(@fields,\@datum);
                   4439:     }
                   4440:     return (@fields);
1.26      albertel 4441: }
                   4442: 
                   4443: sub csvuploadmap_footer {
1.41      ng       4444:     my ($request,$i,$keyfields) =@_;
1.703     bisitz   4445:     my $buttontext = &mt('Assign Grades');
1.41      ng       4446:     $request->print(<<ENDPICK);
1.26      albertel 4447: </table>
                   4448: <input type="hidden" name="nfields" value="$i" />
                   4449: <input type="hidden" name="keyfields" value="$keyfields" />
1.703     bisitz   4450: <input type="button" onclick="javascript:verify(this.form)" value="$buttontext" /><br />
1.26      albertel 4451: </form>
                   4452: ENDPICK
                   4453: }
                   4454: 
1.283     albertel 4455: sub checkforfile_js {
1.638     www      4456:     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
1.736     damieng  4457:     &js_escape(\$alertmsg);
1.597     wenzelju 4458:     my $result = &Apache::lonhtmlcommon::scripttag(<<CSVFORMJS);
1.86      ng       4459:     function checkUpload(formname) {
                   4460: 	if (formname.upfile.value == "") {
1.539     riegler  4461: 	    alert("$alertmsg");
1.86      ng       4462: 	    return false;
                   4463: 	}
                   4464: 	formname.submit();
                   4465:     }
                   4466: CSVFORMJS
1.283     albertel 4467:     return $result;
                   4468: }
                   4469: 
                   4470: sub upcsvScores_form {
1.608     www      4471:     my ($request,$symb) = @_;
1.283     albertel 4472:     if (!$symb) {return '';}
                   4473:     my $result=&checkforfile_js();
1.632     www      4474:     $result.=&Apache::loncommon::start_data_table().
                   4475:              &Apache::loncommon::start_data_table_header_row().
                   4476:              '<th>'.&mt('Specify a file containing the class scores for current resource.').'</th>'.
                   4477:              &Apache::loncommon::end_data_table_header_row().
                   4478:              &Apache::loncommon::start_data_table_row().'<td>';
1.370     www      4479:     my $upload=&mt("Upload Scores");
1.86      ng       4480:     my $upfile_select=&Apache::loncommon::upfile_select_html();
1.245     albertel 4481:     my $ignore=&mt('Ignore First Line');
1.418     albertel 4482:     $symb = &Apache::lonenc::check_encrypt($symb);
1.86      ng       4483:     $result.=<<ENDUPFORM;
1.106     albertel 4484: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
1.86      ng       4485: <input type="hidden" name="symb" value="$symb" />
                   4486: <input type="hidden" name="command" value="csvuploadmap" />
                   4487: $upfile_select
1.589     bisitz   4488: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
1.86      ng       4489: </form>
                   4490: ENDUPFORM
1.370     www      4491:     $result.=&Apache::loncommon::help_open_topic("Course_Convert_To_CSV",
1.632     www      4492:                            &mt("How do I create a CSV file from a spreadsheet")).
                   4493:              '</td>'.
                   4494:             &Apache::loncommon::end_data_table_row().
                   4495:             &Apache::loncommon::end_data_table();
1.86      ng       4496:     return $result;
                   4497: }
                   4498: 
                   4499: 
1.26      albertel 4500: sub csvuploadmap {
1.608     www      4501:     my ($request,$symb)= @_;
1.41      ng       4502:     if (!$symb) {return '';}
1.72      ng       4503: 
1.41      ng       4504:     my $datatoken;
1.257     albertel 4505:     if (!$env{'form.datatoken'}) {
1.41      ng       4506: 	$datatoken=&Apache::loncommon::upfile_store($request);
1.26      albertel 4507:     } else {
1.742     raeburn  4508: 	$datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'});
                   4509:         if ($datatoken ne '') {
                   4510: 	    &Apache::loncommon::load_tmp_file($request,$datatoken);
                   4511:         }
1.26      albertel 4512:     }
1.41      ng       4513:     my @records=&Apache::loncommon::upfile_record_sep();
1.324     albertel 4514:     &csvuploadmap_header($request,$symb,$datatoken,$#records+1);
1.41      ng       4515:     my ($i,$keyfields);
                   4516:     if (@records) {
1.582     raeburn  4517:         my $fieldserror;
                   4518: 	my @fields=&csvupload_fields($symb,\$fieldserror);
                   4519:         if ($fieldserror) {
                   4520:             $request->print(&navmap_errormsg());
                   4521:             return;
                   4522:         }
1.257     albertel 4523: 	if ($env{'form.upfile_associate'} eq 'reverse') {	
1.41      ng       4524: 	    &Apache::loncommon::csv_print_samples($request,\@records);
                   4525: 	    $i=&Apache::loncommon::csv_print_select_table($request,\@records,
                   4526: 							  \@fields);
                   4527: 	    foreach (@fields) { $keyfields.=$_->[0].','; }
                   4528: 	    chop($keyfields);
                   4529: 	} else {
                   4530: 	    unshift(@fields,['none','']);
                   4531: 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
                   4532: 							    \@fields);
1.311     banghart 4533:             foreach my $rec (@records) {
                   4534:                 my %temp = &Apache::loncommon::record_sep($rec);
                   4535:                 if (%temp) {
                   4536:                     $keyfields=join(',',sort(keys(%temp)));
                   4537:                     last;
                   4538:                 }
                   4539:             }
1.41      ng       4540: 	}
                   4541:     }
                   4542:     &csvuploadmap_footer($request,$i,$keyfields);
1.72      ng       4543: 
1.41      ng       4544:     return '';
1.27      albertel 4545: }
                   4546: 
1.246     albertel 4547: sub csvuploadoptions {
1.608     www      4548:     my ($request,$symb)= @_;
1.632     www      4549:     my $overwrite=&mt('Overwrite any existing score');
1.246     albertel 4550:     $request->print(<<ENDPICK);
                   4551: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
                   4552: <input type="hidden" name="command"    value="csvuploadassign" />
                   4553: <p>
                   4554: <label>
                   4555:    <input type="checkbox" name="overwite_scores" checked="checked" />
1.632     www      4556:    $overwrite
1.246     albertel 4557: </label>
                   4558: </p>
                   4559: ENDPICK
                   4560:     my %fields=&get_fields();
                   4561:     if (!defined($fields{'domain'})) {
1.257     albertel 4562: 	my $domform = &Apache::loncommon::select_dom_form($env{'request.role.domain'},'default_domain');
1.632     www      4563: 	$request->print("\n<p>".&mt('Users are in domain: [_1]',$domform)."</p>\n");
1.246     albertel 4564:     }
1.257     albertel 4565:     foreach my $key (sort(keys(%env))) {
1.246     albertel 4566: 	if ($key !~ /^form\.(.*)$/) { next; }
                   4567: 	my $cleankey=$1;
                   4568: 	if ($cleankey eq 'command') { next; }
                   4569: 	$request->print('<input type="hidden" name="'.$cleankey.
1.257     albertel 4570: 			'"  value="'.$env{$key}.'" />'."\n");
1.246     albertel 4571:     }
                   4572:     # FIXME do a check for any duplicated user ids...
                   4573:     # FIXME do a check for any invalid user ids?...
1.703     bisitz   4574:     $request->print('<input type="submit" value="'.&mt('Assign Grades').'" /><br />
1.290     albertel 4575: <hr /></form>'."\n");
1.246     albertel 4576:     return '';
                   4577: }
                   4578: 
                   4579: sub get_fields {
                   4580:     my %fields;
1.257     albertel 4581:     my @keyfields = split(/\,/,$env{'form.keyfields'});
                   4582:     for (my $i=0; $i<=$env{'form.nfields'}; $i++) {
                   4583: 	if ($env{'form.upfile_associate'} eq 'reverse') {
                   4584: 	    if ($env{'form.f'.$i} ne 'none') {
                   4585: 		$fields{$keyfields[$i]}=$env{'form.f'.$i};
1.41      ng       4586: 	    }
                   4587: 	} else {
1.257     albertel 4588: 	    if ($env{'form.f'.$i} ne 'none') {
                   4589: 		$fields{$env{'form.f'.$i}}=$keyfields[$i];
1.41      ng       4590: 	    }
                   4591: 	}
1.27      albertel 4592:     }
1.246     albertel 4593:     return %fields;
                   4594: }
                   4595: 
                   4596: sub csvuploadassign {
1.608     www      4597:     my ($request,$symb)= @_;
1.246     albertel 4598:     if (!$symb) {return '';}
1.345     bowersj2 4599:     my $error_msg = '';
1.742     raeburn  4600:     my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
                   4601:     if ($datatoken ne '') { 
                   4602:         &Apache::loncommon::load_tmp_file($request,$datatoken);
                   4603:     }
1.246     albertel 4604:     my @gradedata = &Apache::loncommon::upfile_record_sep();
                   4605:     my %fields=&get_fields();
1.257     albertel 4606:     my $courseid=$env{'request.course.id'};
1.97      albertel 4607:     my ($classlist) = &getclasslist('all',0);
1.106     albertel 4608:     my @notallowed;
1.41      ng       4609:     my @skipped;
1.657     raeburn  4610:     my @warnings;
1.41      ng       4611:     my $countdone=0;
                   4612:     foreach my $grade (@gradedata) {
                   4613: 	my %entries=&Apache::loncommon::record_sep($grade);
1.246     albertel 4614: 	my $domain;
                   4615: 	if ($entries{$fields{'domain'}}) {
                   4616: 	    $domain=$entries{$fields{'domain'}};
                   4617: 	} else {
1.257     albertel 4618: 	    $domain=$env{'form.default_domain'};
1.246     albertel 4619: 	}
1.243     albertel 4620: 	$domain=~s/\s//g;
1.41      ng       4621: 	my $username=$entries{$fields{'username'}};
1.160     albertel 4622: 	$username=~s/\s//g;
1.243     albertel 4623: 	if (!$username) {
                   4624: 	    my $id=$entries{$fields{'ID'}};
1.247     albertel 4625: 	    $id=~s/\s//g;
1.737     raeburn  4626:             if ($id ne '') {
                   4627: 	        my %ids=&Apache::lonnet::idget($domain,[$id]);
                   4628: 	        $username=$ids{$id};
                   4629:             } else {
                   4630:                 if ($entries{$fields{'clicker'}}) {
                   4631:                     my $clicker = $entries{$fields{'clicker'}};
                   4632:                     $clicker=~s/\s//g;
                   4633:                     if ($clicker ne '') {
                   4634:                         my %clickers = &Apache::lonnet::idget($domain,[$clicker],'clickers');
                   4635:                         if ($clickers{$clicker} ne '') {  
                   4636:                             my $match = 0;
                   4637:                             my @inclass;
                   4638:                             foreach my $poss (split(/,/,$clickers{$clicker})) {
                   4639:                                 if (exists($$classlist{"$poss:$domain"})) {
                   4640:                                     $username = $poss;
                   4641:                                     push(@inclass,$poss);
                   4642:                                     $match ++;
                   4643:                                     
                   4644:                                 }
                   4645:                             }
                   4646:                             if ($match > 1) {
                   4647:                                 undef($username); 
                   4648:                                 $request->print('<p class="LC_warning">'.
                   4649:                                                 &mt('Score not saved for clicker: [_1] (matched multiple usernames: [_2])',
                   4650:                                                 $clicker,join(', ',@inclass)).'</p>');
                   4651:                             }
                   4652:                         }
                   4653:                     }
                   4654:                 }
                   4655:             }
1.243     albertel 4656: 	}
1.41      ng       4657: 	if (!exists($$classlist{"$username:$domain"})) {
1.247     albertel 4658: 	    my $id=$entries{$fields{'ID'}};
                   4659: 	    $id=~s/\s//g;
1.737     raeburn  4660:             my $clicker = $entries{$fields{'clicker'}};
                   4661:             $clicker=~s/\s//g;
                   4662:             if ($clicker) {
                   4663:                 push(@skipped,"$clicker:$domain");
                   4664: 	    } elsif ($id) {
1.247     albertel 4665: 		push(@skipped,"$id:$domain");
                   4666: 	    } else {
                   4667: 		push(@skipped,"$username:$domain");
                   4668: 	    }
1.41      ng       4669: 	    next;
                   4670: 	}
1.108     albertel 4671: 	my $usec=$classlist->{"$username:$domain"}[5];
1.106     albertel 4672: 	if (!&canmodify($usec)) {
                   4673: 	    push(@notallowed,"$username:$domain");
                   4674: 	    next;
                   4675: 	}
1.244     albertel 4676: 	my %points;
1.41      ng       4677: 	my %grades;
                   4678: 	foreach my $dest (keys(%fields)) {
1.244     albertel 4679: 	    if ($dest eq 'ID' || $dest eq 'username' ||
                   4680: 		$dest eq 'domain') { next; }
                   4681: 	    if ($entries{$fields{$dest}} =~ /^\s*$/) { next; }
                   4682: 	    if ($dest=~/stores_(.*)_points/) {
                   4683: 		my $part=$1;
                   4684: 		my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
                   4685: 					      $symb,$domain,$username);
1.345     bowersj2 4686:                 if ($wgt) {
                   4687:                     $entries{$fields{$dest}}=~s/\s//g;
                   4688:                     my $pcr=$entries{$fields{$dest}} / $wgt;
1.463     albertel 4689:                     my $award=($pcr == 0) ? 'incorrect_by_override'
                   4690:                                           : 'correct_by_override';
1.638     www      4691:                     if ($pcr>1) {
1.657     raeburn  4692:                        push(@warnings,&mt("[_1]: point value larger than weight","$username:$domain"));
1.638     www      4693:                     }
1.345     bowersj2 4694:                     $grades{"resource.$part.awarded"}=$pcr;
                   4695:                     $grades{"resource.$part.solved"}=$award;
                   4696:                     $points{$part}=1;
                   4697:                 } else {
                   4698:                     $error_msg = "<br />" .
                   4699:                         &mt("Some point values were assigned"
                   4700:                             ." for problems with a weight "
                   4701:                             ."of zero. These values were "
                   4702:                             ."ignored.");
                   4703:                 }
1.244     albertel 4704: 	    } else {
                   4705: 		if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
                   4706: 		if ($dest=~/stores_(.*)_solved/)  { if ($points{$1}) {next;} }
                   4707: 		my $store_key=$dest;
                   4708: 		$store_key=~s/^stores/resource/;
                   4709: 		$store_key=~s/_/\./g;
                   4710: 		$grades{$store_key}=$entries{$fields{$dest}};
                   4711: 	    }
1.41      ng       4712: 	}
1.508     www      4713: 	if (! %grades) { 
                   4714:            push(@skipped,&mt("[_1]: no data to save","$username:$domain")); 
                   4715:         } else {
                   4716: 	   $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
                   4717: 	   my $result=&Apache::lonnet::cstore(\%grades,$symb,
1.302     albertel 4718: 					   $env{'request.course.id'},
                   4719: 					   $domain,$username);
1.508     www      4720: 	   if ($result eq 'ok') {
1.627     www      4721: # Successfully stored
1.508     www      4722: 	      $request->print('.');
1.627     www      4723: # Remove from grading queue
                   4724:               &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
                   4725:                                              $env{'course.'.$env{'request.course.id'}.'.domain'},
                   4726:                                              $env{'course.'.$env{'request.course.id'}.'.num'},
                   4727:                                              $domain,$username);
                   4728:               $countdone++;
                   4729:            } else {
1.508     www      4730: 	      $request->print("<p><span class=\"LC_error\">".
                   4731:                               &mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
                   4732:                                   "$username:$domain",$result)."</span></p>");
                   4733: 	   }
                   4734: 	   $request->rflush();
                   4735:         }
1.41      ng       4736:     }
1.570     www      4737:     $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt("Saved scores for [quant,_1,student]",$countdone),$countdone==0));
1.657     raeburn  4738:     if (@warnings) {
                   4739:         $request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Warnings generated for the following saved scores:'),1).'<br />');
                   4740:         $request->print(join(', ',@warnings));
                   4741:     }
1.41      ng       4742:     if (@skipped) {
1.571     www      4743: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('No scores stored for the following username(s):'),1).'<br />');
                   4744:         $request->print(join(', ',@skipped));
1.106     albertel 4745:     }
                   4746:     if (@notallowed) {
1.571     www      4747: 	$request->print('<br />'.&Apache::lonhtmlcommon::confirm_success(&mt('Modification of scores not allowed for the following username(s):'),1).'<br />');
                   4748: 	$request->print(join(', ',@notallowed));
1.41      ng       4749:     }
1.106     albertel 4750:     $request->print("<br />\n");
1.345     bowersj2 4751:     return $error_msg;
1.26      albertel 4752: }
1.44      ng       4753: #------------- end of section for handling csv file upload ---------
                   4754: #
                   4755: #-------------------------------------------------------------------
                   4756: #
1.122     ng       4757: #-------------- Next few routines handle grading by page/sequence
1.72      ng       4758: #
                   4759: #--- Select a page/sequence and a student to grade
1.68      ng       4760: sub pickStudentPage {
1.608     www      4761:     my ($request,$symb) = @_;
1.68      ng       4762: 
1.539     riegler  4763:     my $alertmsg = &mt('Please select the student you wish to grade.');
1.736     damieng  4764:     &js_escape(\$alertmsg);
1.597     wenzelju 4765:     $request->print(&Apache::lonhtmlcommon::scripttag(<<LISTJAVASCRIPT));
1.68      ng       4766: 
                   4767: function checkPickOne(formname) {
1.76      ng       4768:     if (radioSelection(formname.student) == null) {
1.539     riegler  4769: 	alert("$alertmsg");
1.68      ng       4770: 	return;
                   4771:     }
1.125     ng       4772:     ptr = pullDownSelection(formname.selectpage);
                   4773:     formname.page.value = formname["page"+ptr].value;
                   4774:     formname.title.value = formname["title"+ptr].value;
1.68      ng       4775:     formname.submit();
                   4776: }
                   4777: 
                   4778: LISTJAVASCRIPT
1.118     ng       4779:     &commonJSfunctions($request);
1.608     www      4780: 
1.257     albertel 4781:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   4782:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   4783:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
1.68      ng       4784: 
1.398     albertel 4785:     my $result='<h3><span class="LC_info">&nbsp;'.
1.485     albertel 4786: 	&mt('Manual Grading by Page or Sequence').'</span></h3>';
1.68      ng       4787: 
1.80      ng       4788:     $result.='<form action="/adm/grades" method="post" name="displayPage">'."\n";
1.582     raeburn  4789:     my $map_error;
                   4790:     my ($titles,$symbx) = &getSymbMap($map_error);
                   4791:     if ($map_error) {
                   4792:         $request->print(&navmap_errormsg());
                   4793:         return; 
                   4794:     }
1.137     albertel 4795:     my ($curpage) =&Apache::lonnet::decode_symb($symb); 
                   4796: #    my ($curpage,$mapId) =&Apache::lonnet::decode_symb($symb); 
                   4797: #    my $type=($curpage =~ /\.(page|sequence)/);
1.700     bisitz   4798: 
                   4799:     # Collection of hidden fields
1.70      ng       4800:     my $ctr=0;
1.68      ng       4801:     foreach (@$titles) {
1.700     bisitz   4802:         my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
                   4803:         $result.='<input type="hidden" name="page'.$ctr.'" value="'.$$symbx{$_}.'" />'."\n";
                   4804:         $result.='<input type="hidden" name="title'.$ctr.'" value="'.$showtitle.'" />'."\n";
                   4805:         $ctr++;
1.68      ng       4806:     }
1.700     bisitz   4807:     $result.='<input type="hidden" name="page" />'."\n".
                   4808:         '<input type="hidden" name="title" />'."\n";
                   4809: 
                   4810:     $result.=&build_section_inputs();
                   4811:     my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
                   4812:     $result.='<input type="hidden" name="Status"  value="'.$stu_status.'" />'."\n".
                   4813: 	'<input type="hidden" name="command" value="displayPage" />'."\n".
                   4814: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
1.485     albertel 4815: 
1.700     bisitz   4816:     # Show grading options
                   4817:     $result.=&Apache::lonhtmlcommon::start_pick_box();
                   4818:     my $select = '<select name="selectpage">'."\n";
1.70      ng       4819:     $ctr=0;
                   4820:     foreach (@$titles) {
                   4821: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
1.700     bisitz   4822: 	$select.='<option value="'.$ctr.'"'.
                   4823: 	    ($$symbx{$_} =~ /$curpage$/ ? ' selected="selected"' : '').
                   4824: 	    '>'.$showtitle.'</option>'."\n";
1.70      ng       4825: 	$ctr++;
                   4826:     }
1.700     bisitz   4827:     $select.= '</select>';
1.68      ng       4828: 
1.700     bisitz   4829:     $result.=
                   4830:         &Apache::lonhtmlcommon::row_title(&mt('Problems from'))
                   4831:        .$select
                   4832:        .&Apache::lonhtmlcommon::row_closure();
                   4833: 
                   4834:     $result.=
                   4835:         &Apache::lonhtmlcommon::row_title(&mt('View Problem Text'))
                   4836:        .'<label><input type="radio" name="vProb" value="no"'
                   4837:            .' checked="checked" /> '.&mt('no').' </label>'."\n"
                   4838:        .'<label><input type="radio" name="vProb" value="yes" />'
                   4839:            .&mt('yes').'</label>'."\n"
                   4840:        .&Apache::lonhtmlcommon::row_closure();
                   4841: 
                   4842:     $result.=
                   4843:         &Apache::lonhtmlcommon::row_title(&mt('View Submissions'))
                   4844:        .'<label><input type="radio" name="lastSub" value="none" /> '
                   4845:            .&mt('none').' </label>'."\n"
                   4846:        .'<label><input type="radio" name="lastSub" value="datesub"'
                   4847:            .' checked="checked" /> '.&mt('all submissions').'</label>'."\n"
                   4848:        .'<label><input type="radio" name="lastSub" value="all" /> '
                   4849:            .&mt('all submissions with details').' </label>'
                   4850:        .&Apache::lonhtmlcommon::row_closure();
1.432     banghart 4851:     
1.700     bisitz   4852:     $result.=
                   4853:         &Apache::lonhtmlcommon::row_title(&mt('Use CODE'))
                   4854:        .'<input type="text" name="CODE" value="" />'
                   4855:        .&Apache::lonhtmlcommon::row_closure(1)
                   4856:        .&Apache::lonhtmlcommon::end_pick_box();
1.382     albertel 4857: 
1.700     bisitz   4858:     # Show list of students to select for grading
                   4859:     $result.='<br /><input type="button" '.
1.589     bisitz   4860:              'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /><br />'."\n";
1.72      ng       4861: 
1.68      ng       4862:     $request->print($result);
                   4863: 
1.485     albertel 4864:     my $studentTable.='&nbsp;<b>'.&mt('Select a student you wish to grade and then click on the Next button.').'</b><br />'.
1.484     albertel 4865: 	&Apache::loncommon::start_data_table().
                   4866: 	&Apache::loncommon::start_data_table_header_row().
1.485     albertel 4867: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
1.484     albertel 4868: 	'<th>'.&nameUserString('header').'</th>'.
1.485     albertel 4869: 	'<th align="right">&nbsp;'.&mt('No.').'</th>'.
1.484     albertel 4870: 	'<th>'.&nameUserString('header').'</th>'.
                   4871: 	&Apache::loncommon::end_data_table_header_row();
1.68      ng       4872:  
1.76      ng       4873:     my (undef,undef,$fullname) = &getclasslist($getsec,'1');
1.68      ng       4874:     my $ptr = 1;
1.294     albertel 4875:     foreach my $student (sort 
                   4876: 			 {
                   4877: 			     if (lc($$fullname{$a}) ne lc($$fullname{$b})) {
                   4878: 				 return (lc($$fullname{$a}) cmp lc($$fullname{$b}));
                   4879: 			     }
                   4880: 			     return $a cmp $b;
                   4881: 			 } (keys(%$fullname))) {
1.68      ng       4882: 	my ($uname,$udom) = split(/:/,$student);
1.484     albertel 4883: 	$studentTable.=($ptr%2==1 ? &Apache::loncommon::start_data_table_row()
                   4884:                                   : '</td>');
1.126     ng       4885: 	$studentTable.='<td align="right">'.$ptr.'&nbsp;</td>';
1.288     albertel 4886: 	$studentTable.='<td>&nbsp;<label><input type="radio" name="student" value="'.$student.'" /> '
                   4887: 	    .&nameUserString(undef,$$fullname{$student},$uname,$udom)."</label>\n";
1.484     albertel 4888: 	$studentTable.=
                   4889: 	    ($ptr%2 == 0 ? '</td>'.&Apache::loncommon::end_data_table_row() 
                   4890:                          : '');
1.68      ng       4891: 	$ptr++;
                   4892:     }
1.484     albertel 4893:     if ($ptr%2 == 0) {
                   4894: 	$studentTable.='</td><td>&nbsp;</td><td>&nbsp;</td>'.
                   4895: 	    &Apache::loncommon::end_data_table_row();
                   4896:     }
                   4897:     $studentTable.=&Apache::loncommon::end_data_table()."\n";
1.126     ng       4898:     $studentTable.='<input type="button" '.
1.589     bisitz   4899:                    'onclick="javascript:checkPickOne(this.form);" value="'.&mt('Next').' &rarr;" /></form>'."\n";
1.68      ng       4900: 
                   4901:     $request->print($studentTable);
                   4902: 
                   4903:     return '';
                   4904: }
                   4905: 
                   4906: sub getSymbMap {
1.582     raeburn  4907:     my ($map_error) = @_;
1.132     bowersj2 4908:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  4909:     unless (ref($navmap)) {
                   4910:         if (ref($map_error)) {
                   4911:             $$map_error = 'navmap';
                   4912:         }
                   4913:         return;
                   4914:     }
1.68      ng       4915:     my %symbx = ();
                   4916:     my @titles = ();
1.117     bowersj2 4917:     my $minder = 0;
                   4918: 
                   4919:     # Gather every sequence that has problems.
1.240     albertel 4920:     my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); },
                   4921: 					       1,0,1);
1.117     bowersj2 4922:     for my $sequence ($navmap->getById('0.0'), @sequences) {
1.745     raeburn  4923: 	if ($navmap->hasResource($sequence, sub { shift->is_gradable(); }, 0) ) {
1.381     albertel 4924: 	    my $title = $minder.'.'.
                   4925: 		&HTML::Entities::encode($sequence->compTitle(),'"\'&');
                   4926: 	    push(@titles, $title); # minder in case two titles are identical
                   4927: 	    $symbx{$title} = &HTML::Entities::encode($sequence->symb(),'"\'&');
1.117     bowersj2 4928: 	    $minder++;
1.241     albertel 4929: 	}
1.68      ng       4930:     }
                   4931:     return \@titles,\%symbx;
                   4932: }
                   4933: 
1.72      ng       4934: #
                   4935: #--- Displays a page/sequence w/wo problems, w/wo submissions
1.68      ng       4936: sub displayPage {
1.608     www      4937:     my ($request,$symb) = @_;
1.257     albertel 4938:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   4939:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   4940:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
                   4941:     my $pageTitle = $env{'form.page'};
1.103     albertel 4942:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
1.257     albertel 4943:     my ($uname,$udom) = split(/:/,$env{'form.student'});
                   4944:     my $usec=$classlist->{$env{'form.student'}}[5];
1.168     albertel 4945: 
                   4946:     #need to make sure we have the correct data for later EXT calls, 
                   4947:     #thus invalidate the cache
                   4948:     &Apache::lonnet::devalidatecourseresdata(
1.257     albertel 4949:                  $env{'course.'.$env{'request.course.id'}.'.num'},
                   4950:                  $env{'course.'.$env{'request.course.id'}.'.domain'});
1.168     albertel 4951:     &Apache::lonnet::clear_EXT_cache_status();
                   4952: 
1.103     albertel 4953:     if (!&canview($usec)) {
1.712     bisitz   4954:         $request->print(
                   4955:             '<span class="LC_warning">'.
                   4956:             &mt('Unable to view requested student. ([_1])',
                   4957:                     $env{'form.student'}).
                   4958:             '</span>');
                   4959:         return;
1.103     albertel 4960:     }
1.398     albertel 4961:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
1.485     albertel 4962:     $result.='<h3>&nbsp;'.&mt('Student: [_1]',&nameUserString(undef,$$fullname{$env{'form.student'}},$uname,$udom)).
1.129     ng       4963: 	'</h3>'."\n";
1.500     albertel 4964:     $env{'form.CODE'} = uc($env{'form.CODE'});
1.501     foxr     4965:     if (&Apache::lonnet::validCODE(uc($env{'form.CODE'}))) {
1.485     albertel 4966: 	$result.='<h3>&nbsp;'.&mt('CODE: [_1]',$env{'form.CODE'}).'</h3>'."\n";
1.382     albertel 4967:     } else {
                   4968: 	delete($env{'form.CODE'});
                   4969:     }
1.71      ng       4970:     &sub_page_js($request);
                   4971:     $request->print($result);
                   4972: 
1.132     bowersj2 4973:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  4974:     unless (ref($navmap)) {
                   4975:         $request->print(&navmap_errormsg());
                   4976:         return;
                   4977:     }
1.257     albertel 4978:     my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($env{'form.page'});
1.68      ng       4979:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
1.288     albertel 4980:     if (!$map) {
1.485     albertel 4981: 	$request->print('<span class="LC_warning">'.&mt('Unable to view requested sequence. ([_1])',$resUrl).'</span>');
1.288     albertel 4982: 	return; 
                   4983:     }
1.68      ng       4984:     my $iterator = $navmap->getIterator($map->map_start(),
                   4985: 					$map->map_finish());
                   4986: 
1.71      ng       4987:     my $studentTable='<form action="/adm/grades" method="post" name="gradePage">'."\n".
1.72      ng       4988: 	'<input type="hidden" name="command" value="gradeByPage" />'."\n".
1.257     albertel 4989: 	'<input type="hidden" name="fullname" value="'.$$fullname{$env{'form.student'}}.'" />'."\n".
                   4990: 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
1.72      ng       4991: 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
1.257     albertel 4992: 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
1.418     albertel 4993: 	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
1.613     www      4994: 	'<input type="hidden" name="overRideScore" value="no" />'."\n";
1.71      ng       4995: 
1.382     albertel 4996:     if (defined($env{'form.CODE'})) {
                   4997: 	$studentTable.=
                   4998: 	    '<input type="hidden" name="CODE" value="'.$env{'form.CODE'}.'" />'."\n";
                   4999:     }
1.381     albertel 5000:     my $checkIcon = '<img alt="'.&mt('Check Mark').
1.485     albertel 5001: 	'" src="'.&Apache::loncommon::lonhttpdurl($request->dir_config('lonIconsURL').'/check.gif').'" height="16" border="0" />';
1.71      ng       5002: 
1.594     bisitz   5003:     $studentTable.='&nbsp;<span class="LC_info">'.
                   5004:         &mt('Problems graded correct by the computer are marked with a [_1] symbol.',$checkIcon).
                   5005:         '</span>'."\n".
1.484     albertel 5006: 	&Apache::loncommon::start_data_table().
                   5007: 	&Apache::loncommon::start_data_table_header_row().
1.700     bisitz   5008: 	'<th>'.&mt('Prob.').'</th>'.
1.485     albertel 5009: 	'<th>&nbsp;'.($env{'form.vProb'} eq 'no' ? &mt('Title') : &mt('Problem Text')).'/'.&mt('Grade').'</th>'.
1.484     albertel 5010: 	&Apache::loncommon::end_data_table_header_row();
1.71      ng       5011: 
1.329     albertel 5012:     &Apache::lonxml::clear_problem_counter();
1.196     albertel 5013:     my ($depth,$question,$prob) = (1,1,1);
1.68      ng       5014:     $iterator->next(); # skip the first BEGIN_MAP
                   5015:     my $curRes = $iterator->next(); # for "current resource"
1.101     albertel 5016:     while ($depth > 0) {
1.68      ng       5017:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
1.100     bowersj2 5018:         if($curRes == $iterator->END_MAP) { $depth--; }
1.68      ng       5019: 
1.745     raeburn  5020:         if (ref($curRes) && $curRes->is_gradable()) {
1.91      albertel 5021: 	    my $parts = $curRes->parts();
1.68      ng       5022:             my $title = $curRes->compTitle();
1.71      ng       5023: 	    my $symbx = $curRes->symb();
1.746   ! raeburn  5024:             my $is_tool = ($symbx =~ /ext\.tool$/);
1.484     albertel 5025: 	    $studentTable.=
                   5026: 		&Apache::loncommon::start_data_table_row().
                   5027: 		'<td align="center" valign="top" >'.$prob.
1.485     albertel 5028: 		(scalar(@{$parts}) == 1 ? '' 
1.681     raeburn  5029: 		                        : '<br />('.&mt('[_1]parts',
                   5030: 							scalar(@{$parts}).'&nbsp;').')'
1.485     albertel 5031: 		 ).
                   5032: 		 '</td>';
1.71      ng       5033: 	    $studentTable.='<td valign="top">';
1.382     albertel 5034: 	    my %form = ('CODE' => $env{'form.CODE'},);
1.745     raeburn  5035:             unless ($is_tool) {
                   5036: 	        if ($env{'form.vProb'} eq 'yes' ) {
                   5037: 		    $studentTable.=&show_problem($request,$symbx,$uname,$udom,1,
                   5038: 					         undef,'both',\%form);
                   5039: 	        } else {
                   5040: 		    my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$env{'request.course.id'},%form);
                   5041: 		    $companswer =~ s|<form(.*?)>||g;
                   5042: 		    $companswer =~ s|</form>||g;
                   5043: #		    while ($companswer =~ /(<a href\=\"javascript:newWindow.*?Script Vars<\/a>)/s) { #<a href="javascript:newWindow</a>
                   5044: #		        $companswer =~ s/$1/ /ms;
                   5045: #		        $request->print('match='.$1."<br />\n");
                   5046: #		    }
                   5047: #		    $companswer =~ s|<table border=\"1\">|<table border=\"0\">|g;
                   5048: 		    $studentTable.='&nbsp;<b>'.$title.'</b>&nbsp;<br />&nbsp;<b>'.&mt('Correct answer').':</b><br />'.$companswer;
                   5049: 		}
1.71      ng       5050: 	    }
                   5051: 
1.257     albertel 5052: 	    my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
1.125     ng       5053: 
1.257     albertel 5054: 	    if ($env{'form.lastSub'} eq 'datesub') {
1.71      ng       5055: 		if ($record{'version'} eq '') {
1.745     raeburn  5056:                     my $msg = &mt('No recorded submission for this problem.');
                   5057:                     if ($is_tool) {
                   5058:                         $msg = &mt('No recorded transactions for this external tool');
                   5059:                     }
                   5060: 		    $studentTable.='<br />&nbsp;<span class="LC_warning">'.$msg.'</span><br />';
1.71      ng       5061: 		} else {
1.116     ng       5062: 		    my %responseType = ();
                   5063: 		    foreach my $partid (@{$parts}) {
1.147     albertel 5064: 			my @responseIds =$curRes->responseIds($partid);
                   5065: 			my @responseType =$curRes->responseType($partid);
                   5066: 			my %responseIds;
                   5067: 			for (my $i=0;$i<=$#responseIds;$i++) {
                   5068: 			    $responseIds{$responseIds[$i]}=$responseType[$i];
                   5069: 			}
                   5070: 			$responseType{$partid} = \%responseIds;
1.116     ng       5071: 		    }
1.148     albertel 5072: 		    $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom);
1.71      ng       5073: 		}
1.257     albertel 5074: 	    } elsif ($env{'form.lastSub'} eq 'all') {
                   5075: 		my $last = ($env{'form.lastSub'} eq 'last' ? 'last' : '');
1.726     raeburn  5076:                 my $identifier = (&canmodify($usec)? $prob : ''); 
1.71      ng       5077: 		$studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom,
1.257     albertel 5078: 									$env{'request.course.id'},
1.726     raeburn  5079: 									'','.submission',undef,
                   5080:                                                                         $usec,$identifier);
1.71      ng       5081:  
                   5082: 	    }
1.103     albertel 5083: 	    if (&canmodify($usec)) {
1.585     bisitz   5084:             $studentTable.=&gradeBox_start();
1.103     albertel 5085: 		foreach my $partid (@{$parts}) {
                   5086: 		    $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record);
                   5087: 		    $studentTable.='<input type="hidden" name="q_'.$question.'" value="'.$partid.'" />'."\n";
                   5088: 		    $question++;
                   5089: 		}
1.585     bisitz   5090:             $studentTable.=&gradeBox_end();
1.196     albertel 5091: 		$prob++;
1.71      ng       5092: 	    }
                   5093: 	    $studentTable.='</td></tr>';
1.68      ng       5094: 
1.103     albertel 5095: 	}
1.68      ng       5096:         $curRes = $iterator->next();
                   5097:     }
                   5098: 
1.589     bisitz   5099:     $studentTable.=
                   5100:         '</table>'."\n".
                   5101:         '<input type="button" value="'.&mt('Save').'" '.
                   5102:         'onclick="javascript:checkSubmitPage(this.form,'.$question.');" />'.
                   5103:         '</form>'."\n";
1.71      ng       5104:     $request->print($studentTable);
                   5105: 
                   5106:     return '';
1.119     ng       5107: }
                   5108: 
                   5109: sub displaySubByDates {
1.148     albertel 5110:     my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_;
1.224     albertel 5111:     my $isCODE=0;
1.335     albertel 5112:     my $isTask = ($symb =~/\.task$/);
1.224     albertel 5113:     if (exists($record->{'resource.CODE'})) { $isCODE=1; }
1.467     albertel 5114:     my $studentTable=&Apache::loncommon::start_data_table().
                   5115: 	&Apache::loncommon::start_data_table_header_row().
                   5116: 	'<th>'.&mt('Date/Time').'</th>'.
                   5117: 	($isCODE?'<th>'.&mt('CODE').'</th>':'').
1.671     raeburn  5118:         ($isTask?'<th>'.&mt('Version').'</th>':'').
1.467     albertel 5119: 	'<th>'.&mt('Submission').'</th>'.
                   5120: 	'<th>'.&mt('Status').'</th>'.
                   5121: 	&Apache::loncommon::end_data_table_header_row();
1.119     ng       5122:     my ($version);
                   5123:     my %mark;
1.148     albertel 5124:     my %orders;
1.119     ng       5125:     $mark{'correct_by_student'} = $checkIcon;
1.147     albertel 5126:     if (!exists($$record{'1:timestamp'})) {
1.539     riegler  5127: 	return '<br />&nbsp;<span class="LC_warning">'.&mt('Nothing submitted - no attempts.').'</span><br />';
1.147     albertel 5128:     }
1.335     albertel 5129: 
                   5130:     my $interaction;
1.525     raeburn  5131:     my $no_increment = 1;
1.735     raeburn  5132:     my (%lastrndseed,%lasttype);
1.119     ng       5133:     for ($version=1;$version<=$$record{'version'};$version++) {
1.467     albertel 5134: 	my $timestamp = 
                   5135: 	    &Apache::lonlocal::locallocaltime($$record{$version.':timestamp'});
1.335     albertel 5136: 	if (exists($$record{$version.':resource.0.version'})) {
                   5137: 	    $interaction = $$record{$version.':resource.0.version'};
                   5138: 	}
1.671     raeburn  5139:         if ($isTask && $env{'form.previousversion'}) {
                   5140:             next unless ($interaction == $env{'form.previousversion'});
                   5141:         }
1.335     albertel 5142: 	my $where = ($isTask ? "$version:resource.$interaction"
                   5143: 		             : "$version:resource");
1.467     albertel 5144: 	$studentTable.=&Apache::loncommon::start_data_table_row().
                   5145: 	    '<td>'.$timestamp.'</td>';
1.224     albertel 5146: 	if ($isCODE) {
                   5147: 	    $studentTable.='<td>'.$record->{$version.':resource.CODE'}.'</td>';
                   5148: 	}
1.671     raeburn  5149:         if ($isTask) {
                   5150:             $studentTable.='<td>'.$interaction.'</td>';
                   5151:         }
1.119     ng       5152: 	my @versionKeys = split(/\:/,$$record{$version.':keys'});
                   5153: 	my @displaySub = ();
                   5154: 	foreach my $partid (@{$parts}) {
1.640     raeburn  5155:             my ($hidden,$type);
                   5156:             $type = $$record{$version.':resource.'.$partid.'.type'};
                   5157:             if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
1.596     raeburn  5158:                 $hidden = 1;
                   5159:             }
1.335     albertel 5160: 	    my @matchKey = ($isTask ? sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys)
                   5161: 			            : sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
                   5162: 	    
1.122     ng       5163: #	    next if ($$record{"$version:resource.$partid.solved"} eq '');
1.324     albertel 5164: 	    my $display_part=&get_display_part($partid,$symb);
1.147     albertel 5165: 	    foreach my $matchKey (@matchKey) {
1.198     albertel 5166: 		if (exists($$record{$version.':'.$matchKey}) &&
                   5167: 		    $$record{$version.':'.$matchKey} ne '') {
1.596     raeburn  5168:                     
1.335     albertel 5169: 		    my ($responseId)= ($isTask ? ($matchKey=~ /^resource\.(.*?)\.\Q$partid\E\.award$/)
                   5170: 				               : ($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/));
1.670     raeburn  5171:                     $displaySub[0].='<span class="LC_nobreak">';
1.577     bisitz   5172:                     $displaySub[0].='<b>'.&mt('Part: [_1]',$display_part).'</b>'
                   5173:                                    .' <span class="LC_internal_info">'
1.625     www      5174:                                    .'('.&mt('Response ID: [_1]',$responseId).')'
1.577     bisitz   5175:                                    .'</span>'
                   5176:                                    .' <b>';
1.596     raeburn  5177:                     if ($hidden) {
                   5178:                         $displaySub[0].= &mt('Anonymous Survey').'</b>';
                   5179:                     } else {
1.640     raeburn  5180:                         my ($trial,$rndseed,$newvariation);
                   5181:                         if ($type eq 'randomizetry') {
                   5182:                             $trial = $$record{"$where.$partid.tries"};
1.733     raeburn  5183:                             $rndseed = $$record{"$where.$partid.rndseed"};
1.640     raeburn  5184:                         }
1.596     raeburn  5185: 		        if ($$record{"$where.$partid.tries"} eq '') {
                   5186: 			    $displaySub[0].=&mt('Trial not counted');
                   5187: 		        } else {
                   5188: 			    $displaySub[0].=&mt('Trial: [_1]',
1.467     albertel 5189: 					    $$record{"$where.$partid.tries"});
1.734     raeburn  5190:                             if (($rndseed ne '') && ($lastrndseed{$partid} ne '')) {
1.735     raeburn  5191:                                 if (($rndseed ne $lastrndseed{$partid}) &&
                   5192:                                     (($type eq 'randomizetry') || ($lasttype{$partid} eq 'randomizetry'))) {
1.640     raeburn  5193:                                     $newvariation = '&nbsp;('.&mt('New variation this try').')';
                   5194:                                 }
                   5195:                             }
                   5196:                             $lastrndseed{$partid} = $rndseed;
1.735     raeburn  5197:                             $lasttype{$partid} = $type;
1.596     raeburn  5198: 		        }
                   5199: 		        my $responseType=($isTask ? 'Task'
1.335     albertel 5200:                                               : $responseType->{$partid}->{$responseId});
1.596     raeburn  5201: 		        if (!exists($orders{$partid})) { $orders{$partid}={}; }
1.640     raeburn  5202: 		        if ((!exists($orders{$partid}->{$responseId})) || ($trial)) {
1.596     raeburn  5203: 			    $orders{$partid}->{$responseId}=
                   5204: 			        &get_order($partid,$responseId,$symb,$uname,$udom,
1.640     raeburn  5205:                                            $no_increment,$type,$trial,$rndseed);
1.596     raeburn  5206: 		        }
1.640     raeburn  5207: 		        $displaySub[0].='</b>'.$newvariation.'</span>'; # /nobreak
1.596     raeburn  5208: 		        $displaySub[0].='&nbsp; '.
1.640     raeburn  5209: 			    &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:",$uname,$udom,$type,$trial,$rndseed).'<br />';
1.596     raeburn  5210:                     }
1.147     albertel 5211: 		}
                   5212: 	    }
1.335     albertel 5213: 	    if (exists($$record{"$where.$partid.checkedin"})) {
1.485     albertel 5214: 		$displaySub[1].=&mt('Checked in by [_1] into slot [_2]',
                   5215: 				    $$record{"$where.$partid.checkedin"},
                   5216: 				    $$record{"$where.$partid.checkedin.slot"}).
                   5217: 					'<br />';
1.335     albertel 5218: 	    }
                   5219: 	    if (exists $$record{"$where.$partid.award"}) {
1.485     albertel 5220: 		$displaySub[1].='<b>'.&mt('Part:').'</b>&nbsp;'.$display_part.' &nbsp;'.
1.335     albertel 5221: 		    lc($$record{"$where.$partid.award"}).' '.
                   5222: 		    $mark{$$record{"$where.$partid.solved"}}.
1.147     albertel 5223: 		    '<br />';
                   5224: 	    }
1.335     albertel 5225: 	    if (exists $$record{"$where.$partid.regrader"}) {
                   5226: 		$displaySub[2].=$$record{"$where.$partid.regrader"}.
                   5227: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
                   5228: 	    } elsif ($$record{"$version:resource.$partid.regrader"} =~ /\S/) {
                   5229: 		$displaySub[2].=
                   5230: 		    $$record{"$version:resource.$partid.regrader"}.
1.207     albertel 5231: 		    ' (<b>'.&mt('Part').':</b> '.$display_part.')';
1.147     albertel 5232: 	    }
                   5233: 	}
                   5234: 	# needed because old essay regrader has not parts info
                   5235: 	if (exists $$record{"$version:resource.regrader"}) {
                   5236: 	    $displaySub[2].=$$record{"$version:resource.regrader"};
                   5237: 	}
                   5238: 	$studentTable.='<td>'.$displaySub[0].'&nbsp;</td><td>'.$displaySub[1];
                   5239: 	if ($displaySub[2]) {
1.467     albertel 5240: 	    $studentTable.=&mt('Manually graded by [_1]',$displaySub[2]);
1.147     albertel 5241: 	}
1.467     albertel 5242: 	$studentTable.='&nbsp;</td>'.
                   5243: 	    &Apache::loncommon::end_data_table_row();
1.119     ng       5244:     }
1.467     albertel 5245:     $studentTable.=&Apache::loncommon::end_data_table();
1.119     ng       5246:     return $studentTable;
1.71      ng       5247: }
                   5248: 
                   5249: sub updateGradeByPage {
1.608     www      5250:     my ($request,$symb) = @_;
1.71      ng       5251: 
1.257     albertel 5252:     my $cdom      = $env{"course.$env{'request.course.id'}.domain"};
                   5253:     my $cnum      = $env{"course.$env{'request.course.id'}.num"};
                   5254:     my $getsec    = $env{'form.section'} eq '' ? 'all' : $env{'form.section'};
                   5255:     my $pageTitle = $env{'form.page'};
1.103     albertel 5256:     my ($classlist,undef,$fullname) = &getclasslist($getsec,'1');
1.257     albertel 5257:     my ($uname,$udom) = split(/:/,$env{'form.student'});
                   5258:     my $usec=$classlist->{$env{'form.student'}}[5];
1.103     albertel 5259:     if (!&canmodify($usec)) {
1.526     raeburn  5260: 	$request->print('<span class="LC_warning">'.&mt('Unable to modify requested student ([_1])',$env{'form.student'}).'</span>');
1.103     albertel 5261: 	return;
                   5262:     }
1.398     albertel 5263:     my $result='<h3><span class="LC_info">&nbsp;'.$env{'form.title'}.'</span></h3>';
1.526     raeburn  5264:     $result.='<h3>&nbsp;'.&mt('Student: ').&nameUserString(undef,$env{'form.fullname'},$uname,$udom).
1.129     ng       5265: 	'</h3>'."\n";
1.70      ng       5266: 
1.68      ng       5267:     $request->print($result);
                   5268: 
1.582     raeburn  5269: 
1.132     bowersj2 5270:     my $navmap = Apache::lonnavmaps::navmap->new();
1.582     raeburn  5271:     unless (ref($navmap)) {
                   5272:         $request->print(&navmap_errormsg());
                   5273:         return;
                   5274:     }
1.257     albertel 5275:     my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $env{'form.page'});
1.71      ng       5276:     my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps
1.288     albertel 5277:     if (!$map) {
1.527     raeburn  5278: 	$request->print('<span class="LC_warning">'.&mt('Unable to grade requested sequence ([_1]).',$resUrl).'</span>');
1.288     albertel 5279: 	return; 
                   5280:     }
1.71      ng       5281:     my $iterator = $navmap->getIterator($map->map_start(),
                   5282: 					$map->map_finish());
1.70      ng       5283: 
1.484     albertel 5284:     my $studentTable=
                   5285: 	&Apache::loncommon::start_data_table().
                   5286: 	&Apache::loncommon::start_data_table_header_row().
1.485     albertel 5287: 	'<th align="center">&nbsp;'.&mt('Prob.').'&nbsp;</th>'.
                   5288: 	'<th>&nbsp;'.&mt('Title').'&nbsp;</th>'.
                   5289: 	'<th>&nbsp;'.&mt('Previous Score').'&nbsp;</th>'.
                   5290: 	'<th>&nbsp;'.&mt('New Score').'&nbsp;</th>'.
1.484     albertel 5291: 	&Apache::loncommon::end_data_table_header_row();
1.71      ng       5292: 
                   5293:     $iterator->next(); # skip the first BEGIN_MAP
                   5294:     my $curRes = $iterator->next(); # for "current resource"
1.726     raeburn  5295:     my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
1.101     albertel 5296:     while ($depth > 0) {
1.71      ng       5297:         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
1.100     bowersj2 5298:         if($curRes == $iterator->END_MAP) { $depth--; }
1.71      ng       5299: 
1.385     albertel 5300:         if (ref($curRes) && $curRes->is_problem()) {
1.91      albertel 5301: 	    my $parts = $curRes->parts();
1.71      ng       5302:             my $title = $curRes->compTitle();
                   5303: 	    my $symbx = $curRes->symb();
1.484     albertel 5304: 	    $studentTable.=
                   5305: 		&Apache::loncommon::start_data_table_row().
                   5306: 		'<td align="center" valign="top" >'.$prob.
1.485     albertel 5307: 		(scalar(@{$parts}) == 1 ? '' 
1.640     raeburn  5308:                                         : '<br />('.&mt('[quant,_1,part]',scalar(@{$parts}))
1.526     raeburn  5309: 		.')').'</td>';
1.71      ng       5310: 	    $studentTable.='<td valign="top">&nbsp;<b>'.$title.'</b>&nbsp;</td>';
                   5311: 
                   5312: 	    my %newrecord=();
                   5313: 	    my @displayPts=();
1.269     raeburn  5314:             my %aggregate = ();
                   5315:             my $aggregateflag = 0;
1.726     raeburn  5316:             if ($env{'form.HIDE'.$prob}) {
                   5317:                 my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
1.727     raeburn  5318:                 my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
1.728     raeburn  5319:                 my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
1.726     raeburn  5320:                 $hideflag += $numchgs;
                   5321:             }
1.71      ng       5322: 	    foreach my $partid (@{$parts}) {
1.257     albertel 5323: 		my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
                   5324: 		my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
1.71      ng       5325: 
1.257     albertel 5326: 		my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ? 
                   5327: 		    $env{'form.WGT'.$question.'_'.$partid} : 1;
1.71      ng       5328: 		my $partial = $newpts/$wgt;
                   5329: 		my $score;
                   5330: 		if ($partial > 0) {
                   5331: 		    $score = 'correct_by_override';
1.125     ng       5332: 		} elsif ($newpts ne '') { #empty is taken as 0
1.71      ng       5333: 		    $score = 'incorrect_by_override';
                   5334: 		}
1.257     albertel 5335: 		my $dropMenu = $env{'form.GD_SEL'.$question.'_'.$partid};
1.125     ng       5336: 		if ($dropMenu eq 'excused') {
1.71      ng       5337: 		    $partial = '';
                   5338: 		    $score = 'excused';
1.125     ng       5339: 		} elsif ($dropMenu eq 'reset status'
1.257     albertel 5340: 			 && $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
1.125     ng       5341: 		    $newrecord{'resource.'.$partid.'.tries'} = 0;
                   5342: 		    $newrecord{'resource.'.$partid.'.solved'} = '';
                   5343: 		    $newrecord{'resource.'.$partid.'.award'} = '';
                   5344: 		    $newrecord{'resource.'.$partid.'.awarded'} = 0;
1.257     albertel 5345: 		    $newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}";
1.125     ng       5346: 		    $changeflag++;
                   5347: 		    $newpts = '';
1.269     raeburn  5348:                     
                   5349:                     my $aggtries =  $env{'form.aggtries'.$question.'_'.$partid};
                   5350:                     my $totaltries = $env{'form.totaltries'.$question.'_'.$partid};
                   5351:                     my $solvedstatus = $env{'form.solved'.$question.'_'.$partid};
                   5352:                     if ($aggtries > 0) {
                   5353:                         &decrement_aggs($symbx,$partid,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                   5354:                         $aggregateflag = 1;
                   5355:                     }
1.71      ng       5356: 		}
1.324     albertel 5357: 		my $display_part=&get_display_part($partid,$curRes->symb());
1.257     albertel 5358: 		my $oldstatus = $env{'form.solved'.$question.'_'.$partid};
1.526     raeburn  5359: 		$displayPts[0].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
1.71      ng       5360: 		    (($oldstatus eq 'excused') ? 'excused' : $oldpts).
1.326     albertel 5361: 		    '&nbsp;<br />';
1.526     raeburn  5362: 		$displayPts[1].='&nbsp;<b>'.&mt('Part').':</b> '.$display_part.' = '.
1.125     ng       5363: 		     (($score eq 'excused') ? 'excused' : $newpts).
1.326     albertel 5364: 		    '&nbsp;<br />';
1.71      ng       5365: 		$question++;
1.380     albertel 5366: 		next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
1.125     ng       5367: 
1.71      ng       5368: 		$newrecord{'resource.'.$partid.'.awarded'}  = $partial if $partial ne '';
1.125     ng       5369: 		$newrecord{'resource.'.$partid.'.solved'}   = $score if $score ne '';
1.257     albertel 5370: 		$newrecord{'resource.'.$partid.'.regrader'} = "$env{'user.name'}:$env{'user.domain'}"
1.125     ng       5371: 		    if (scalar(keys(%newrecord)) > 0);
1.71      ng       5372: 
                   5373: 		$changeflag++;
                   5374: 	    }
                   5375: 	    if (scalar(keys(%newrecord)) > 0) {
1.382     albertel 5376: 		my %record = 
                   5377: 		    &Apache::lonnet::restore($symbx,$env{'request.course.id'},
                   5378: 					     $udom,$uname);
                   5379: 
                   5380: 		if (&Apache::lonnet::validCODE($env{'form.CODE'})) {
                   5381: 		    $newrecord{'resource.CODE'} = $env{'form.CODE'};
                   5382: 		} elsif (&Apache::lonnet::validCODE($record{'resource.CODE'})) {
                   5383: 		    $newrecord{'resource.CODE'} = '';
                   5384: 		}
1.257     albertel 5385: 		&Apache::lonnet::cstore(\%newrecord,$symbx,$env{'request.course.id'},
1.71      ng       5386: 					$udom,$uname);
1.382     albertel 5387: 		%record = &Apache::lonnet::restore($symbx,
                   5388: 						   $env{'request.course.id'},
                   5389: 						   $udom,$uname);
1.380     albertel 5390: 		&check_and_remove_from_queue($parts,\%record,undef,$symbx,
                   5391: 					     $cdom,$cnum,$udom,$uname);
1.71      ng       5392: 	    }
1.380     albertel 5393: 	    
1.269     raeburn  5394:             if ($aggregateflag) {
                   5395:                 &Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
                   5396:                       $env{'course.'.$env{'request.course.id'}.'.domain'},
                   5397:                       $env{'course.'.$env{'request.course.id'}.'.num'});
                   5398:             }
1.125     ng       5399: 
1.71      ng       5400: 	    $studentTable.='<td valign="top">'.$displayPts[0].'</td>'.
                   5401: 		'<td valign="top">'.$displayPts[1].'</td>'.
1.484     albertel 5402: 		&Apache::loncommon::end_data_table_row();
1.68      ng       5403: 
1.196     albertel 5404: 	    $prob++;
1.68      ng       5405: 	}
1.71      ng       5406:         $curRes = $iterator->next();
1.68      ng       5407:     }
1.98      albertel 5408: 
1.484     albertel 5409:     $studentTable.=&Apache::loncommon::end_data_table();
1.526     raeburn  5410:     my $grademsg=($changeflag == 0 ? &mt('No score was changed or updated.') :
                   5411: 		  &mt('The scores were changed for [quant,_1,problem].',
1.726     raeburn  5412: 		  $changeflag).'<br />');
                   5413:     my $hidemsg=($hideflag == 0 ? '' :
                   5414:                  &mt('Submissions were marked "hidden" for [quant,_1,transaction].',
                   5415:                      $hideflag).'<br />');
                   5416:     $request->print($hidemsg.$grademsg.$studentTable);
1.68      ng       5417: 
1.70      ng       5418:     return '';
                   5419: }
                   5420: 
1.72      ng       5421: #-------- end of section for handling grading by page/sequence ---------
                   5422: #
                   5423: #-------------------------------------------------------------------
                   5424: 
1.581     www      5425: #-------------------- Bubblesheet (Scantron) Grading -------------------
1.75      albertel 5426: #
                   5427: #------ start of section for handling grading by page/sequence ---------
                   5428: 
1.423     albertel 5429: =pod
                   5430: 
                   5431: =head1 Bubble sheet grading routines
                   5432: 
1.424     albertel 5433:   For this documentation:
                   5434: 
                   5435:    'scanline' refers to the full line of characters
                   5436:    from the file that we are parsing that represents one entire sheet
                   5437: 
                   5438:    'bubble line' refers to the data
1.659     raeburn  5439:    representing the line of bubbles that are on the physical bubblesheet
1.424     albertel 5440: 
                   5441: 
1.659     raeburn  5442: The overall process is that a scanned in bubblesheet data is uploaded
1.424     albertel 5443: into a course. When a user wants to grade, they select a
1.659     raeburn  5444: sequence/folder of resources, a file of bubblesheet info, and pick
1.424     albertel 5445: one of the predefined configurations for what each scanline looks
                   5446: like.
                   5447: 
                   5448: Next each scanline is checked for any errors of either 'missing
1.435     foxr     5449: bubbles' (it's an error because it may have been mis-scanned
1.424     albertel 5450: because too light bubbling), 'double bubble' (each bubble line should
1.703     bisitz   5451: have no more than one letter picked), invalid or duplicated CODE,
1.556     weissno  5452: invalid student/employee ID
1.424     albertel 5453: 
                   5454: If the CODE option is used that determines the randomization of the
1.556     weissno  5455: homework problems, either way the student/employee ID is looked up into a
1.424     albertel 5456: username:domain.
                   5457: 
                   5458: During the validation phase the instructor can choose to skip scanlines. 
                   5459: 
1.659     raeburn  5460: After the validation phase, there are now 3 bubblesheet files
1.424     albertel 5461: 
                   5462:   scantron_original_filename (unmodified original file)
                   5463:   scantron_corrected_filename (file where the corrected information has replaced the original information)
                   5464:   scantron_skipped_filename (contains the exact text of scanlines that where skipped)
                   5465: 
                   5466: Also there is a separate hash nohist_scantrondata that contains extra
1.659     raeburn  5467: correction information that isn't representable in the bubblesheet
1.424     albertel 5468: file (see &scantron_getfile() for more information)
                   5469: 
                   5470: After all scanlines are either valid, marked as valid or skipped, then
                   5471: foreach line foreach problem in the picked sequence, an ssi request is
                   5472: made that simulates a user submitting their selected letter(s) against
                   5473: the homework problem.
1.423     albertel 5474: 
                   5475: =over 4
                   5476: 
                   5477: 
                   5478: 
                   5479: =item defaultFormData
                   5480: 
                   5481:   Returns html hidden inputs used to hold context/default values.
                   5482: 
                   5483:  Arguments:
                   5484:   $symb - $symb of the current resource 
                   5485: 
                   5486: =cut
1.422     foxr     5487: 
1.81      albertel 5488: sub defaultFormData {
1.324     albertel 5489:     my ($symb)=@_;
1.613     www      5490:     return '<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />';
1.81      albertel 5491: }
                   5492: 
1.447     foxr     5493: 
1.423     albertel 5494: =pod 
                   5495: 
                   5496: =item getSequenceDropDown
                   5497: 
                   5498:    Return html dropdown of possible sequences to grade
                   5499:  
                   5500:  Arguments:
1.582     raeburn  5501:    $symb - $symb of the current resource
                   5502:    $map_error - ref to scalar which will container error if
                   5503:                 $navmap object is unavailable in &getSymbMap().
1.423     albertel 5504: 
                   5505: =cut
1.422     foxr     5506: 
1.75      albertel 5507: sub getSequenceDropDown {
1.582     raeburn  5508:     my ($symb,$map_error)=@_;
1.75      albertel 5509:     my $result='<select name="selectpage">'."\n";
1.582     raeburn  5510:     my ($titles,$symbx) = &getSymbMap($map_error);
                   5511:     if (ref($map_error)) {
                   5512:         return if ($$map_error);
                   5513:     }
1.137     albertel 5514:     my ($curpage)=&Apache::lonnet::decode_symb($symb); 
1.75      albertel 5515:     my $ctr=0;
                   5516:     foreach (@$titles) {
                   5517: 	my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/);
                   5518: 	$result.='<option value="'.$$symbx{$_}.'" '.
1.401     albertel 5519: 	    ($$symbx{$_} =~ /$curpage$/ ? 'selected="selected"' : '').
1.75      albertel 5520: 	    '>'.$showtitle.'</option>'."\n";
                   5521: 	$ctr++;
                   5522:     }
                   5523:     $result.= '</select>';
                   5524:     return $result;
                   5525: }
                   5526: 
1.495     albertel 5527: my %bubble_lines_per_response;     # no. bubble lines for each response.
1.554     raeburn  5528:                                    # key is zero-based index - 0, 1, 2 ...
1.495     albertel 5529: 
                   5530: my %first_bubble_line;             # First bubble line no. for each bubble.
                   5531: 
1.509     raeburn  5532: my %subdivided_bubble_lines;       # no. bubble lines for optionresponse, 
                   5533:                                    # matchresponse or rankresponse, where 
                   5534:                                    # an individual response can have multiple 
                   5535:                                    # lines
1.503     raeburn  5536: 
                   5537: my %responsetype_per_response;     # responsetype for each response
                   5538: 
1.691     raeburn  5539: my %masterseq_id_responsenum;      # src_id (e.g., 12.3_0.11 etc.) for each
                   5540:                                    # numbered response. Needed when randomorder
                   5541:                                    # or randompick are in use. Key is ID, value 
                   5542:                                    # is response number.
                   5543: 
1.495     albertel 5544: # Save and restore the bubble lines array to the form env.
                   5545: 
                   5546: 
                   5547: sub save_bubble_lines {
                   5548:     foreach my $line (keys(%bubble_lines_per_response)) {
                   5549: 	$env{"form.scantron.bubblelines.$line"}  = $bubble_lines_per_response{$line};
                   5550: 	$env{"form.scantron.first_bubble_line.$line"} =
                   5551: 	    $first_bubble_line{$line};
1.503     raeburn  5552:         $env{"form.scantron.sub_bubblelines.$line"} = 
                   5553:             $subdivided_bubble_lines{$line};
                   5554:         $env{"form.scantron.responsetype.$line"} =
                   5555:             $responsetype_per_response{$line};
1.495     albertel 5556:     }
1.691     raeburn  5557:     foreach my $resid (keys(%masterseq_id_responsenum)) {
                   5558:         my $line = $masterseq_id_responsenum{$resid};
                   5559:         $env{"form.scantron.residpart.$line"} = $resid;
                   5560:     }
1.495     albertel 5561: }
                   5562: 
                   5563: 
                   5564: sub restore_bubble_lines {
                   5565:     my $line = 0;
                   5566:     %bubble_lines_per_response = ();
1.691     raeburn  5567:     %masterseq_id_responsenum = ();
1.495     albertel 5568:     while ($env{"form.scantron.bubblelines.$line"}) {
                   5569: 	my $value = $env{"form.scantron.bubblelines.$line"};
                   5570: 	$bubble_lines_per_response{$line} = $value;
                   5571: 	$first_bubble_line{$line}  =
                   5572: 	    $env{"form.scantron.first_bubble_line.$line"};
1.503     raeburn  5573:         $subdivided_bubble_lines{$line} =
                   5574:             $env{"form.scantron.sub_bubblelines.$line"};
                   5575:         $responsetype_per_response{$line} =
                   5576:             $env{"form.scantron.responsetype.$line"};
1.691     raeburn  5577:         my $id = $env{"form.scantron.residpart.$line"};
                   5578:         $masterseq_id_responsenum{$id} = $line;
1.495     albertel 5579: 	$line++;
                   5580:     }
                   5581: }
                   5582: 
1.423     albertel 5583: =pod 
                   5584: 
                   5585: =item scantron_filenames
                   5586: 
                   5587:    Returns a list of the scantron files in the current course 
                   5588: 
                   5589: =cut
1.422     foxr     5590: 
1.202     albertel 5591: sub scantron_filenames {
1.257     albertel 5592:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   5593:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
1.517     raeburn  5594:     my $getpropath = 1;
1.662     raeburn  5595:     my ($dirlist,$listerror) = &Apache::lonnet::dirlist('userfiles',$cdom,
                   5596:                                                         $cname,$getpropath);
1.202     albertel 5597:     my @possiblenames;
1.662     raeburn  5598:     if (ref($dirlist) eq 'ARRAY') {
                   5599:         foreach my $filename (sort(@{$dirlist})) {
                   5600: 	    ($filename)=split(/&/,$filename);
                   5601: 	    if ($filename!~/^scantron_orig_/) { next ; }
                   5602: 	    $filename=~s/^scantron_orig_//;
                   5603: 	    push(@possiblenames,$filename);
                   5604:         }
1.202     albertel 5605:     }
                   5606:     return @possiblenames;
                   5607: }
                   5608: 
1.423     albertel 5609: =pod 
                   5610: 
                   5611: =item scantron_uploads
                   5612: 
                   5613:    Returns  html drop-down list of scantron files in current course.
                   5614: 
                   5615:  Arguments:
                   5616:    $file2grade - filename to set as selected in the dropdown
                   5617: 
                   5618: =cut
1.422     foxr     5619: 
1.202     albertel 5620: sub scantron_uploads {
1.209     ng       5621:     my ($file2grade) = @_;
1.202     albertel 5622:     my $result=	'<select name="scantron_selectfile">';
                   5623:     $result.="<option></option>";
                   5624:     foreach my $filename (sort(&scantron_filenames())) {
1.401     albertel 5625: 	$result.="<option".($filename eq $file2grade ? ' selected="selected"':'').">$filename</option>\n";
1.81      albertel 5626:     }
                   5627:     $result.="</select>";
                   5628:     return $result;
                   5629: }
                   5630: 
1.423     albertel 5631: =pod 
                   5632: 
                   5633: =item scantron_scantab
                   5634: 
                   5635:   Returns html drop down of the scantron formats in the scantronformat.tab
                   5636:   file.
                   5637: 
                   5638: =cut
1.422     foxr     5639: 
1.82      albertel 5640: sub scantron_scantab {
                   5641:     my $result='<select name="scantron_format">'."\n";
1.191     albertel 5642:     $result.='<option></option>'."\n";
1.518     raeburn  5643:     my @lines = &get_scantronformat_file();
                   5644:     if (@lines > 0) {
                   5645:         foreach my $line (@lines) {
                   5646:             next if (($line =~ /^\#/) || ($line eq ''));
                   5647: 	    my ($name,$descrip)=split(/:/,$line);
                   5648: 	    $result.='<option value="'.$name.'">'.$descrip.'</option>'."\n";
                   5649:         }
1.82      albertel 5650:     }
                   5651:     $result.='</select>'."\n";
1.518     raeburn  5652:     return $result;
                   5653: }
                   5654: 
                   5655: =pod
                   5656: 
                   5657: =item get_scantronformat_file
                   5658: 
                   5659:   Returns an array containing lines from the scantron format file for
                   5660:   the domain of the course.
                   5661: 
                   5662:   If a url for a custom.tab file is listed in domain's configuration.db, 
                   5663:   lines are from this file.
                   5664: 
                   5665:   Otherwise, if a default.tab has been published in RES space by the 
                   5666:   domainconfig user, lines are from this file.
                   5667: 
                   5668:   Otherwise, fall back to getting lines from the legacy file on the
1.519     raeburn  5669:   local server:  /home/httpd/lonTabs/default_scantronformat.tab    
1.82      albertel 5670: 
1.518     raeburn  5671: =cut
                   5672: 
                   5673: sub get_scantronformat_file {
                   5674:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5675:     my %domconfig = &Apache::lonnet::get_dom('configuration',['scantron'],$cdom);
                   5676:     my $gottab = 0;
                   5677:     my @lines;
                   5678:     if (ref($domconfig{'scantron'}) eq 'HASH') {
                   5679:         if ($domconfig{'scantron'}{'scantronformat'} ne '') {
                   5680:             my $formatfile = &Apache::lonnet::getfile($Apache::lonnet::perlvar{'lonDocRoot'}.$domconfig{'scantron'}{'scantronformat'});
                   5681:             if ($formatfile ne '-1') {
                   5682:                 @lines = split("\n",$formatfile,-1);
                   5683:                 $gottab = 1;
                   5684:             }
                   5685:         }
                   5686:     }
                   5687:     if (!$gottab) {
                   5688:         my $confname = $cdom.'-domainconfig';
                   5689:         my $default = $Apache::lonnet::perlvar{'lonDocRoot'}.'/res/'.$cdom.'/'.$confname.'/default.tab';
                   5690:         my $formatfile =  &Apache::lonnet::getfile($default);
                   5691:         if ($formatfile ne '-1') {
                   5692:             @lines = split("\n",$formatfile,-1);
                   5693:             $gottab = 1;
                   5694:         }
                   5695:     }
                   5696:     if (!$gottab) {
1.519     raeburn  5697:         my @domains = &Apache::lonnet::current_machine_domains();
                   5698:         if (grep(/^\Q$cdom\E$/,@domains)) {
                   5699:             my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
                   5700:             @lines = <$fh>;
                   5701:             close($fh);
                   5702:         } else {
                   5703:             my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/default_scantronformat.tab');
                   5704:             @lines = <$fh>;
                   5705:             close($fh);
                   5706:         }
1.518     raeburn  5707:     }
                   5708:     return @lines;
1.82      albertel 5709: }
                   5710: 
1.423     albertel 5711: =pod 
                   5712: 
                   5713: =item scantron_CODElist
                   5714: 
                   5715:   Returns html drop down of the saved CODE lists from current course,
                   5716:   generated from earlier printings.
                   5717: 
                   5718: =cut
1.422     foxr     5719: 
1.186     albertel 5720: sub scantron_CODElist {
1.257     albertel 5721:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5722:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
1.186     albertel 5723:     my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum);
                   5724:     my $namechoice='<option></option>';
1.225     albertel 5725:     foreach my $name (sort {uc($a) cmp uc($b)} @names) {
1.191     albertel 5726: 	if ($name =~ /^error: 2 /) { next; }
1.278     albertel 5727: 	if ($name =~ /^type\0/) { next; }
1.186     albertel 5728: 	$namechoice.='<option value="'.$name.'">'.$name.'</option>';
                   5729:     }
                   5730:     $namechoice='<select name="scantron_CODElist">'.$namechoice.'</select>';
                   5731:     return $namechoice;
                   5732: }
                   5733: 
1.423     albertel 5734: =pod 
                   5735: 
                   5736: =item scantron_CODEunique
                   5737: 
                   5738:   Returns the html for "Each CODE to be used once" radio.
                   5739: 
                   5740: =cut
1.422     foxr     5741: 
1.186     albertel 5742: sub scantron_CODEunique {
1.532     bisitz   5743:     my $result='<span class="LC_nobreak">
1.272     albertel 5744:                  <label><input type="radio" name="scantron_CODEunique"
1.423     albertel 5745:                         value="yes" checked="checked" />'.&mt('Yes').' </label>
1.381     albertel 5746:                 </span>
1.532     bisitz   5747:                 <span class="LC_nobreak">
1.272     albertel 5748:                  <label><input type="radio" name="scantron_CODEunique"
1.423     albertel 5749:                         value="no" />'.&mt('No').' </label>
1.381     albertel 5750:                 </span>';
1.186     albertel 5751:     return $result;
                   5752: }
1.423     albertel 5753: 
                   5754: =pod 
                   5755: 
                   5756: =item scantron_selectphase
                   5757: 
1.659     raeburn  5758:   Generates the initial screen to start the bubblesheet process.
1.423     albertel 5759:   Allows for - starting a grading run.
1.424     albertel 5760:              - downloading existing scan data (original, corrected
1.423     albertel 5761:                                                 or skipped info)
                   5762: 
                   5763:              - uploading new scan data
                   5764: 
                   5765:  Arguments:
                   5766:   $r          - The Apache request object
                   5767:   $file2grade - name of the file that contain the scanned data to score
                   5768: 
                   5769: =cut
1.186     albertel 5770: 
1.75      albertel 5771: sub scantron_selectphase {
1.608     www      5772:     my ($r,$file2grade,$symb) = @_;
1.75      albertel 5773:     if (!$symb) {return '';}
1.582     raeburn  5774:     my $map_error;
                   5775:     my $sequence_selector=&getSequenceDropDown($symb,\$map_error);
                   5776:     if ($map_error) {
                   5777:         $r->print('<br />'.&navmap_errormsg().'<br />');
                   5778:         return;
                   5779:     }
1.324     albertel 5780:     my $default_form_data=&defaultFormData($symb);
1.209     ng       5781:     my $file_selector=&scantron_uploads($file2grade);
1.82      albertel 5782:     my $format_selector=&scantron_scantab();
1.186     albertel 5783:     my $CODE_selector=&scantron_CODElist();
                   5784:     my $CODE_unique=&scantron_CODEunique();
1.75      albertel 5785:     my $result;
1.422     foxr     5786: 
1.513     foxr     5787:     $ssi_error = 0;
                   5788: 
1.606     wenzelju 5789:     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
                   5790:         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
                   5791: 
                   5792: 	# Chunk of form to prompt for a scantron file upload.
                   5793: 
                   5794:         $r->print('
                   5795:     <br />
                   5796:     '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5797:        '.&Apache::loncommon::start_data_table_header_row().'
                   5798:             <th>
                   5799:               &nbsp;'.&mt('Specify a bubblesheet data file to upload.').'
                   5800:             </th>
                   5801:        '.&Apache::loncommon::end_data_table_header_row().'
                   5802:        '.&Apache::loncommon::start_data_table_row().'
                   5803:             <td>
                   5804: ');
1.608     www      5805:     my $default_form_data=&defaultFormData($symb);
1.606     wenzelju 5806:     my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
                   5807:     my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
1.736     damieng  5808:     my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
                   5809:     &js_escape(\$alertmsg);
1.606     wenzelju 5810:     $r->print(&Apache::lonhtmlcommon::scripttag('
                   5811:     function checkUpload(formname) {
                   5812: 	if (formname.upfile.value == "") {
1.736     damieng  5813: 	    alert("'.$alertmsg.'");
1.606     wenzelju 5814: 	    return false;
                   5815: 	}
                   5816: 	formname.submit();
                   5817:     }'));
                   5818:     $r->print('
                   5819:               <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
                   5820:                 '.$default_form_data.'
                   5821:                 <input name="courseid" type="hidden" value="'.$cnum.'" />
                   5822:                 <input name="domainid" type="hidden" value="'.$cdom.'" />
                   5823:                 <input name="command" value="scantronupload_save" type="hidden" />
                   5824:                 '.&mt('File to upload: [_1]','<input type="file" name="upfile" size="50" />').'
                   5825:                 <br />
                   5826:                 <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
                   5827:               </form>
                   5828: ');
                   5829: 
                   5830:         $r->print('
                   5831:             </td>
                   5832:        '.&Apache::loncommon::end_data_table_row().'
                   5833:        '.&Apache::loncommon::end_data_table().'
                   5834: ');
                   5835:     }
                   5836: 
1.422     foxr     5837:     # Chunk of form to prompt for a file to grade and how:
                   5838: 
1.489     albertel 5839:     $result.= '
                   5840:     <br />
                   5841:     <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantron_process">
                   5842:     <input type="hidden" name="command" value="scantron_warning" />
                   5843:     '.$default_form_data.'
                   5844:     '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5845:        '.&Apache::loncommon::start_data_table_header_row().'
                   5846:             <th colspan="2">
1.492     albertel 5847:               &nbsp;'.&mt('Specify file and which Folder/Sequence to grade').'
1.489     albertel 5848:             </th>
                   5849:        '.&Apache::loncommon::end_data_table_header_row().'
                   5850:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5851:             <td> '.&mt('Sequence to grade:').' </td><td> '.$sequence_selector.' </td>
1.489     albertel 5852:        '.&Apache::loncommon::end_data_table_row().'
                   5853:        '.&Apache::loncommon::start_data_table_row().'
1.572     www      5854:             <td> '.&mt('Filename of bubblesheet data file:').' </td><td> '.$file_selector.' </td>
1.489     albertel 5855:        '.&Apache::loncommon::end_data_table_row().'
                   5856:        '.&Apache::loncommon::start_data_table_row().'
1.572     www      5857:             <td> '.&mt('Format of bubblesheet data file:').' </td><td> '.$format_selector.' </td>
1.489     albertel 5858:        '.&Apache::loncommon::end_data_table_row().'
                   5859:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5860:             <td> '.&mt('Saved CODEs to validate against:').' </td><td> '.$CODE_selector.' </td>
1.489     albertel 5861:        '.&Apache::loncommon::end_data_table_row().'
                   5862:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5863:             <td> '.&mt('Each CODE is only to be used once:').'</td><td> '.$CODE_unique.' </td>
1.489     albertel 5864:        '.&Apache::loncommon::end_data_table_row().'
                   5865:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5866: 	    <td> '.&mt('Options:').' </td>
1.187     albertel 5867:             <td>
1.492     albertel 5868: 	       <label><input type="checkbox" name="scantron_options_redo" value="redo_skipped"/> '.&mt('Do only previously skipped records').'</label> <br />
                   5869:                <label><input type="checkbox" name="scantron_options_ignore" value="ignore_corrections"/> '.&mt('Remove all existing corrections').'</label> <br />
                   5870:                <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources when grading').'</label>
1.187     albertel 5871: 	    </td>
1.489     albertel 5872:        '.&Apache::loncommon::end_data_table_row().'
                   5873:        '.&Apache::loncommon::start_data_table_row().'
1.174     albertel 5874:             <td colspan="2">
1.572     www      5875:               <input type="submit" value="'.&mt('Grading: Validate Bubblesheet Records').'" />
1.162     albertel 5876:             </td>
1.489     albertel 5877:        '.&Apache::loncommon::end_data_table_row().'
                   5878:     '.&Apache::loncommon::end_data_table().'
                   5879:     </form>
                   5880: ';
1.162     albertel 5881:    
                   5882:     $r->print($result);
                   5883: 
1.422     foxr     5884: 
                   5885: 
                   5886:     # Chunk of the form that prompts to view a scoring office file,
                   5887:     # corrected file, skipped records in a file.
                   5888: 
1.489     albertel 5889:     $r->print('
                   5890:    <br />
                   5891:    <form action="/adm/grades" name="scantron_download">
                   5892:      '.$default_form_data.'
                   5893:      <input type="hidden" name="command" value="scantron_download" />
                   5894:      '.&Apache::loncommon::start_data_table('LC_scantron_action').'
                   5895:        '.&Apache::loncommon::start_data_table_header_row().'
                   5896:               <th>
1.492     albertel 5897:                 &nbsp;'.&mt('Download a scoring office file').'
1.489     albertel 5898:               </th>
                   5899:        '.&Apache::loncommon::end_data_table_header_row().'
                   5900:        '.&Apache::loncommon::start_data_table_row().'
1.492     albertel 5901:               <td> '.&mt('Filename of scoring office file: [_1]',$file_selector).' 
1.489     albertel 5902:                 <br />
1.492     albertel 5903:                 <input type="submit" value="'.&mt('Download: Show List of Associated Files').'" />
1.489     albertel 5904:        '.&Apache::loncommon::end_data_table_row().'
                   5905:      '.&Apache::loncommon::end_data_table().'
                   5906:    </form>
                   5907:    <br />
                   5908: ');
1.162     albertel 5909: 
1.457     banghart 5910:     &Apache::lonpickcode::code_list($r,2);
1.523     raeburn  5911: 
1.694     bisitz   5912:     $r->print('<br /><form method="post" name="checkscantron" action="">'.
1.523     raeburn  5913:              $default_form_data."\n".
                   5914:              &Apache::loncommon::start_data_table('LC_scantron_action')."\n".
                   5915:              &Apache::loncommon::start_data_table_header_row()."\n".
                   5916:              '<th colspan="2">
1.572     www      5917:               &nbsp;'.&mt('Review bubblesheet data and submissions for a previously graded folder/sequence')."\n".
1.523     raeburn  5918:              '</th>'."\n".
                   5919:               &Apache::loncommon::end_data_table_header_row()."\n".
                   5920:               &Apache::loncommon::start_data_table_row()."\n".
                   5921:               '<td> '.&mt('Graded folder/sequence:').' </td>'."\n".
                   5922:               '<td> '.$sequence_selector.' </td>'.
                   5923:               &Apache::loncommon::end_data_table_row()."\n".
                   5924:               &Apache::loncommon::start_data_table_row()."\n".
                   5925:               '<td> '.&mt('Filename of scoring office file:').' </td>'."\n".
                   5926:               '<td> '.$file_selector.' </td>'."\n".
                   5927:               &Apache::loncommon::end_data_table_row()."\n".
                   5928:               &Apache::loncommon::start_data_table_row()."\n".
                   5929:               '<td> '.&mt('Format of data file:').' </td>'."\n".
                   5930:               '<td> '.$format_selector.' </td>'."\n".
                   5931:               &Apache::loncommon::end_data_table_row()."\n".
                   5932:               &Apache::loncommon::start_data_table_row()."\n".
1.557     raeburn  5933:               '<td> '.&mt('Options').' </td>'."\n".
                   5934:               '<td> <label><input type="checkbox" name="scantron_options_hidden" value="ignore_hidden"/> '.&mt('Skip hidden resources').'</label></td>'.
                   5935:               &Apache::loncommon::end_data_table_row()."\n".
                   5936:               &Apache::loncommon::start_data_table_row()."\n".
1.523     raeburn  5937:               '<td colspan="2">'."\n".
                   5938:               '<input type="hidden" name="command" value="checksubmissions" />'."\n".
1.575     www      5939:               '<input type="submit" value="'.&mt('Review Bubblesheet Data and Submission Records').'" />'."\n".
1.523     raeburn  5940:               '</td>'."\n".
                   5941:               &Apache::loncommon::end_data_table_row()."\n".
                   5942:               &Apache::loncommon::end_data_table()."\n".
                   5943:               '</form><br />');
                   5944:     return;
1.75      albertel 5945: }
                   5946: 
1.423     albertel 5947: =pod
                   5948: 
                   5949: =item get_scantron_config
                   5950: 
1.711     bisitz   5951:    Parse and return the bubblesheet configuration line selected as a
1.423     albertel 5952:    hash of configuration file fields.
                   5953: 
                   5954:  Arguments:
                   5955:     which - the name of the configuration to parse from the file.
                   5956: 
                   5957: 
                   5958:  Returns:
                   5959:             If the named configuration is not in the file, an empty
                   5960:             hash is returned.
                   5961:     a hash with the fields
                   5962:       name         - internal name for the this configuration setup
                   5963:       description  - text to display to operator that describes this config
                   5964:       CODElocation - if 0 or the string 'none'
                   5965:                           - no CODE exists for this config
                   5966:                      if -1 || the string 'letter'
                   5967:                           - a CODE exists for this config and is
                   5968:                             a string of letters
                   5969:                      Unsupported value (but planned for future support)
                   5970:                           if a positive integer
                   5971:                                - The CODE exists as the first n items from
                   5972:                                  the question section of the form
                   5973:                           if the string 'number'
                   5974:                                - The CODE exists for this config and is
                   5975:                                  a string of numbers
                   5976:       CODEstart   - (only matter if a CODE exists) column in the line where
                   5977:                      the CODE starts
                   5978:       CODElength  - length of the CODE
1.573     bisitz   5979:       IDstart     - column where the student/employee ID starts
1.556     weissno  5980:       IDlength    - length of the student/employee ID info
1.423     albertel 5981:       Qstart      - column where the information from the bubbled
                   5982:                     'questions' start
                   5983:       Qlength     - number of columns comprising a single bubble line from
                   5984:                     the sheet. (usually either 1 or 10)
1.424     albertel 5985:       Qon         - either a single character representing the character used
1.423     albertel 5986:                     to signal a bubble was chosen in the positional setup, or
                   5987:                     the string 'letter' if the letter of the chosen bubble is
                   5988:                     in the final, or 'number' if a number representing the
                   5989:                     chosen bubble is in the file (1->A 0->J)
1.424     albertel 5990:       Qoff        - the character used to represent that a bubble was
                   5991:                     left blank
1.423     albertel 5992:       PaperID     - if the scanning process generates a unique number for each
                   5993:                     sheet scanned the column that this ID number starts in
                   5994:       PaperIDlength - number of columns that comprise the unique ID number
                   5995:                       for the sheet of paper
1.424     albertel 5996:       FirstName   - column that the first name starts in
1.423     albertel 5997:       FirstNameLength - number of columns that the first name spans
                   5998:  
                   5999:       LastName    - column that the last name starts in
                   6000:       LastNameLength - number of columns that the last name spans
1.649     raeburn  6001:       BubblesPerRow - number of bubbles available in each row used to 
                   6002:                       bubble an answer. (If not specified, 10 assumed).
1.671     raeburn  6003: 
1.423     albertel 6004: =cut
1.422     foxr     6005: 
1.82      albertel 6006: sub get_scantron_config {
                   6007:     my ($which) = @_;
1.518     raeburn  6008:     my @lines = &get_scantronformat_file();
1.82      albertel 6009:     my %config;
1.157     albertel 6010:     #FIXME probably should move to XML it has already gotten a bit much now
1.518     raeburn  6011:     foreach my $line (@lines) {
1.82      albertel 6012: 	my ($name,$descrip)=split(/:/,$line);
                   6013: 	if ($name ne $which ) { next; }
                   6014: 	chomp($line);
                   6015: 	my @config=split(/:/,$line);
                   6016: 	$config{'name'}=$config[0];
                   6017: 	$config{'description'}=$config[1];
                   6018: 	$config{'CODElocation'}=$config[2];
                   6019: 	$config{'CODEstart'}=$config[3];
                   6020: 	$config{'CODElength'}=$config[4];
                   6021: 	$config{'IDstart'}=$config[5];
                   6022: 	$config{'IDlength'}=$config[6];
                   6023: 	$config{'Qstart'}=$config[7];
1.497     foxr     6024:  	$config{'Qlength'}=$config[8];
1.82      albertel 6025: 	$config{'Qoff'}=$config[9];
                   6026: 	$config{'Qon'}=$config[10];
1.157     albertel 6027: 	$config{'PaperID'}=$config[11];
                   6028: 	$config{'PaperIDlength'}=$config[12];
                   6029: 	$config{'FirstName'}=$config[13];
                   6030: 	$config{'FirstNamelength'}=$config[14];
                   6031: 	$config{'LastName'}=$config[15];
                   6032: 	$config{'LastNamelength'}=$config[16];
1.649     raeburn  6033:         $config{'BubblesPerRow'}=$config[17];
1.82      albertel 6034: 	last;
                   6035:     }
                   6036:     return %config;
                   6037: }
                   6038: 
1.423     albertel 6039: =pod 
                   6040: 
                   6041: =item username_to_idmap
                   6042: 
1.556     weissno  6043:     creates a hash keyed by student/employee ID with values of the corresponding
1.731     raeburn  6044:     student username:domain. If a single ID occurs for more than one student,
                   6045:     the status of the student is checked, and if Active, the value in the hash
                   6046:     will be set to the Active student.
1.423     albertel 6047: 
                   6048:   Arguments:
                   6049: 
                   6050:     $classlist - reference to the class list hash. This is a hash
                   6051:                  keyed by student name:domain  whose elements are references
1.424     albertel 6052:                  to arrays containing various chunks of information
1.423     albertel 6053:                  about the student. (See loncoursedata for more info).
                   6054: 
                   6055:   Returns
                   6056:     %idmap - the constructed hash
                   6057: 
                   6058: =cut
                   6059: 
1.82      albertel 6060: sub username_to_idmap {
                   6061:     my ($classlist)= @_;
                   6062:     my %idmap;
                   6063:     foreach my $student (keys(%$classlist)) {
1.731     raeburn  6064:         my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID];
                   6065:         unless ($id eq '') {
                   6066:             if (!exists($idmap{$id})) {
                   6067:                 $idmap{$id} = $student;
                   6068:             } else {
                   6069:                 my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS];
                   6070:                 if ($status eq 'Active') {
                   6071:                     $idmap{$id} = $student;
                   6072:                 }
                   6073:             }
                   6074:         }
1.82      albertel 6075:     }
                   6076:     return %idmap;
                   6077: }
1.423     albertel 6078: 
                   6079: =pod
                   6080: 
1.424     albertel 6081: =item scantron_fixup_scanline
1.423     albertel 6082: 
                   6083:    Process a requested correction to a scanline.
                   6084: 
                   6085:   Arguments:
                   6086:     $scantron_config   - hash from &get_scantron_config()
                   6087:     $scan_data         - hash of correction information 
                   6088:                           (see &scantron_getfile())
                   6089:     $line              - existing scanline
                   6090:     $whichline         - line number of the passed in scanline
                   6091:     $field             - type of change to process 
                   6092:                          (either 
1.573     bisitz   6093:                           'ID'     -> correct the student/employee ID
1.423     albertel 6094:                           'CODE'   -> correct the CODE
                   6095:                           'answer' -> fixup the submitted answers)
                   6096:     
                   6097:    $args               - hash of additional info,
                   6098:                           - 'ID' 
                   6099:                                'newid' -> studentID to use in replacement
1.424     albertel 6100:                                           of existing one
1.423     albertel 6101:                           - 'CODE' 
                   6102:                                'CODE_ignore_dup' - set to true if duplicates
                   6103:                                                    should be ignored.
                   6104: 	                       'CODE' - is new code or 'use_unfound'
1.424     albertel 6105:                                         if the existing unfound code should
1.423     albertel 6106:                                         be used as is
                   6107:                           - 'answer'
                   6108:                                'response' - new answer or 'none' if blank
                   6109:                                'question' - the bubble line to change
1.503     raeburn  6110:                                'questionnum' - the question identifier,
                   6111:                                                may include subquestion. 
1.423     albertel 6112: 
                   6113:   Returns:
                   6114:     $line - the modified scanline
                   6115: 
                   6116:   Side effects: 
                   6117:     $scan_data - may be updated
                   6118: 
                   6119: =cut
                   6120: 
1.82      albertel 6121: 
1.157     albertel 6122: sub scantron_fixup_scanline {
                   6123:     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
                   6124:     if ($field eq 'ID') {
                   6125: 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
1.186     albertel 6126: 	    return ($line,1,'New value too large');
1.157     albertel 6127: 	}
                   6128: 	if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) {
                   6129: 	    $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s',
                   6130: 				     $args->{'newid'});
                   6131: 	}
                   6132: 	substr($line,$$scantron_config{'IDstart'}-1,
                   6133: 	       $$scantron_config{'IDlength'})=$args->{'newid'};
                   6134: 	if ($args->{'newid'}=~/^\s*$/) {
                   6135: 	    &scan_data($scan_data,"$whichline.user",
                   6136: 		       $args->{'username'}.':'.$args->{'domain'});
                   6137: 	}
1.186     albertel 6138:     } elsif ($field eq 'CODE') {
1.192     albertel 6139: 	if ($args->{'CODE_ignore_dup'}) {
                   6140: 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
                   6141: 	}
                   6142: 	&scan_data($scan_data,"$whichline.useCODE",'1');
                   6143: 	if ($args->{'CODE'} ne 'use_unfound') {
1.191     albertel 6144: 	    if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) {
                   6145: 		return ($line,1,'New CODE value too large');
                   6146: 	    }
                   6147: 	    if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) {
                   6148: 		$args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'});
                   6149: 	    }
                   6150: 	    substr($line,$$scantron_config{'CODEstart'}-1,
                   6151: 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
1.186     albertel 6152: 	}
1.157     albertel 6153:     } elsif ($field eq 'answer') {
1.497     foxr     6154: 	my $length=$scantron_config->{'Qlength'};
1.157     albertel 6155: 	my $off=$scantron_config->{'Qoff'};
                   6156: 	my $on=$scantron_config->{'Qon'};
1.497     foxr     6157: 	my $answer=${off}x$length;
                   6158: 	if ($args->{'response'} eq 'none') {
                   6159: 	    &scan_data($scan_data,
1.503     raeburn  6160: 		       "$whichline.no_bubble.".$args->{'questionnum'},'1');
1.497     foxr     6161: 	} else {
                   6162: 	    if ($on eq 'letter') {
                   6163: 		my @alphabet=('A'..'Z');
                   6164: 		$answer=$alphabet[$args->{'response'}];
                   6165: 	    } elsif ($on eq 'number') {
                   6166: 		$answer=$args->{'response'}+1;
                   6167: 		if ($answer == 10) { $answer = '0'; }
1.274     albertel 6168: 	    } else {
1.497     foxr     6169: 		substr($answer,$args->{'response'},1)=$on;
1.274     albertel 6170: 	    }
1.497     foxr     6171: 	    &scan_data($scan_data,
1.503     raeburn  6172: 		       "$whichline.no_bubble.".$args->{'questionnum'},undef,'1');
1.157     albertel 6173: 	}
1.497     foxr     6174: 	my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'};
                   6175: 	substr($line,$where-1,$length)=$answer;
1.157     albertel 6176:     }
                   6177:     return $line;
                   6178: }
1.423     albertel 6179: 
                   6180: =pod
                   6181: 
                   6182: =item scan_data
                   6183: 
                   6184:     Edit or look up  an item in the scan_data hash.
                   6185: 
                   6186:   Arguments:
                   6187:     $scan_data  - The hash (see scantron_getfile)
                   6188:     $key        - shorthand of the key to edit (actual key is
1.424     albertel 6189:                   scantronfilename_key).
1.423     albertel 6190:     $data        - New value of the hash entry.
                   6191:     $delete      - If true, the entry is removed from the hash.
                   6192: 
                   6193:   Returns:
                   6194:     The new value of the hash table field (undefined if deleted).
                   6195: 
                   6196: =cut
                   6197: 
                   6198: 
1.157     albertel 6199: sub scan_data {
                   6200:     my ($scan_data,$key,$value,$delete)=@_;
1.257     albertel 6201:     my $filename=$env{'form.scantron_selectfile'};
1.157     albertel 6202:     if (defined($value)) {
                   6203: 	$scan_data->{$filename.'_'.$key} = $value;
                   6204:     }
                   6205:     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
                   6206:     return $scan_data->{$filename.'_'.$key};
                   6207: }
1.423     albertel 6208: 
1.495     albertel 6209: # ----- These first few routines are general use routines.----
                   6210: 
                   6211: # Return the number of occurences of a pattern in a string.
                   6212: 
                   6213: sub occurence_count {
                   6214:     my ($string, $pattern) = @_;
                   6215: 
                   6216:     my @matches = ($string =~ /$pattern/g);
                   6217: 
                   6218:     return scalar(@matches);
                   6219: }
                   6220: 
                   6221: 
                   6222: # Take a string known to have digits and convert all the
                   6223: # digits into letters in the range J,A..I.
                   6224: 
                   6225: sub digits_to_letters {
                   6226:     my ($input) = @_;
                   6227: 
                   6228:     my @alphabet = ('J', 'A'..'I');
                   6229: 
                   6230:     my @input    = split(//, $input);
                   6231:     my $output ='';
                   6232:     for (my $i = 0; $i < scalar(@input); $i++) {
                   6233: 	if ($input[$i] =~ /\d/) {
                   6234: 	    $output .= $alphabet[$input[$i]];
                   6235: 	} else {
                   6236: 	    $output .= $input[$i];
                   6237: 	}
                   6238:     }
                   6239:     return $output;
                   6240: }
                   6241: 
1.423     albertel 6242: =pod 
                   6243: 
                   6244: =item scantron_parse_scanline
                   6245: 
1.711     bisitz   6246:   Decodes a scanline from the selected bubblesheet file
1.423     albertel 6247: 
                   6248:  Arguments:
1.711     bisitz   6249:     line             - The text of the bubblesheet file line to process
1.423     albertel 6250:     whichline        - Line number
1.711     bisitz   6251:     scantron_config  - Hash describing the format of the bubblesheet lines.
1.423     albertel 6252:     scan_data        - Hash of extra information about the scanline
                   6253:                        (see scantron_getfile for more information)
                   6254:     just_header      - True if should not process question answers but only
                   6255:                        the stuff to the left of the answers.
1.691     raeburn  6256:     randomorder      - True if randomorder in use
                   6257:     randompick       - True if randompick in use
                   6258:     sequence         - Exam folder URL
                   6259:     master_seq       - Ref to array containing symbs in exam folder
                   6260:     symb_to_resource - Ref to hash of symbs for resources in exam folder
                   6261:                        (corresponding values are resource objects)
                   6262:     partids_by_symb  - Ref to hash of symb -> array ref of partIDs
                   6263:     orderedforcode   - Ref to hash of arrays. keys are CODEs and values
                   6264:                        are refs to an array of resource objects, ordered
                   6265:                        according to order used for CODE, when randomorder
                   6266:                        and or randompick are in use.
                   6267:     respnumlookup    - Ref to hash mapping question numbers in bubble lines
                   6268:                        for current line to question number used for same question
                   6269:                         in "Master Sequence" (as seen by Course Coordinator).
                   6270:     startline        - Ref to hash where key is question number (0 is first)
                   6271:                        and value is number of first bubble line for current 
                   6272:                        student or code-based randompick and/or randomorder.
                   6273:     totalref         - Ref of scalar used to score total number of bubble
                   6274:                        lines needed for responses in a scan line (used when
                   6275:                        randompick in use. 
                   6276:     
1.423     albertel 6277:  Returns:
                   6278:    Hash containing the result of parsing the scanline
                   6279: 
                   6280:    Keys are all proceeded by the string 'scantron.'
                   6281: 
                   6282:        CODE    - the CODE in use for this scanline
                   6283:        useCODE - 1 if the CODE is invalid but it usage has been forced
                   6284:                  by the operator
                   6285:        CODE_ignore_dup - 1 if the CODE is a duplicated use when unique
                   6286:                             CODEs were selected, but the usage has been
                   6287:                             forced by the operator
1.556     weissno  6288:        ID  - student/employee ID
1.423     albertel 6289:        PaperID - if used, the ID number printed on the sheet when the 
                   6290:                  paper was scanned
                   6291:        FirstName - first name from the sheet
                   6292:        LastName  - last name from the sheet
                   6293: 
                   6294:      if just_header was not true these key may also exist
                   6295: 
1.447     foxr     6296:        missingerror - a list of bubble ranges that are considered to be answers
                   6297:                       to a single question that don't have any bubbles filled in.
                   6298:                       Of the form questionnumber:firstbubblenumber:count.
                   6299:        doubleerror  - a list of bubble ranges that are considered to be answers
                   6300:                       to a single question that have more than one bubble filled in.
                   6301:                       Of the form questionnumber::firstbubblenumber:count
                   6302:    
                   6303:                 In the above, count is the number of bubble responses in the
                   6304:                 input line needed to represent the possible answers to the question.
                   6305:                 e.g. a radioresponse with 15 choices in an answer sheet with 10 choices
                   6306:                 per line would have count = 2.
                   6307: 
1.423     albertel 6308:        maxquest     - the number of the last bubble line that was parsed
                   6309: 
                   6310:        (<number> starts at 1)
                   6311:        <number>.answer - zero or more letters representing the selected
                   6312:                          letters from the scanline for the bubble line 
                   6313:                          <number>.
                   6314:                          if blank there was either no bubble or there where
                   6315:                          multiple bubbles, (consult the keys missingerror and
                   6316:                          doubleerror if this is an error condition)
                   6317: 
                   6318: =cut
                   6319: 
1.82      albertel 6320: sub scantron_parse_scanline {
1.691     raeburn  6321:     my ($line,$whichline,$scantron_config,$scan_data,$just_header,$idmap,
                   6322:         $randomorder,$randompick,$sequence,$master_seq,$symb_to_resource,
                   6323:         $partids_by_symb,$orderedforcode,$respnumlookup,$startline,$totalref)=@_;
1.470     foxr     6324: 
1.82      albertel 6325:     my %record;
1.691     raeburn  6326:     my $data=substr($line,0,$$scantron_config{'Qstart'}-1); # stuff before answers
1.278     albertel 6327:     if (!($$scantron_config{'CODElocation'} eq 0 ||
                   6328: 	  $$scantron_config{'CODElocation'} eq 'none')) {
                   6329: 	if ($$scantron_config{'CODElocation'} < 0 ||
                   6330: 	    $$scantron_config{'CODElocation'} eq 'letter' ||
                   6331: 	    $$scantron_config{'CODElocation'} eq 'number') {
1.191     albertel 6332: 	    $record{'scantron.CODE'}=substr($data,
                   6333: 					    $$scantron_config{'CODEstart'}-1,
1.83      albertel 6334: 					    $$scantron_config{'CODElength'});
1.191     albertel 6335: 	    if (&scan_data($scan_data,"$whichline.useCODE")) {
                   6336: 		$record{'scantron.useCODE'}=1;
                   6337: 	    }
1.192     albertel 6338: 	    if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) {
                   6339: 		$record{'scantron.CODE_ignore_dup'}=1;
                   6340: 	    }
1.82      albertel 6341: 	} else {
                   6342: 	    #FIXME interpret first N questions
                   6343: 	}
                   6344:     }
1.83      albertel 6345:     $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1,
                   6346: 				  $$scantron_config{'IDlength'});
1.157     albertel 6347:     $record{'scantron.PaperID'}=
                   6348: 	substr($data,$$scantron_config{'PaperID'}-1,
                   6349: 	       $$scantron_config{'PaperIDlength'});
                   6350:     $record{'scantron.FirstName'}=
                   6351: 	substr($data,$$scantron_config{'FirstName'}-1,
                   6352: 	       $$scantron_config{'FirstNamelength'});
                   6353:     $record{'scantron.LastName'}=
                   6354: 	substr($data,$$scantron_config{'LastName'}-1,
                   6355: 	       $$scantron_config{'LastNamelength'});
1.423     albertel 6356:     if ($just_header) { return \%record; }
1.194     albertel 6357: 
1.82      albertel 6358:     my @alphabet=('A'..'Z');
                   6359:     my $questnum=0;
1.447     foxr     6360:     my $ansnum  =1;		# Multiple 'answer lines'/question.
                   6361: 
1.691     raeburn  6362:     my $lastpos = $env{'form.scantron_maxbubble'}*$$scantron_config{'Qlength'};
                   6363:     if ($randompick || $randomorder) {
                   6364:         my $total = &get_respnum_lookups($sequence,$scan_data,$idmap,$line,\%record,
                   6365:                                          $master_seq,$symb_to_resource,
                   6366:                                          $partids_by_symb,$orderedforcode,
                   6367:                                          $respnumlookup,$startline);
                   6368:         if ($total) {
                   6369:             $lastpos = $total*$$scantron_config{'Qlength'}; 
                   6370:         }
                   6371:         if (ref($totalref)) {
                   6372:             $$totalref = $total;
                   6373:         }
                   6374:     }
                   6375:     my $questions=substr($line,$$scantron_config{'Qstart'}-1,$lastpos);  # Answers
1.470     foxr     6376:     chomp($questions);		# Get rid of any trailing \n.
                   6377:     $questions =~ s/\r$//;      # Get rid of trailing \r too (MAC or Win uploads).
                   6378:     while (length($questions)) {
1.691     raeburn  6379:         my $answers_needed;
                   6380:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6381:             $answers_needed = $bubble_lines_per_response{$respnumlookup->{$questnum}};
                   6382:         } else {
                   6383: 	    $answers_needed = $bubble_lines_per_response{$questnum};
                   6384:         }
1.503     raeburn  6385:         my $answer_length  = ($$scantron_config{'Qlength'} * $answers_needed)
                   6386:                              || 1;
                   6387:         $questnum++;
                   6388:         my $quest_id = $questnum;
                   6389:         my $currentquest = substr($questions,0,$answer_length);
                   6390:         $questions       = substr($questions,$answer_length);
                   6391:         if (length($currentquest) < $answer_length) { next; }
                   6392: 
1.691     raeburn  6393:         my $subdivided;
                   6394:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6395:             $subdivided = $subdivided_bubble_lines{$respnumlookup->{$questnum-1}};
                   6396:         } else {
                   6397:             $subdivided = $subdivided_bubble_lines{$questnum-1};
                   6398:         }
                   6399:         if ($subdivided =~ /,/) {
1.503     raeburn  6400:             my $subquestnum = 1;
                   6401:             my $subquestions = $currentquest;
1.691     raeburn  6402:             my @subanswers_needed = split(/,/,$subdivided);
1.503     raeburn  6403:             foreach my $subans (@subanswers_needed) {
                   6404:                 my $subans_length =
                   6405:                     ($$scantron_config{'Qlength'} * $subans)  || 1;
                   6406:                 my $currsubquest = substr($subquestions,0,$subans_length);
                   6407:                 $subquestions   = substr($subquestions,$subans_length);
                   6408:                 $quest_id = "$questnum.$subquestnum";
                   6409:                 if (($$scantron_config{'Qon'} eq 'letter') ||
                   6410:                     ($$scantron_config{'Qon'} eq 'number')) {
                   6411:                     $ansnum = &scantron_validator_lettnum($ansnum, 
                   6412:                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
1.691     raeburn  6413:                         \@alphabet,\%record,$scantron_config,$scan_data,
                   6414:                         $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6415:                 } else {
                   6416:                     $ansnum = &scantron_validator_positional($ansnum,
1.691     raeburn  6417:                         $questnum,$quest_id,$subans,$currsubquest,$whichline,
                   6418:                         \@alphabet,\%record,$scantron_config,$scan_data,
                   6419:                         $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6420:                 }
                   6421:                 $subquestnum ++;
                   6422:             }
                   6423:         } else {
                   6424:             if (($$scantron_config{'Qon'} eq 'letter') ||
                   6425:                 ($$scantron_config{'Qon'} eq 'number')) {
                   6426:                 $ansnum = &scantron_validator_lettnum($ansnum,$questnum,
                   6427:                     $quest_id,$answers_needed,$currentquest,$whichline,
1.691     raeburn  6428:                     \@alphabet,\%record,$scantron_config,$scan_data,
                   6429:                     $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6430:             } else {
                   6431:                 $ansnum = &scantron_validator_positional($ansnum,$questnum,
                   6432:                     $quest_id,$answers_needed,$currentquest,$whichline,
1.691     raeburn  6433:                     \@alphabet,\%record,$scantron_config,$scan_data,
                   6434:                     $randomorder,$randompick,$respnumlookup);
1.503     raeburn  6435:             }
                   6436:         }
                   6437:     }
                   6438:     $record{'scantron.maxquest'}=$questnum;
                   6439:     return \%record;
                   6440: }
1.447     foxr     6441: 
1.691     raeburn  6442: sub get_master_seq {
                   6443:     my ($resources,$master_seq,$symb_to_resource) = @_;
                   6444:     return unless ((ref($resources) eq 'ARRAY') && (ref($master_seq) eq 'ARRAY') && 
                   6445:                    (ref($symb_to_resource) eq 'HASH'));
                   6446:     my $resource_error;
                   6447:     foreach my $resource (@{$resources}) {
                   6448:         my $ressymb;
                   6449:         if (ref($resource)) {
                   6450:             $ressymb = $resource->symb();
                   6451:             push(@{$master_seq},$ressymb);
                   6452:             $symb_to_resource->{$ressymb} = $resource;
                   6453:         } else {
                   6454:             $resource_error = 1;
                   6455:             last;
                   6456:         }
                   6457:     }
                   6458:     return $resource_error;
                   6459: }
                   6460: 
                   6461: sub get_respnum_lookups {
                   6462:     my ($sequence,$scan_data,$idmap,$line,$record,$master_seq,$symb_to_resource,
                   6463:         $partids_by_symb,$orderedforcode,$respnumlookup,$startline) = @_;
                   6464:     return unless ((ref($record) eq 'HASH') && (ref($master_seq) eq 'ARRAY') &&
                   6465:                    (ref($symb_to_resource) eq 'HASH') && (ref($partids_by_symb) eq 'HASH') &&
                   6466:                    (ref($orderedforcode) eq 'HASH') && (ref($respnumlookup) eq 'HASH') &&
                   6467:                    (ref($startline) eq 'HASH'));
                   6468:     my ($user,$scancode);
                   6469:     if ((exists($record->{'scantron.CODE'})) &&
                   6470:         (&Apache::lonnet::validCODE($record->{'scantron.CODE'}))) {
                   6471:         $scancode = $record->{'scantron.CODE'};
                   6472:     } else {
                   6473:         $user = &scantron_find_student($record,$scan_data,$idmap,$line);
                   6474:     }
                   6475:     my @mapresources =
                   6476:         &users_order($user,$scancode,$sequence,$master_seq,$symb_to_resource,
                   6477:                      $orderedforcode);
                   6478:     my $total = 0;
                   6479:     my $count = 0;
                   6480:     foreach my $resource (@mapresources) {
                   6481:         my $id = $resource->id();
                   6482:         my $symb = $resource->symb();
                   6483:         if (ref($partids_by_symb->{$symb}) eq 'ARRAY') {
                   6484:             foreach my $partid (@{$partids_by_symb->{$symb}}) {
                   6485:                 my $respnum = $masterseq_id_responsenum{$id.'_'.$partid};
                   6486:                 if ($respnum ne '') {
                   6487:                     $respnumlookup->{$count} = $respnum;
                   6488:                     $startline->{$count} = $total;
                   6489:                     $total += $bubble_lines_per_response{$respnum};
                   6490:                     $count ++;
                   6491:                 }
                   6492:             }
                   6493:         }
                   6494:     }
                   6495:     return $total;
                   6496: }
                   6497: 
1.503     raeburn  6498: sub scantron_validator_lettnum {
                   6499:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,$whichline,
1.691     raeburn  6500:         $alphabet,$record,$scantron_config,$scan_data,$randomorder,
                   6501:         $randompick,$respnumlookup) = @_;
1.503     raeburn  6502: 
                   6503:     # Qon 'letter' implies for each slot in currquest we have:
                   6504:     #    ? or * for doubles, a letter in A-Z for a bubble, and
                   6505:     #    about anything else (esp. a value of Qoff) for missing
                   6506:     #    bubbles.
                   6507:     #
                   6508:     # Qon 'number' implies each slot gives a digit that indexes the
                   6509:     #    bubbles filled, or Qoff, or a non-number for unbubbled lines,
                   6510:     #    and * or ? for double bubbles on a single line.
                   6511:     #
1.447     foxr     6512: 
1.503     raeburn  6513:     my $matchon;
                   6514:     if ($$scantron_config{'Qon'} eq 'letter') {
                   6515:         $matchon = '[A-Z]';
                   6516:     } elsif ($$scantron_config{'Qon'} eq 'number') {
                   6517:         $matchon = '\d';
                   6518:     }
                   6519:     my $occurrences = 0;
1.691     raeburn  6520:     my $responsenum = $questnum-1;
                   6521:     if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6522:        $responsenum = $respnumlookup->{$questnum-1} 
                   6523:     }
                   6524:     if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   6525:         ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   6526:         ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   6527:         ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   6528:         ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   6529:         ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
1.503     raeburn  6530:         my @singlelines = split('',$currquest);
                   6531:         foreach my $entry (@singlelines) {
                   6532:             $occurrences = &occurence_count($entry,$matchon);
                   6533:             if ($occurrences > 1) {
                   6534:                 last;
                   6535:             }
1.691     raeburn  6536:         }
1.503     raeburn  6537:     } else {
                   6538:         $occurrences = &occurence_count($currquest,$matchon); 
                   6539:     }
                   6540:     if (($currquest =~ /\?/ || $currquest =~ /\*/) || ($occurrences > 1)) {
                   6541:         push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6542:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6543:             my $bubble = substr($currquest,$ans,1);
                   6544:             if ($bubble =~ /$matchon/ ) {
                   6545:                 if ($$scantron_config{'Qon'} eq 'number') {
                   6546:                     if ($bubble == 0) {
                   6547:                         $bubble = 10; 
                   6548:                     }
                   6549:                     $record->{"scantron.$ansnum.answer"} = 
                   6550:                         $alphabet->[$bubble-1];
                   6551:                 } else {
                   6552:                     $record->{"scantron.$ansnum.answer"} = $bubble;
                   6553:                 }
                   6554:             } else {
                   6555:                 $record->{"scantron.$ansnum.answer"}='';
                   6556:             }
                   6557:             $ansnum++;
                   6558:         }
                   6559:     } elsif (!defined($currquest)
                   6560:             || (&occurence_count($currquest, $$scantron_config{'Qoff'}) == length($currquest))
                   6561:             || (&occurence_count($currquest,$matchon) == 0)) {
                   6562:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
                   6563:             $record->{"scantron.$ansnum.answer"}='';
                   6564:             $ansnum++;
                   6565:         }
                   6566:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
                   6567:             push(@{$record->{'scantron.missingerror'}},$quest_id);
                   6568:         }
                   6569:     } else {
                   6570:         if ($$scantron_config{'Qon'} eq 'number') {
                   6571:             $currquest = &digits_to_letters($currquest);            
                   6572:         }
                   6573:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6574:             my $bubble = substr($currquest,$ans,1);
                   6575:             $record->{"scantron.$ansnum.answer"} = $bubble;
                   6576:             $ansnum++;
                   6577:         }
                   6578:     }
                   6579:     return $ansnum;
                   6580: }
1.447     foxr     6581: 
1.503     raeburn  6582: sub scantron_validator_positional {
                   6583:     my ($ansnum,$questnum,$quest_id,$answers_needed,$currquest,
1.691     raeburn  6584:         $whichline,$alphabet,$record,$scantron_config,$scan_data,
                   6585:         $randomorder,$randompick,$respnumlookup) = @_;
1.447     foxr     6586: 
1.503     raeburn  6587:     # Otherwise there's a positional notation;
                   6588:     # each bubble line requires Qlength items, and there are filled in
                   6589:     # bubbles for each case where there 'Qon' characters.
                   6590:     #
1.447     foxr     6591: 
1.503     raeburn  6592:     my @array=split($$scantron_config{'Qon'},$currquest,-1);
1.447     foxr     6593: 
1.503     raeburn  6594:     # If the split only gives us one element.. the full length of the
                   6595:     # answer string, no bubbles are filled in:
1.447     foxr     6596: 
1.507     raeburn  6597:     if ($answers_needed eq '') {
                   6598:         return;
                   6599:     }
                   6600: 
1.503     raeburn  6601:     if (length($array[0]) eq $$scantron_config{'Qlength'}*$answers_needed) {
                   6602:         for (my $ans=0; $ans<$answers_needed; $ans++ ) {
                   6603:             $record->{"scantron.$ansnum.answer"}='';
                   6604:             $ansnum++;
                   6605:         }
                   6606:         if (!&scan_data($scan_data,"$whichline.no_bubble.$quest_id")) {
                   6607:             push(@{$record->{"scantron.missingerror"}},$quest_id);
                   6608:         }
                   6609:     } elsif (scalar(@array) == 2) {
                   6610:         my $location = length($array[0]);
                   6611:         my $line_num = int($location / $$scantron_config{'Qlength'});
                   6612:         my $bubble   = $alphabet->[$location % $$scantron_config{'Qlength'}];
                   6613:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6614:             if ($ans eq $line_num) {
                   6615:                 $record->{"scantron.$ansnum.answer"} = $bubble;
                   6616:             } else {
                   6617:                 $record->{"scantron.$ansnum.answer"} = ' ';
                   6618:             }
                   6619:             $ansnum++;
                   6620:          }
                   6621:     } else {
                   6622:         #  If there's more than one instance of a bubble character
                   6623:         #  That's a double bubble; with positional notation we can
                   6624:         #  record all the bubbles filled in as well as the
                   6625:         #  fact this response consists of multiple bubbles.
                   6626:         #
1.691     raeburn  6627:         my $responsenum = $questnum-1;
                   6628:         if (($randompick || $randomorder) && (ref($respnumlookup) eq 'HASH')) {
                   6629:             $responsenum = $respnumlookup->{$questnum-1}
                   6630:         }
                   6631:         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   6632:             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   6633:             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   6634:             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   6635:             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   6636:             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
1.503     raeburn  6637:             my $doubleerror = 0;
                   6638:             while (($currquest >= $$scantron_config{'Qlength'}) && 
                   6639:                    (!$doubleerror)) {
                   6640:                my $currline = substr($currquest,0,$$scantron_config{'Qlength'});
                   6641:                $currquest = substr($currquest,$$scantron_config{'Qlength'});
                   6642:                my @currarray = split($$scantron_config{'Qon'},$currline,-1);
                   6643:                if (length(@currarray) > 2) {
                   6644:                    $doubleerror = 1;
                   6645:                } 
                   6646:             }
                   6647:             if ($doubleerror) {
                   6648:                 push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6649:             }
                   6650:         } else {
                   6651:             push(@{$record->{'scantron.doubleerror'}},$quest_id);
                   6652:         }
                   6653:         my $item = $ansnum;
                   6654:         for (my $ans=0; $ans<$answers_needed; $ans++) {
                   6655:             $record->{"scantron.$item.answer"} = '';
                   6656:             $item ++;
                   6657:         }
1.447     foxr     6658: 
1.503     raeburn  6659:         my @ans=@array;
                   6660:         my $i=0;
                   6661:         my $increment = 0;
                   6662:         while ($#ans) {
                   6663:             $i+=length($ans[0]) + $increment;
                   6664:             my $line   = int($i/$$scantron_config{'Qlength'} + $ansnum);
                   6665:             my $bubble = $i%$$scantron_config{'Qlength'};
                   6666:             $record->{"scantron.$line.answer"}.=$alphabet->[$bubble];
                   6667:             shift(@ans);
                   6668:             $increment = 1;
                   6669:         }
                   6670:         $ansnum += $answers_needed;
1.82      albertel 6671:     }
1.503     raeburn  6672:     return $ansnum;
1.82      albertel 6673: }
                   6674: 
1.423     albertel 6675: =pod
                   6676: 
                   6677: =item scantron_add_delay
                   6678: 
                   6679:    Adds an error message that occurred during the grading phase to a
                   6680:    queue of messages to be shown after grading pass is complete
                   6681: 
                   6682:  Arguments:
1.424     albertel 6683:    $delayqueue  - arrary ref of hash ref of error messages
1.423     albertel 6684:    $scanline    - the scanline that caused the error
                   6685:    $errormesage - the error message
                   6686:    $errorcode   - a numeric code for the error
                   6687: 
                   6688:  Side Effects:
1.424     albertel 6689:    updates the $delayqueue to have a new hash ref of the error
1.423     albertel 6690: 
                   6691: =cut
                   6692: 
1.82      albertel 6693: sub scantron_add_delay {
1.140     albertel 6694:     my ($delayqueue,$scanline,$errormessage,$errorcode)=@_;
                   6695:     push(@$delayqueue,
                   6696: 	 {'line' => $scanline, 'emsg' => $errormessage,
                   6697: 	  'ecode' => $errorcode }
                   6698: 	 );
1.82      albertel 6699: }
                   6700: 
1.423     albertel 6701: =pod
                   6702: 
                   6703: =item scantron_find_student
                   6704: 
1.424     albertel 6705:    Finds the username for the current scanline
                   6706: 
                   6707:   Arguments:
                   6708:    $scantron_record - hash result from scantron_parse_scanline
                   6709:    $scan_data       - hash of correction information 
                   6710:                       (see &scantron_getfile() form more information)
                   6711:    $idmap           - hash from &username_to_idmap()
                   6712:    $line            - number of current scanline
                   6713:  
                   6714:   Returns:
                   6715:    Either 'username:domain' or undef if unknown
                   6716: 
1.423     albertel 6717: =cut
                   6718: 
1.82      albertel 6719: sub scantron_find_student {
1.157     albertel 6720:     my ($scantron_record,$scan_data,$idmap,$line)=@_;
1.83      albertel 6721:     my $scanID=$$scantron_record{'scantron.ID'};
1.157     albertel 6722:     if ($scanID =~ /^\s*$/) {
                   6723:  	return &scan_data($scan_data,"$line.user");
                   6724:     }
1.83      albertel 6725:     foreach my $id (keys(%$idmap)) {
1.157     albertel 6726:  	if (lc($id) eq lc($scanID)) {
                   6727:  	    return $$idmap{$id};
                   6728:  	}
1.83      albertel 6729:     }
                   6730:     return undef;
                   6731: }
                   6732: 
1.423     albertel 6733: =pod
                   6734: 
                   6735: =item scantron_filter
                   6736: 
1.424     albertel 6737:    Filter sub for lonnavmaps, filters out hidden resources if ignore
                   6738:    hidden resources was selected
                   6739: 
1.423     albertel 6740: =cut
                   6741: 
1.83      albertel 6742: sub scantron_filter {
                   6743:     my ($curres)=@_;
1.331     albertel 6744: 
                   6745:     if (ref($curres) && $curres->is_problem()) {
                   6746: 	# if the user has asked to not have either hidden
                   6747: 	# or 'randomout' controlled resources to be graded
                   6748: 	# don't include them
                   6749: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
                   6750: 	    && $curres->randomout) {
                   6751: 	    return 0;
                   6752: 	}
1.83      albertel 6753: 	return 1;
                   6754:     }
                   6755:     return 0;
1.82      albertel 6756: }
                   6757: 
1.423     albertel 6758: =pod
                   6759: 
                   6760: =item scantron_process_corrections
                   6761: 
1.424     albertel 6762:    Gets correction information out of submitted form data and corrects
                   6763:    the scanline
                   6764: 
1.423     albertel 6765: =cut
                   6766: 
1.157     albertel 6767: sub scantron_process_corrections {
                   6768:     my ($r) = @_;
1.257     albertel 6769:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 6770:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6771:     my $classlist=&Apache::loncoursedata::get_classlist();
1.257     albertel 6772:     my $which=$env{'form.scantron_line'};
1.200     albertel 6773:     my $line=&scantron_get_line($scanlines,$scan_data,$which);
1.157     albertel 6774:     my ($skip,$err,$errmsg);
1.257     albertel 6775:     if ($env{'form.scantron_skip_record'}) {
1.157     albertel 6776: 	$skip=1;
1.257     albertel 6777:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) {
                   6778: 	my $newstudent=$env{'form.scantron_username'}.':'.
                   6779: 	    $env{'form.scantron_domain'};
1.157     albertel 6780: 	my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID];
                   6781: 	($line,$err,$errmsg)=
                   6782: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
                   6783: 				     'ID',{'newid'=>$newid,
1.257     albertel 6784: 				    'username'=>$env{'form.scantron_username'},
                   6785: 				    'domain'=>$env{'form.scantron_domain'}});
                   6786:     } elsif ($env{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) {
                   6787: 	my $resolution=$env{'form.scantron_CODE_resolution'};
1.190     albertel 6788: 	my $newCODE;
1.192     albertel 6789: 	my %args;
1.190     albertel 6790: 	if      ($resolution eq 'use_unfound') {
1.191     albertel 6791: 	    $newCODE='use_unfound';
1.190     albertel 6792: 	} elsif ($resolution eq 'use_found') {
1.257     albertel 6793: 	    $newCODE=$env{'form.scantron_CODE_selectedvalue'};
1.190     albertel 6794: 	} elsif ($resolution eq 'use_typed') {
1.257     albertel 6795: 	    $newCODE=$env{'form.scantron_CODE_newvalue'};
1.194     albertel 6796: 	} elsif ($resolution =~ /^use_closest_(\d+)/) {
1.257     albertel 6797: 	    $newCODE=$env{"form.scantron_CODE_closest_$1"};
1.190     albertel 6798: 	}
1.257     albertel 6799: 	if ($env{'form.scantron_corrections'} eq 'duplicateCODE') {
1.192     albertel 6800: 	    $args{'CODE_ignore_dup'}=1;
                   6801: 	}
                   6802: 	$args{'CODE'}=$newCODE;
1.186     albertel 6803: 	($line,$err,$errmsg)=
                   6804: 	    &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which,
1.192     albertel 6805: 				     'CODE',\%args);
1.257     albertel 6806:     } elsif ($env{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) {
                   6807: 	foreach my $question (split(',',$env{'form.scantron_questions'})) {
1.157     albertel 6808: 	    ($line,$err,$errmsg)=
                   6809: 		&scantron_fixup_scanline(\%scantron_config,$scan_data,$line,
                   6810: 					 $which,'answer',
                   6811: 					 { 'question'=>$question,
1.503     raeburn  6812: 		      		   'response'=>$env{"form.scantron_correct_Q_$question"},
                   6813:                                    'questionnum'=>$env{"form.scantron_questionnum_Q_$question"}});
1.157     albertel 6814: 	    if ($err) { last; }
                   6815: 	}
                   6816:     }
                   6817:     if ($err) {
1.703     bisitz   6818:         $r->print(
                   6819:             '<p class="LC_error">'
                   6820:            .&mt('Unable to accept last correction, an error occurred: [_1]',
                   6821:                 $errmsg)
1.704     raeburn  6822:            .'</p>');
1.157     albertel 6823:     } else {
1.200     albertel 6824: 	&scantron_put_line($scanlines,$scan_data,$which,$line,$skip);
1.157     albertel 6825: 	&scantron_putfile($scanlines,$scan_data);
                   6826:     }
                   6827: }
                   6828: 
1.423     albertel 6829: =pod
                   6830: 
                   6831: =item reset_skipping_status
                   6832: 
1.424     albertel 6833:    Forgets the current set of remember skipped scanlines (and thus
                   6834:    reverts back to considering all lines in the
                   6835:    scantron_skipped_<filename> file)
                   6836: 
1.423     albertel 6837: =cut
                   6838: 
1.200     albertel 6839: sub reset_skipping_status {
                   6840:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6841:     &scan_data($scan_data,'remember_skipping',undef,1);
                   6842:     &scantron_putfile(undef,$scan_data);
                   6843: }
                   6844: 
1.423     albertel 6845: =pod
                   6846: 
                   6847: =item start_skipping
                   6848: 
1.424     albertel 6849:    Marks a scanline to be skipped. 
                   6850: 
1.423     albertel 6851: =cut
                   6852: 
1.376     albertel 6853: sub start_skipping {
1.200     albertel 6854:     my ($scan_data,$i)=@_;
                   6855:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
1.376     albertel 6856:     if ($env{'form.scantron_options_redo'} =~ /^redo_/) {
                   6857: 	$remembered{$i}=2;
                   6858:     } else {
                   6859: 	$remembered{$i}=1;
                   6860:     }
1.200     albertel 6861:     &scan_data($scan_data,'remember_skipping',join(':',%remembered));
                   6862: }
                   6863: 
1.423     albertel 6864: =pod
                   6865: 
                   6866: =item should_be_skipped
                   6867: 
1.424     albertel 6868:    Checks whether a scanline should be skipped.
                   6869: 
1.423     albertel 6870: =cut
                   6871: 
1.200     albertel 6872: sub should_be_skipped {
1.376     albertel 6873:     my ($scanlines,$scan_data,$i)=@_;
1.257     albertel 6874:     if ($env{'form.scantron_options_redo'} !~ /^redo_/) {
1.200     albertel 6875: 	# not redoing old skips
1.376     albertel 6876: 	if ($scanlines->{'skipped'}[$i]) { return 1; }
1.200     albertel 6877: 	return 0;
                   6878:     }
                   6879:     my %remembered=split(':',&scan_data($scan_data,'remember_skipping'));
1.376     albertel 6880: 
                   6881:     if (exists($remembered{$i}) && $remembered{$i} != 2 ) {
                   6882: 	return 0;
                   6883:     }
1.200     albertel 6884:     return 1;
                   6885: }
                   6886: 
1.423     albertel 6887: =pod
                   6888: 
                   6889: =item remember_current_skipped
                   6890: 
1.424     albertel 6891:    Discovers what scanlines are in the scantron_skipped_<filename>
                   6892:    file and remembers them into scan_data for later use.
                   6893: 
1.423     albertel 6894: =cut
                   6895: 
1.200     albertel 6896: sub remember_current_skipped {
                   6897:     my ($scanlines,$scan_data)=&scantron_getfile();
                   6898:     my %to_remember;
                   6899:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
                   6900: 	if ($scanlines->{'skipped'}[$i]) {
                   6901: 	    $to_remember{$i}=1;
                   6902: 	}
                   6903:     }
1.376     albertel 6904: 
1.200     albertel 6905:     &scan_data($scan_data,'remember_skipping',join(':',%to_remember));
                   6906:     &scantron_putfile(undef,$scan_data);
                   6907: }
                   6908: 
1.423     albertel 6909: =pod
                   6910: 
                   6911: =item check_for_error
                   6912: 
1.424     albertel 6913:     Checks if there was an error when attempting to remove a specific
1.659     raeburn  6914:     scantron_.. bubblesheet data file. Prints out an error if
1.424     albertel 6915:     something went wrong.
                   6916: 
1.423     albertel 6917: =cut
                   6918: 
1.200     albertel 6919: sub check_for_error {
                   6920:     my ($r,$result)=@_;
                   6921:     if ($result ne 'ok' && $result ne 'not_found' ) {
1.492     albertel 6922: 	$r->print(&mt("An error occurred ([_1]) when trying to remove the existing corrections.",$result));
1.200     albertel 6923:     }
                   6924: }
1.157     albertel 6925: 
1.423     albertel 6926: =pod
                   6927: 
                   6928: =item scantron_warning_screen
                   6929: 
1.424     albertel 6930:    Interstitial screen to make sure the operator has selected the
                   6931:    correct options before we start the validation phase.
                   6932: 
1.423     albertel 6933: =cut
                   6934: 
1.203     albertel 6935: sub scantron_warning_screen {
1.650     raeburn  6936:     my ($button_text,$symb)=@_;
1.257     albertel 6937:     my $title=&Apache::lonnet::gettitle($env{'form.selectpage'});
1.284     albertel 6938:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.373     albertel 6939:     my $CODElist;
1.284     albertel 6940:     if ($scantron_config{'CODElocation'} &&
                   6941: 	$scantron_config{'CODEstart'} &&
                   6942: 	$scantron_config{'CODElength'}) {
                   6943: 	$CODElist=$env{'form.scantron_CODElist'};
1.721     bisitz   6944: 	if ($env{'form.scantron_CODElist'} eq '') { $CODElist='<span class="LC_warning">'.&mt('None').'</span>'; }
1.284     albertel 6945: 	$CODElist=
1.492     albertel 6946: 	    '<tr><td><b>'.&mt('List of CODES to validate against:').'</b></td><td><tt>'.
1.373     albertel 6947: 	    $env{'form.scantron_CODElist'}.'</tt></td></tr>';
1.284     albertel 6948:     }
1.663     raeburn  6949:     my $lastbubblepoints;
                   6950:     if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   6951:         $lastbubblepoints =
                   6952:             '<tr><td><b>'.&mt('Hand-graded items: points from last bubble in row').'</b></td><td><tt>'.
                   6953:             $env{'form.scantron_lastbubblepoints'}.'</tt></td></tr>';
                   6954:     }
1.492     albertel 6955:     return ('
1.203     albertel 6956: <p>
1.492     albertel 6957: <span class="LC_warning">
1.705     raeburn  6958: '.&mt("Please double check the information below before clicking on '[_1]'",&mt($button_text)).'</span>
1.203     albertel 6959: </p>
                   6960: <table>
1.492     albertel 6961: <tr><td><b>'.&mt('Sequence to be Graded:').'</b></td><td>'.$title.'</td></tr>
                   6962: <tr><td><b>'.&mt('Data File that will be used:').'</b></td><td><tt>'.$env{'form.scantron_selectfile'}.'</tt></td></tr>
1.663     raeburn  6963: '.$CODElist.$lastbubblepoints.'
1.203     albertel 6964: </table>
1.680     raeburn  6965: <p> '.&mt("If this information is correct, please click on '[_1]'.",&mt($button_text)).'<br />
1.650     raeburn  6966: '.&mt('If something is incorrect, please return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>
1.203     albertel 6967: 
                   6968: <br />
1.492     albertel 6969: ');
1.203     albertel 6970: }
                   6971: 
1.423     albertel 6972: =pod
                   6973: 
                   6974: =item scantron_do_warning
                   6975: 
1.424     albertel 6976:    Check if the operator has picked something for all required
                   6977:    fields. Error out if something is missing.
                   6978: 
1.423     albertel 6979: =cut
                   6980: 
1.203     albertel 6981: sub scantron_do_warning {
1.608     www      6982:     my ($r,$symb)=@_;
1.203     albertel 6983:     if (!$symb) {return '';}
1.324     albertel 6984:     my $default_form_data=&defaultFormData($symb);
1.203     albertel 6985:     $r->print(&scantron_form_start().$default_form_data);
1.257     albertel 6986:     if ( $env{'form.selectpage'} eq '' ||
                   6987: 	 $env{'form.scantron_selectfile'} eq '' ||
                   6988: 	 $env{'form.scantron_format'} eq '' ) {
1.642     raeburn  6989: 	$r->print("<p>".&mt('You have forgotten to specify some information. Please go Back and try again.')."</p>");
1.257     albertel 6990: 	if ( $env{'form.selectpage'} eq '') {
1.492     albertel 6991: 	    $r->print('<p><span class="LC_error">'.&mt('You have not selected a Sequence to grade').'</span></p>');
1.237     albertel 6992: 	} 
1.257     albertel 6993: 	if ( $env{'form.scantron_selectfile'} eq '') {
1.642     raeburn  6994: 	    $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 6995: 	} 
1.257     albertel 6996: 	if ( $env{'form.scantron_format'} eq '') {
1.642     raeburn  6997: 	    $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 6998: 	} 
                   6999:     } else {
1.650     raeburn  7000: 	my $warning=&scantron_warning_screen('Grading: Validate Records',$symb);
1.663     raeburn  7001:         my $bubbledbyhand=&hand_bubble_option();
1.492     albertel 7002: 	$r->print('
1.663     raeburn  7003: '.$warning.$bubbledbyhand.'
1.492     albertel 7004: <input type="submit" name="submit" value="'.&mt('Grading: Validate Records').'" />
1.203     albertel 7005: <input type="hidden" name="command" value="scantron_validate" />
1.492     albertel 7006: ');
1.237     albertel 7007:     }
1.614     www      7008:     $r->print("</form><br />");
1.203     albertel 7009:     return '';
                   7010: }
                   7011: 
1.423     albertel 7012: =pod
                   7013: 
                   7014: =item scantron_form_start
                   7015: 
1.424     albertel 7016:     html hidden input for remembering all selected grading options
                   7017: 
1.423     albertel 7018: =cut
                   7019: 
1.203     albertel 7020: sub scantron_form_start {
                   7021:     my ($max_bubble)=@_;
                   7022:     my $result= <<SCANTRONFORM;
                   7023: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
1.257     albertel 7024:   <input type="hidden" name="selectpage" value="$env{'form.selectpage'}" />
                   7025:   <input type="hidden" name="scantron_format" value="$env{'form.scantron_format'}" />
                   7026:   <input type="hidden" name="scantron_selectfile" value="$env{'form.scantron_selectfile'}" />
1.218     albertel 7027:   <input type="hidden" name="scantron_maxbubble" value="$max_bubble" />
1.257     albertel 7028:   <input type="hidden" name="scantron_CODElist" value="$env{'form.scantron_CODElist'}" />
                   7029:   <input type="hidden" name="scantron_CODEunique" value="$env{'form.scantron_CODEunique'}" />
                   7030:   <input type="hidden" name="scantron_options_redo" value="$env{'form.scantron_options_redo'}" />
                   7031:   <input type="hidden" name="scantron_options_ignore" value="$env{'form.scantron_options_ignore'}" />
1.331     albertel 7032:   <input type="hidden" name="scantron_options_hidden" value="$env{'form.scantron_options_hidden'}" />
1.203     albertel 7033: SCANTRONFORM
1.447     foxr     7034: 
                   7035:   my $line = 0;
                   7036:     while (defined($env{"form.scantron.bubblelines.$line"})) {
                   7037:        my $chunk =
                   7038: 	   '<input type="hidden" name="scantron.bubblelines.'.$line.'" value="'.$env{"form.scantron.bubblelines.$line"}.'" />'."\n";
1.448     foxr     7039:        $chunk .=
                   7040: 	   '<input type="hidden" name="scantron.first_bubble_line.'.$line.'" value="'.$env{"form.scantron.first_bubble_line.$line"}.'" />'."\n";
1.503     raeburn  7041:        $chunk .= 
                   7042:            '<input type="hidden" name="scantron.sub_bubblelines.'.$line.'" value="'.$env{"form.scantron.sub_bubblelines.$line"}.'" />'."\n";
1.504     raeburn  7043:        $chunk .=
                   7044:            '<input type="hidden" name="scantron.responsetype.'.$line.'" value="'.$env{"form.scantron.responsetype.$line"}.'" />'."\n";
1.691     raeburn  7045:        $chunk .=
                   7046:            '<input type="hidden" name="scantron.residpart.'.$line.'" value="'.$env{"form.scantron.residpart.$line"}.'" />'."\n";
1.447     foxr     7047:        $result .= $chunk;
                   7048:        $line++;
1.691     raeburn  7049:     }
1.203     albertel 7050:     return $result;
                   7051: }
                   7052: 
1.423     albertel 7053: =pod
                   7054: 
                   7055: =item scantron_validate_file
                   7056: 
1.659     raeburn  7057:     Dispatch routine for doing validation of a bubblesheet data file.
1.424     albertel 7058: 
                   7059:     Also processes any necessary information resets that need to
                   7060:     occur before validation begins (ignore previous corrections,
                   7061:     restarting the skipped records processing)
                   7062: 
1.423     albertel 7063: =cut
                   7064: 
1.157     albertel 7065: sub scantron_validate_file {
1.608     www      7066:     my ($r,$symb) = @_;
1.157     albertel 7067:     if (!$symb) {return '';}
1.324     albertel 7068:     my $default_form_data=&defaultFormData($symb);
1.200     albertel 7069:     
1.703     bisitz   7070:     # do the detection of only doing skipped records first before we delete
1.424     albertel 7071:     # them when doing the corrections reset
1.257     albertel 7072:     if ($env{'form.scantron_options_redo'} ne 'redo_skipped_ready') {
1.200     albertel 7073: 	&reset_skipping_status();
                   7074:     }
1.257     albertel 7075:     if ($env{'form.scantron_options_redo'} eq 'redo_skipped') {
1.200     albertel 7076: 	&remember_current_skipped();
1.257     albertel 7077: 	$env{'form.scantron_options_redo'}='redo_skipped_ready';
1.200     albertel 7078:     }
                   7079: 
1.257     albertel 7080:     if ($env{'form.scantron_options_ignore'} eq 'ignore_corrections') {
1.200     albertel 7081: 	&check_for_error($r,&scantron_remove_file('corrected'));
                   7082: 	&check_for_error($r,&scantron_remove_file('skipped'));
                   7083: 	&check_for_error($r,&scantron_remove_scan_data());
1.257     albertel 7084: 	$env{'form.scantron_options_ignore'}='done';
1.192     albertel 7085:     }
1.200     albertel 7086: 
1.257     albertel 7087:     if ($env{'form.scantron_corrections'}) {
1.157     albertel 7088: 	&scantron_process_corrections($r);
                   7089:     }
1.503     raeburn  7090:     $r->print('<p>'.&mt('Gathering necessary information.').'</p>');$r->rflush();
1.157     albertel 7091:     #get the student pick code ready
                   7092:     $r->print(&Apache::loncommon::studentbrowser_javascript());
1.582     raeburn  7093:     my $nav_error;
1.649     raeburn  7094:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
                   7095:     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
1.582     raeburn  7096:     if ($nav_error) {
                   7097:         $r->print(&navmap_errormsg());
                   7098:         return '';
                   7099:     }
1.203     albertel 7100:     my $result=&scantron_form_start($max_bubble).$default_form_data;
1.663     raeburn  7101:     if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   7102:         $result .= '<input type="hidden" name="scantron_lastbubblepoints" value="'.$env{'form.scantron_lastbubblepoints'}.'" />';
                   7103:     }
1.157     albertel 7104:     $r->print($result);
                   7105:     
1.334     albertel 7106:     my @validate_phases=( 'sequence',
                   7107: 			  'ID',
1.157     albertel 7108: 			  'CODE',
                   7109: 			  'doublebubble',
                   7110: 			  'missingbubbles');
1.257     albertel 7111:     if (!$env{'form.validatepass'}) {
                   7112: 	$env{'form.validatepass'} = 0;
1.157     albertel 7113:     }
1.257     albertel 7114:     my $currentphase=$env{'form.validatepass'};
1.157     albertel 7115: 
1.448     foxr     7116: 
1.157     albertel 7117:     my $stop=0;
                   7118:     while (!$stop && $currentphase < scalar(@validate_phases)) {
1.503     raeburn  7119: 	$r->print(&mt('Validating '.$validate_phases[$currentphase]).'<br />');
1.157     albertel 7120: 	$r->rflush();
1.691     raeburn  7121:      
1.157     albertel 7122: 	my $which="scantron_validate_".$validate_phases[$currentphase];
                   7123: 	{
                   7124: 	    no strict 'refs';
                   7125: 	    ($stop,$currentphase)=&$which($r,$currentphase);
                   7126: 	}
                   7127:     }
                   7128:     if (!$stop) {
1.650     raeburn  7129: 	my $warning=&scantron_warning_screen('Start Grading',$symb);
1.542     raeburn  7130: 	$r->print(&mt('Validation process complete.').'<br />'.
                   7131:                   $warning.
                   7132:                   &mt('Perform verification for each student after storage of submissions?').
                   7133:                   '&nbsp;<span class="LC_nobreak"><label>'.
                   7134:                   '<input type="radio" name="verifyrecord" value="1" />'.&mt('Yes').'</label>'.
                   7135:                   ('&nbsp;'x3).'<label>'.
                   7136:                   '<input type="radio" name="verifyrecord" value="0" checked="checked" />'.&mt('No').
                   7137:                   '</label></span><br />'.
                   7138:                   &mt('Grading will take longer if you use verification.').'<br />'.
1.650     raeburn  7139:                   &mt('Otherwise, Grade/Manage/Review Bubblesheets [_1] Review bubblesheet data can be used once grading is complete.','&raquo;').'<br /><br />'.
1.542     raeburn  7140:                   '<input type="submit" name="submit" value="'.&mt('Start Grading').'" />'.
                   7141:                   '<input type="hidden" name="command" value="scantron_process" />'."\n");
1.157     albertel 7142:     } else {
                   7143: 	$r->print('<input type="hidden" name="command" value="scantron_validate" />');
                   7144: 	$r->print("<input type='hidden' name='validatepass' value='".$currentphase."' />");
                   7145:     }
                   7146:     if ($stop) {
1.334     albertel 7147: 	if ($validate_phases[$currentphase] eq 'sequence') {
1.539     riegler  7148: 	    $r->print('<input type="submit" name="submit" value="'.&mt('Ignore').' &rarr; " />');
1.492     albertel 7149: 	    $r->print(' '.&mt('this error').' <br />');
1.334     albertel 7150: 
1.650     raeburn  7151: 	    $r->print('<p>'.&mt('Or return to [_1]Grade/Manage/Review Bubblesheets[_2] to start over.','<a href="/adm/grades?symb='.$symb.'&command=scantron_selectphase" class="LC_info">','</a>').'</p>');
1.334     albertel 7152: 	} else {
1.503     raeburn  7153:             if ($validate_phases[$currentphase] eq 'doublebubble' || $validate_phases[$currentphase] eq 'missingbubbles') {
1.539     riegler  7154: 	        $r->print('<input type="button" name="submitbutton" value="'.&mt('Continue').' &rarr;" onclick="javascript:verify_bubble_radio(this.form)" />');
1.503     raeburn  7155:             } else {
1.539     riegler  7156:                 $r->print('<input type="submit" name="submit" value="'.&mt('Continue').' &rarr;" />');
1.503     raeburn  7157:             }
1.492     albertel 7158: 	    $r->print(' '.&mt('using corrected info').' <br />');
                   7159: 	    $r->print("<input type='submit' value='".&mt("Skip")."' name='scantron_skip_record' />");
                   7160: 	    $r->print(" ".&mt("this scanline saving it for later."));
1.334     albertel 7161: 	}
1.157     albertel 7162:     }
1.614     www      7163:     $r->print(" </form><br />");
1.157     albertel 7164:     return '';
                   7165: }
                   7166: 
1.423     albertel 7167: 
                   7168: =pod
                   7169: 
                   7170: =item scantron_remove_file
                   7171: 
1.659     raeburn  7172:    Removes the requested bubblesheet data file, makes sure that
1.424     albertel 7173:    scantron_original_<filename> is never removed
                   7174: 
                   7175: 
1.423     albertel 7176: =cut
                   7177: 
1.200     albertel 7178: sub scantron_remove_file {
1.192     albertel 7179:     my ($which)=@_;
1.257     albertel 7180:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7181:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.192     albertel 7182:     my $file='scantron_';
1.200     albertel 7183:     if ($which eq 'corrected' || $which eq 'skipped') {
                   7184: 	$file.=$which.'_';
1.192     albertel 7185:     } else {
                   7186: 	return 'refused';
                   7187:     }
1.257     albertel 7188:     $file.=$env{'form.scantron_selectfile'};
1.200     albertel 7189:     return &Apache::lonnet::removeuserfile($cname,$cdom,$file);
                   7190: }
                   7191: 
1.423     albertel 7192: 
                   7193: =pod
                   7194: 
                   7195: =item scantron_remove_scan_data
                   7196: 
1.659     raeburn  7197:    Removes all scan_data correction for the requested bubblesheet
1.424     albertel 7198:    data file.  (In the case that both the are doing skipped records we need
                   7199:    to remember the old skipped lines for the time being so that element
                   7200:    persists for a while.)
                   7201: 
1.423     albertel 7202: =cut
                   7203: 
1.200     albertel 7204: sub scantron_remove_scan_data {
1.257     albertel 7205:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7206:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.192     albertel 7207:     my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname);
                   7208:     my @todelete;
1.257     albertel 7209:     my $filename=$env{'form.scantron_selectfile'};
1.192     albertel 7210:     foreach my $key (@keys) {
                   7211: 	if ($key=~/^\Q$filename\E_/) {
1.257     albertel 7212: 	    if ($env{'form.scantron_options_redo'} eq 'redo_skipped_ready' &&
1.200     albertel 7213: 		$key=~/remember_skipping/) {
                   7214: 		next;
                   7215: 	    }
1.192     albertel 7216: 	    push(@todelete,$key);
                   7217: 	}
                   7218:     }
1.200     albertel 7219:     my $result;
1.192     albertel 7220:     if (@todelete) {
1.491     albertel 7221: 	$result = &Apache::lonnet::del('nohist_scantrondata',
                   7222: 				       \@todelete,$cdom,$cname);
                   7223:     } else {
                   7224: 	$result = 'ok';
1.192     albertel 7225:     }
                   7226:     return $result;
                   7227: }
                   7228: 
1.423     albertel 7229: 
                   7230: =pod
                   7231: 
                   7232: =item scantron_getfile
                   7233: 
1.659     raeburn  7234:     Fetches the requested bubblesheet data file (all 3 versions), and
1.424     albertel 7235:     the scan_data hash
                   7236:   
                   7237:   Arguments:
                   7238:     None
                   7239: 
                   7240:   Returns:
                   7241:     2 hash references
                   7242: 
                   7243:      - first one has 
                   7244:          orig      -
                   7245:          corrected -
                   7246:          skipped   -  each of which points to an array ref of the specified
                   7247:                       file broken up into individual lines
                   7248:          count     - number of scanlines
                   7249:  
                   7250:      - second is the scan_data hash possible keys are
1.425     albertel 7251:        ($number refers to scanline numbered $number and thus the key affects
                   7252:         only that scanline
                   7253:         $bubline refers to the specific bubble line element and the aspects
                   7254:         refers to that specific bubble line element)
                   7255: 
                   7256:        $number.user - username:domain to use
                   7257:        $number.CODE_ignore_dup 
                   7258:                     - ignore the duplicate CODE error 
                   7259:        $number.useCODE
                   7260:                     - use the CODE in the scanline as is
                   7261:        $number.no_bubble.$bubline
                   7262:                     - it is valid that there is no bubbled in bubble
                   7263:                       at $number $bubline
                   7264:        remember_skipping
                   7265:                     - a frozen hash containing keys of $number and values
                   7266:                       of either 
                   7267:                         1 - we are on a 'do skipped records pass' and plan
                   7268:                             on processing this line
                   7269:                         2 - we are on a 'do skipped records pass' and this
                   7270:                             scanline has been marked to skip yet again
1.424     albertel 7271: 
1.423     albertel 7272: =cut
                   7273: 
1.157     albertel 7274: sub scantron_getfile {
1.200     albertel 7275:     #FIXME really would prefer a scantron directory
1.257     albertel 7276:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7277:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.157     albertel 7278:     my $lines;
                   7279:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7280: 		       'scantron_orig_'.$env{'form.scantron_selectfile'});
1.157     albertel 7281:     my %scanlines;
                   7282:     $scanlines{'orig'}=[(split("\n",$lines,-1))];
                   7283:     my $temp=$scanlines{'orig'};
                   7284:     $scanlines{'count'}=$#$temp;
                   7285: 
                   7286:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7287: 		       'scantron_corrected_'.$env{'form.scantron_selectfile'});
1.157     albertel 7288:     if ($lines eq '-1') {
                   7289: 	$scanlines{'corrected'}=[];
                   7290:     } else {
                   7291: 	$scanlines{'corrected'}=[(split("\n",$lines,-1))];
                   7292:     }
                   7293:     $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.
1.257     albertel 7294: 		       'scantron_skipped_'.$env{'form.scantron_selectfile'});
1.157     albertel 7295:     if ($lines eq '-1') {
                   7296: 	$scanlines{'skipped'}=[];
                   7297:     } else {
                   7298: 	$scanlines{'skipped'}=[(split("\n",$lines,-1))];
                   7299:     }
1.175     albertel 7300:     my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname);
1.157     albertel 7301:     if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); }
                   7302:     my %scan_data = @tmp;
                   7303:     return (\%scanlines,\%scan_data);
                   7304: }
                   7305: 
1.423     albertel 7306: =pod
                   7307: 
                   7308: =item lonnet_putfile
                   7309: 
1.424     albertel 7310:    Wrapper routine to call &Apache::lonnet::finishuserfileupload
                   7311: 
                   7312:  Arguments:
                   7313:    $contents - data to store
                   7314:    $filename - filename to store $contents into
                   7315: 
                   7316:  Returns:
                   7317:    result value from &Apache::lonnet::finishuserfileupload
                   7318: 
1.423     albertel 7319: =cut
                   7320: 
1.157     albertel 7321: sub lonnet_putfile {
                   7322:     my ($contents,$filename)=@_;
1.257     albertel 7323:     my $docuname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7324:     my $docudom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   7325:     $env{'form.sillywaytopassafilearound'}=$contents;
1.275     albertel 7326:     &Apache::lonnet::finishuserfileupload($docuname,$docudom,'sillywaytopassafilearound',$filename);
1.157     albertel 7327: 
                   7328: }
                   7329: 
1.423     albertel 7330: =pod
                   7331: 
                   7332: =item scantron_putfile
                   7333: 
1.659     raeburn  7334:     Stores the current version of the bubblesheet data files, and the
1.424     albertel 7335:     scan_data hash. (Does not modify the original version only the
                   7336:     corrected and skipped versions.
                   7337: 
                   7338:  Arguments:
                   7339:     $scanlines - hash ref that looks like the first return value from
                   7340:                  &scantron_getfile()
                   7341:     $scan_data - hash ref that looks like the second return value from
                   7342:                  &scantron_getfile()
                   7343: 
1.423     albertel 7344: =cut
                   7345: 
1.157     albertel 7346: sub scantron_putfile {
                   7347:     my ($scanlines,$scan_data) = @_;
1.200     albertel 7348:     #FIXME really would prefer a scantron directory
1.257     albertel 7349:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   7350:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
1.200     albertel 7351:     if ($scanlines) {
                   7352: 	my $prefix='scantron_';
1.157     albertel 7353: # no need to update orig, shouldn't change
                   7354: #   &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'.
1.257     albertel 7355: #		    $env{'form.scantron_selectfile'});
1.200     albertel 7356: 	&lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}),
                   7357: 			$prefix.'corrected_'.
1.257     albertel 7358: 			$env{'form.scantron_selectfile'});
1.200     albertel 7359: 	&lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}),
                   7360: 			$prefix.'skipped_'.
1.257     albertel 7361: 			$env{'form.scantron_selectfile'});
1.200     albertel 7362:     }
1.175     albertel 7363:     &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname);
1.157     albertel 7364: }
                   7365: 
1.423     albertel 7366: =pod
                   7367: 
                   7368: =item scantron_get_line
                   7369: 
1.424     albertel 7370:    Returns the correct version of the scanline
                   7371: 
                   7372:  Arguments:
                   7373:     $scanlines - hash ref that looks like the first return value from
                   7374:                  &scantron_getfile()
                   7375:     $scan_data - hash ref that looks like the second return value from
                   7376:                  &scantron_getfile()
                   7377:     $i         - number of the requested line (starts at 0)
                   7378: 
                   7379:  Returns:
                   7380:    A scanline, (either the original or the corrected one if it
                   7381:    exists), or undef if the requested scanline should be
                   7382:    skipped. (Either because it's an skipped scanline, or it's an
                   7383:    unskipped scanline and we are not doing a 'do skipped scanlines'
                   7384:    pass.
                   7385: 
1.423     albertel 7386: =cut
                   7387: 
1.157     albertel 7388: sub scantron_get_line {
1.200     albertel 7389:     my ($scanlines,$scan_data,$i)=@_;
1.376     albertel 7390:     if (&should_be_skipped($scanlines,$scan_data,$i)) { return undef; }
                   7391:     #if ($scanlines->{'skipped'}[$i]) { return undef; }
1.157     albertel 7392:     if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];}
                   7393:     return $scanlines->{'orig'}[$i]; 
                   7394: }
                   7395: 
1.423     albertel 7396: =pod
                   7397: 
                   7398: =item scantron_todo_count
                   7399: 
1.424     albertel 7400:     Counts the number of scanlines that need processing.
                   7401: 
                   7402:  Arguments:
                   7403:     $scanlines - hash ref that looks like the first return value from
                   7404:                  &scantron_getfile()
                   7405:     $scan_data - hash ref that looks like the second return value from
                   7406:                  &scantron_getfile()
                   7407: 
                   7408:  Returns:
                   7409:     $count - number of scanlines to process
                   7410: 
1.423     albertel 7411: =cut
                   7412: 
1.200     albertel 7413: sub get_todo_count {
                   7414:     my ($scanlines,$scan_data)=@_;
                   7415:     my $count=0;
                   7416:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
                   7417: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
                   7418: 	if ($line=~/^[\s\cz]*$/) { next; }
                   7419: 	$count++;
                   7420:     }
                   7421:     return $count;
                   7422: }
                   7423: 
1.423     albertel 7424: =pod
                   7425: 
                   7426: =item scantron_put_line
                   7427: 
1.659     raeburn  7428:     Updates the 'corrected' or 'skipped' versions of the bubblesheet
1.424     albertel 7429:     data file.
                   7430: 
                   7431:  Arguments:
                   7432:     $scanlines - hash ref that looks like the first return value from
                   7433:                  &scantron_getfile()
                   7434:     $scan_data - hash ref that looks like the second return value from
                   7435:                  &scantron_getfile()
                   7436:     $i         - line number to update
                   7437:     $newline   - contents of the updated scanline
                   7438:     $skip      - if true make the line for skipping and update the
                   7439:                  'skipped' file
                   7440: 
1.423     albertel 7441: =cut
                   7442: 
1.157     albertel 7443: sub scantron_put_line {
1.200     albertel 7444:     my ($scanlines,$scan_data,$i,$newline,$skip)=@_;
1.157     albertel 7445:     if ($skip) {
                   7446: 	$scanlines->{'skipped'}[$i]=$newline;
1.376     albertel 7447: 	&start_skipping($scan_data,$i);
1.157     albertel 7448: 	return;
                   7449:     }
                   7450:     $scanlines->{'corrected'}[$i]=$newline;
                   7451: }
                   7452: 
1.423     albertel 7453: =pod
                   7454: 
                   7455: =item scantron_clear_skip
                   7456: 
1.424     albertel 7457:    Remove a line from the 'skipped' file
                   7458: 
                   7459:  Arguments:
                   7460:     $scanlines - hash ref that looks like the first return value from
                   7461:                  &scantron_getfile()
                   7462:     $scan_data - hash ref that looks like the second return value from
                   7463:                  &scantron_getfile()
                   7464:     $i         - line number to update
                   7465: 
1.423     albertel 7466: =cut
                   7467: 
1.376     albertel 7468: sub scantron_clear_skip {
                   7469:     my ($scanlines,$scan_data,$i)=@_;
                   7470:     if (exists($scanlines->{'skipped'}[$i])) {
                   7471: 	undef($scanlines->{'skipped'}[$i]);
                   7472: 	return 1;
                   7473:     }
                   7474:     return 0;
                   7475: }
                   7476: 
1.423     albertel 7477: =pod
                   7478: 
                   7479: =item scantron_filter_not_exam
                   7480: 
1.424     albertel 7481:    Filter routine used by &Apache::lonnavmaps::retrieveResources(), to
                   7482:    filter out resources that are not marked as 'exam' mode
                   7483: 
1.423     albertel 7484: =cut
                   7485: 
1.334     albertel 7486: sub scantron_filter_not_exam {
                   7487:     my ($curres)=@_;
                   7488:     
                   7489:     if (ref($curres) && $curres->is_problem() && !$curres->is_exam()) {
                   7490: 	# if the user has asked to not have either hidden
                   7491: 	# or 'randomout' controlled resources to be graded
                   7492: 	# don't include them
                   7493: 	if ($env{'form.scantron_options_hidden'} eq 'ignore_hidden'
                   7494: 	    && $curres->randomout) {
                   7495: 	    return 0;
                   7496: 	}
                   7497: 	return 1;
                   7498:     }
                   7499:     return 0;
                   7500: }
                   7501: 
1.423     albertel 7502: =pod
                   7503: 
                   7504: =item scantron_validate_sequence
                   7505: 
1.424     albertel 7506:     Validates the selected sequence, checking for resource that are
                   7507:     not set to exam mode.
                   7508: 
1.423     albertel 7509: =cut
                   7510: 
1.334     albertel 7511: sub scantron_validate_sequence {
                   7512:     my ($r,$currentphase) = @_;
                   7513: 
                   7514:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  7515:     unless (ref($navmap)) {
                   7516:         $r->print(&navmap_errormsg());
                   7517:         return (1,$currentphase);
                   7518:     }
1.334     albertel 7519:     my (undef,undef,$sequence)=
                   7520: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
                   7521: 
                   7522:     my $map=$navmap->getResourceByUrl($sequence);
                   7523: 
                   7524:     $r->print('<input type="hidden" name="validate_sequence_exam"
                   7525:                                     value="ignore" />');
                   7526:     if ($env{'form.validate_sequence_exam'} ne 'ignore') {
                   7527: 	my @resources=
                   7528: 	    $navmap->retrieveResources($map,\&scantron_filter_not_exam,1,0);
                   7529: 	if (@resources) {
1.675     bisitz   7530: 	    $r->print(
                   7531:                 '<p class="LC_warning">'
                   7532:                .&mt('Some resources in the sequence currently are not set to'
1.684     bisitz   7533:                    .' bubblesheet exam mode. Grading these resources currently may not'
1.675     bisitz   7534:                    .' work correctly.')
                   7535:                .'</p>'
                   7536:             );
1.334     albertel 7537: 	    return (1,$currentphase);
                   7538: 	}
                   7539:     }
                   7540: 
                   7541:     return (0,$currentphase+1);
                   7542: }
                   7543: 
1.423     albertel 7544: 
                   7545: 
1.157     albertel 7546: sub scantron_validate_ID {
                   7547:     my ($r,$currentphase) = @_;
                   7548:     
                   7549:     #get student info
                   7550:     my $classlist=&Apache::loncoursedata::get_classlist();
                   7551:     my %idmap=&username_to_idmap($classlist);
                   7552: 
                   7553:     #get scantron line setup
1.257     albertel 7554:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 7555:     my ($scanlines,$scan_data)=&scantron_getfile();
1.582     raeburn  7556: 
                   7557:     my $nav_error;
1.649     raeburn  7558:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble_lines.. array.
1.582     raeburn  7559:     if ($nav_error) {
                   7560:         $r->print(&navmap_errormsg());
                   7561:         return(1,$currentphase);
                   7562:     }
1.157     albertel 7563: 
                   7564:     my %found=('ids'=>{},'usernames'=>{});
                   7565:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 7566: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 7567: 	if ($line=~/^[\s\cz]*$/) { next; }
                   7568: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
                   7569: 						 $scan_data);
                   7570: 	my $id=$$scan_record{'scantron.ID'};
                   7571: 	my $found;
                   7572: 	foreach my $checkid (keys(%idmap)) {
                   7573: 	    if (lc($checkid) eq lc($id)) { $found=$checkid;last; }
                   7574: 	}
                   7575: 	if ($found) {
                   7576: 	    my $username=$idmap{$found};
                   7577: 	    if ($found{'ids'}{$found}) {
                   7578: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7579: 					 $line,'duplicateID',$found);
1.194     albertel 7580: 		return(1,$currentphase);
1.157     albertel 7581: 	    } elsif ($found{'usernames'}{$username}) {
                   7582: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7583: 					 $line,'duplicateID',$username);
1.194     albertel 7584: 		return(1,$currentphase);
1.157     albertel 7585: 	    }
1.186     albertel 7586: 	    #FIXME store away line we previously saw the ID on to use above
1.157     albertel 7587: 	    $found{'ids'}{$found}++;
                   7588: 	    $found{'usernames'}{$username}++;
                   7589: 	} else {
                   7590: 	    if ($id =~ /^\s*$/) {
1.158     albertel 7591: 		my $username=&scan_data($scan_data,"$i.user");
1.157     albertel 7592: 		if (defined($username) && $found{'usernames'}{$username}) {
                   7593: 		    &scantron_get_correction($r,$i,$scan_record,
                   7594: 					     \%scantron_config,
                   7595: 					     $line,'duplicateID',$username);
1.194     albertel 7596: 		    return(1,$currentphase);
1.157     albertel 7597: 		} elsif (!defined($username)) {
                   7598: 		    &scantron_get_correction($r,$i,$scan_record,
                   7599: 					     \%scantron_config,
                   7600: 					     $line,'incorrectID');
1.194     albertel 7601: 		    return(1,$currentphase);
1.157     albertel 7602: 		}
                   7603: 		$found{'usernames'}{$username}++;
                   7604: 	    } else {
                   7605: 		&scantron_get_correction($r,$i,$scan_record,\%scantron_config,
                   7606: 					 $line,'incorrectID');
1.194     albertel 7607: 		return(1,$currentphase);
1.157     albertel 7608: 	    }
                   7609: 	}
                   7610:     }
                   7611: 
                   7612:     return (0,$currentphase+1);
                   7613: }
                   7614: 
1.423     albertel 7615: 
1.157     albertel 7616: sub scantron_get_correction {
1.691     raeburn  7617:     my ($r,$i,$scan_record,$scan_config,$line,$error,$arg,
                   7618:         $randomorder,$randompick,$respnumlookup,$startline)=@_;
1.454     banghart 7619: #FIXME in the case of a duplicated ID the previous line, probably need
1.157     albertel 7620: #to show both the current line and the previous one and allow skipping
                   7621: #the previous one or the current one
                   7622: 
1.333     albertel 7623:     if ( $$scan_record{'scantron.PaperID'} =~ /\S/) {
1.658     bisitz   7624:         $r->print(
                   7625:             '<p class="LC_warning">'
                   7626:            .&mt('An error was detected ([_1]) for PaperID [_2]',
                   7627:                 "<b>$error</b>",
                   7628:                 '<tt>'.$$scan_record{'scantron.PaperID'}.'</tt>')
                   7629:            ."</p> \n");
1.157     albertel 7630:     } else {
1.658     bisitz   7631:         $r->print(
                   7632:             '<p class="LC_warning">'
                   7633:            .&mt('An error was detected ([_1]) in scanline [_2] [_3]',
                   7634:                 "<b>$error</b>", $i, "<pre>$line</pre>")
                   7635:            ."</p> \n");
                   7636:     }
                   7637:     my $message =
                   7638:         '<p>'
                   7639:        .&mt('The ID on the form is [_1]',
                   7640:             "<tt>$$scan_record{'scantron.ID'}</tt>")
                   7641:        .'<br />'
1.665     raeburn  7642:        .&mt('The name on the paper is [_1], [_2]',
1.658     bisitz   7643:             $$scan_record{'scantron.LastName'},
                   7644:             $$scan_record{'scantron.FirstName'})
                   7645:        .'</p>';
1.242     albertel 7646: 
1.157     albertel 7647:     $r->print('<input type="hidden" name="scantron_corrections" value="'.$error.'" />'."\n");
                   7648:     $r->print('<input type="hidden" name="scantron_line" value="'.$i.'" />'."\n");
1.503     raeburn  7649:                            # Array populated for doublebubble or
                   7650:     my @lines_to_correct;  # missingbubble errors to build javascript
                   7651:                            # to validate radio button checking   
                   7652: 
1.157     albertel 7653:     if ($error =~ /ID$/) {
1.186     albertel 7654: 	if ($error eq 'incorrectID') {
1.658     bisitz   7655:             $r->print('<p class="LC_warning">'.&mt("The encoded ID is not in the classlist").
1.492     albertel 7656: 		      "</p>\n");
1.157     albertel 7657: 	} elsif ($error eq 'duplicateID') {
1.658     bisitz   7658:             $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 7659: 	}
1.242     albertel 7660: 	$r->print($message);
1.492     albertel 7661: 	$r->print("<p>".&mt("How should I handle this?")." <br /> \n");
1.157     albertel 7662: 	$r->print("\n<ul><li> ");
                   7663: 	#FIXME it would be nice if this sent back the user ID and
                   7664: 	#could do partial userID matches
                   7665: 	$r->print(&Apache::loncommon::selectstudent_link('scantronupload',
                   7666: 				       'scantron_username','scantron_domain'));
                   7667: 	$r->print(": <input type='text' name='scantron_username' value='' />");
1.685     bisitz   7668: 	$r->print("\n:\n".
1.257     albertel 7669: 		 &Apache::loncommon::select_dom_form($env{'request.role.domain'},'scantron_domain'));
1.157     albertel 7670: 
                   7671: 	$r->print('</li>');
1.186     albertel 7672:     } elsif ($error =~ /CODE$/) {
                   7673: 	if ($error eq 'incorrectCODE') {
1.658     bisitz   7674: 	    $r->print('<p class="LC_warning">'.&mt("The encoded CODE is not in the list of possible CODEs.")."</p>\n");
1.186     albertel 7675: 	} elsif ($error eq 'duplicateCODE') {
1.658     bisitz   7676: 	    $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 7677: 	}
1.658     bisitz   7678: 	$r->print("<p>".&mt('The CODE on the form is [_1]',
                   7679: 			    "<tt>'$$scan_record{'scantron.CODE'}'</tt>")
                   7680:                  ."</p>\n");
1.242     albertel 7681: 	$r->print($message);
1.658     bisitz   7682: 	$r->print("<p>".&mt("How should I handle this?")."</p>\n");
1.187     albertel 7683: 	$r->print("\n<br /> ");
1.194     albertel 7684: 	my $i=0;
1.273     albertel 7685: 	if ($error eq 'incorrectCODE' 
                   7686: 	    && $$scan_record{'scantron.CODE'}=~/\S/ ) {
1.194     albertel 7687: 	    my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'});
1.278     albertel 7688: 	    if ($closest > 0) {
                   7689: 		foreach my $testcode (@{$closest}) {
                   7690: 		    my $checked='';
1.569     bisitz   7691: 		    if (!$i) { $checked=' checked="checked"'; }
1.492     albertel 7692: 		    $r->print("
                   7693:    <label>
1.569     bisitz   7694:        <input type='radio' name='scantron_CODE_resolution' value='use_closest_$i'$checked />
1.492     albertel 7695:        ".&mt("Use the similar CODE [_1] instead.",
                   7696: 	    "<b><tt>".$testcode."</tt></b>")."
                   7697:     </label>
                   7698:     <input type='hidden' name='scantron_CODE_closest_$i' value='$testcode' />");
1.278     albertel 7699: 		    $r->print("\n<br />");
                   7700: 		    $i++;
                   7701: 		}
1.194     albertel 7702: 	    }
                   7703: 	}
1.273     albertel 7704: 	if ($$scan_record{'scantron.CODE'}=~/\S/ ) {
1.569     bisitz   7705: 	    my $checked; if (!$i) { $checked=' checked="checked"'; }
1.492     albertel 7706: 	    $r->print("
                   7707:     <label>
1.569     bisitz   7708:         <input type='radio' name='scantron_CODE_resolution' value='use_unfound'$checked />
1.659     raeburn  7709:        ".&mt("Use the CODE [_1] that was on the paper, ignoring the error.",
1.492     albertel 7710: 	     "<b><tt>".$$scan_record{'scantron.CODE'}."</tt></b>")."
                   7711:     </label>");
1.273     albertel 7712: 	    $r->print("\n<br />");
                   7713: 	}
1.194     albertel 7714: 
1.597     wenzelju 7715: 	$r->print(&Apache::lonhtmlcommon::scripttag(<<ENDSCRIPT));
1.188     albertel 7716: function change_radio(field) {
1.190     albertel 7717:     var slct=document.scantronupload.scantron_CODE_resolution;
1.188     albertel 7718:     var i;
                   7719:     for (i=0;i<slct.length;i++) {
                   7720:         if (slct[i].value==field) { slct[i].checked=true; }
                   7721:     }
                   7722: }
                   7723: ENDSCRIPT
1.187     albertel 7724: 	my $href="/adm/pickcode?".
1.359     www      7725: 	   "form=".&escape("scantronupload").
                   7726: 	   "&scantron_format=".&escape($env{'form.scantron_format'}).
                   7727: 	   "&scantron_CODElist=".&escape($env{'form.scantron_CODElist'}).
                   7728: 	   "&curCODE=".&escape($$scan_record{'scantron.CODE'}).
                   7729: 	   "&scantron_selectfile=".&escape($env{'form.scantron_selectfile'});
1.332     albertel 7730: 	if ($env{'form.scantron_CODElist'} =~ /\S/) { 
1.492     albertel 7731: 	    $r->print("
                   7732:     <label>
                   7733:        <input type='radio' name='scantron_CODE_resolution' value='use_found' />
                   7734:        ".&mt("[_1]Select[_2] a CODE from the list of all CODEs and use it.",
                   7735: 	     "<a target='_blank' href='$href'>","</a>")."
                   7736:     </label> 
1.558     bisitz   7737:     ".&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 7738: 	    $r->print("\n<br />");
                   7739: 	}
1.492     albertel 7740: 	$r->print("
                   7741:     <label>
                   7742:        <input type='radio' name='scantron_CODE_resolution' value='use_typed' />
                   7743:        ".&mt("Use [_1] as the CODE.",
                   7744: 	     "</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 7745: 	$r->print("\n<br /><br />");
1.157     albertel 7746:     } elsif ($error eq 'doublebubble') {
1.658     bisitz   7747: 	$r->print('<p class="LC_warning">'.&mt("There have been multiple bubbles scanned for some question(s)")."</p>\n");
1.497     foxr     7748: 
                   7749: 	# The form field scantron_questions is acutally a list of line numbers.
                   7750: 	# represented by this form so:
                   7751: 
1.691     raeburn  7752: 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                   7753:                                                 $respnumlookup,$startline);
1.497     foxr     7754: 
1.157     albertel 7755: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
1.497     foxr     7756: 		  $line_list.'" />');
1.242     albertel 7757: 	$r->print($message);
1.492     albertel 7758: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading")."</p>");
1.157     albertel 7759: 	foreach my $question (@{$arg}) {
1.503     raeburn  7760: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
1.691     raeburn  7761:                                                    $scan_record, $error,
                   7762:                                                    $randomorder,$randompick,
                   7763:                                                    $respnumlookup,$startline);
1.524     raeburn  7764:             push(@lines_to_correct,@linenums);
1.157     albertel 7765: 	}
1.503     raeburn  7766:         $r->print(&verify_bubbles_checked(@lines_to_correct));
1.157     albertel 7767:     } elsif ($error eq 'missingbubble') {
1.658     bisitz   7768: 	$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 7769: 	$r->print($message);
1.492     albertel 7770: 	$r->print("<p>".&mt("Please indicate which bubble should be used for grading.")."</p>");
1.503     raeburn  7771: 	$r->print(&mt("Some questions have no scanned bubbles.")."\n");
1.497     foxr     7772: 
1.503     raeburn  7773: 	# The form field scantron_questions is actually a list of line numbers not
1.497     foxr     7774: 	# a list of question numbers. Therefore:
                   7775: 	#
1.691     raeburn  7776: 
                   7777: 	my $line_list = &questions_to_line_list($arg,$randomorder,$randompick,
                   7778:                                                 $respnumlookup,$startline);
1.497     foxr     7779: 
1.157     albertel 7780: 	$r->print('<input type="hidden" name="scantron_questions" value="'.
1.497     foxr     7781: 		  $line_list.'" />');
1.157     albertel 7782: 	foreach my $question (@{$arg}) {
1.503     raeburn  7783: 	    my @linenums = &prompt_for_corrections($r,$question,$scan_config,
1.691     raeburn  7784:                                                    $scan_record, $error,
                   7785:                                                    $randomorder,$randompick,
                   7786:                                                    $respnumlookup,$startline);
1.524     raeburn  7787:             push(@lines_to_correct,@linenums);
1.157     albertel 7788: 	}
1.503     raeburn  7789:         $r->print(&verify_bubbles_checked(@lines_to_correct));
1.157     albertel 7790:     } else {
                   7791: 	$r->print("\n<ul>");
                   7792:     }
                   7793:     $r->print("\n</li></ul>");
1.497     foxr     7794: }
                   7795: 
1.503     raeburn  7796: sub verify_bubbles_checked {
                   7797:     my (@ansnums) = @_;
                   7798:     my $ansnumstr = join('","',@ansnums);
                   7799:     my $warning = &mt("A bubble or 'No bubble' selection has not been made for one or more lines.");
1.736     damieng  7800:     &js_escape(\$warning);
1.597     wenzelju 7801:     my $output = &Apache::lonhtmlcommon::scripttag((<<ENDSCRIPT));
1.503     raeburn  7802: function verify_bubble_radio(form) {
                   7803:     var ansnumArray = new Array ("$ansnumstr");
                   7804:     var need_bubble_count = 0;
                   7805:     for (var i=0; i<ansnumArray.length; i++) {
                   7806:         if (form.elements["scantron_correct_Q_"+ansnumArray[i]].length > 1) {
                   7807:             var bubble_picked = 0; 
                   7808:             for (var j=0; j<form.elements["scantron_correct_Q_"+ansnumArray[i]].length; j++) {
                   7809:                 if (form.elements["scantron_correct_Q_"+ansnumArray[i]][j].checked == true) {
                   7810:                     bubble_picked = 1;
                   7811:                 }
                   7812:             }
                   7813:             if (bubble_picked == 0) {
                   7814:                 need_bubble_count ++;
                   7815:             }
                   7816:         }
                   7817:     }
                   7818:     if (need_bubble_count) {
                   7819:         alert("$warning");
                   7820:         return;
                   7821:     }
                   7822:     form.submit(); 
                   7823: }
                   7824: ENDSCRIPT
                   7825:     return $output;
                   7826: }
                   7827: 
1.497     foxr     7828: =pod
                   7829: 
                   7830: =item  questions_to_line_list
1.157     albertel 7831: 
1.497     foxr     7832: Converts a list of questions into a string of comma separated
                   7833: line numbers in the answer sheet used by the questions.  This is
                   7834: used to fill in the scantron_questions form field.
                   7835: 
                   7836:   Arguments:
                   7837:      questions    - Reference to an array of questions.
1.691     raeburn  7838:      randomorder  - True if randomorder in use.
                   7839:      randompick   - True if randompick in use.
                   7840:      respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   7841:                      for current line to question number used for same question
                   7842:                      in "Master Seqence" (as seen by Course Coordinator).
                   7843:      startline    - Reference to hash where key is question number (0 is first)
                   7844:                     and key is number of first bubble line for current student
                   7845:                     or code-based randompick and/or randomorder.
1.693     raeburn  7846: 
1.497     foxr     7847: =cut
                   7848: 
                   7849: 
                   7850: sub questions_to_line_list {
1.691     raeburn  7851:     my ($questions,$randomorder,$randompick,$respnumlookup,$startline) = @_;
1.497     foxr     7852:     my @lines;
                   7853: 
1.503     raeburn  7854:     foreach my $item (@{$questions}) {
                   7855:         my $question = $item;
                   7856:         my ($first,$count,$last);
                   7857:         if ($item =~ /^(\d+)\.(\d+)$/) {
                   7858:             $question = $1;
                   7859:             my $subquestion = $2;
1.691     raeburn  7860:             my $responsenum = $question-1;
                   7861:             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7862:                 $responsenum = $respnumlookup->{$question-1};
                   7863:                 if (ref($startline) eq 'HASH') {
                   7864:                     $first = $startline->{$question-1} + 1;
                   7865:                 }
                   7866:             } else {
                   7867:                 $first = $first_bubble_line{$responsenum} + 1;
                   7868:             }
                   7869:             my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
1.503     raeburn  7870:             my $subcount = 1;
                   7871:             while ($subcount<$subquestion) {
                   7872:                 $first += $subans[$subcount-1];
                   7873:                 $subcount ++;
                   7874:             }
                   7875:             $count = $subans[$subquestion-1];
                   7876:         } else {
1.691     raeburn  7877:             my $responsenum = $question-1;
                   7878:             if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7879:                 $responsenum = $respnumlookup->{$question-1};
                   7880:                 if (ref($startline) eq 'HASH') {
                   7881:                     $first = $startline->{$question-1} + 1;
                   7882:                 }
                   7883:             } else {
                   7884:                 $first = $first_bubble_line{$responsenum} + 1;
                   7885:             }
                   7886: 	    $count   = $bubble_lines_per_response{$responsenum};
1.503     raeburn  7887:         }
1.506     raeburn  7888:         $last = $first+$count-1;
1.503     raeburn  7889:         push(@lines, ($first..$last));
1.497     foxr     7890:     }
                   7891:     return join(',', @lines);
                   7892: }
                   7893: 
                   7894: =pod 
                   7895: 
                   7896: =item prompt_for_corrections
                   7897: 
                   7898: Prompts for a potentially multiline correction to the
                   7899: user's bubbling (factors out common code from scantron_get_correction
                   7900: for multi and missing bubble cases).
                   7901: 
                   7902:  Arguments:
                   7903:    $r           - Apache request object.
                   7904:    $question    - The question number to prompt for.
                   7905:    $scan_config - The scantron file configuration hash.
                   7906:    $scan_record - Reference to the hash that has the the parsed scanlines.
1.503     raeburn  7907:    $error       - Type of error
1.691     raeburn  7908:    $randomorder - True if randomorder in use.
                   7909:    $randompick  - True if randompick in use.
                   7910:    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   7911:                     for current line to question number used for same question
                   7912:                     in "Master Seqence" (as seen by Course Coordinator).
                   7913:    $startline   - Reference to hash where key is question number (0 is first)
                   7914:                   and value is number of first bubble line for current student
                   7915:                   or code-based randompick and/or randomorder.
                   7916: 
1.497     foxr     7917: 
                   7918:  Implicit inputs:
                   7919:    %bubble_lines_per_response   - Starting line numbers for each question.
                   7920:                                   Numbered from 0 (but question numbers are from
                   7921:                                   1.
                   7922:    %first_bubble_line           - Starting bubble line for each question.
1.509     raeburn  7923:    %subdivided_bubble_lines     - optionresponse, matchresponse and rankresponse 
                   7924:                                   type problems render as separate sub-questions, 
1.503     raeburn  7925:                                   in exam mode. This hash contains a 
                   7926:                                   comma-separated list of the lines per 
                   7927:                                   sub-question.
1.510     raeburn  7928:    %responsetype_per_response   - essayresponse, formularesponse,
                   7929:                                   stringresponse, imageresponse, reactionresponse,
                   7930:                                   and organicresponse type problem parts can have
1.503     raeburn  7931:                                   multiple lines per response if the weight
                   7932:                                   assigned exceeds 10.  In this case, only
                   7933:                                   one bubble per line is permitted, but more 
                   7934:                                   than one line might contain bubbles, e.g.
                   7935:                                   bubbling of: line 1 - J, line 2 - J, 
                   7936:                                   line 3 - B would assign 22 points.  
1.497     foxr     7937: 
                   7938: =cut
                   7939: 
                   7940: sub prompt_for_corrections {
1.691     raeburn  7941:     my ($r, $question, $scan_config, $scan_record, $error, $randomorder,
                   7942:         $randompick, $respnumlookup, $startline) = @_;
1.503     raeburn  7943:     my ($current_line,$lines);
                   7944:     my @linenums;
                   7945:     my $questionnum = $question;
1.691     raeburn  7946:     my ($first,$responsenum);
1.503     raeburn  7947:     if ($question =~ /^(\d+)\.(\d+)$/) {
                   7948:         $question = $1;
                   7949:         my $subquestion = $2;
1.691     raeburn  7950:         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7951:             $responsenum = $respnumlookup->{$question-1};
                   7952:             if (ref($startline) eq 'HASH') {
                   7953:                 $first = $startline->{$question-1};
                   7954:             }
                   7955:         } else {
                   7956:             $responsenum = $question-1;
1.714     raeburn  7957:             $first = $first_bubble_line{$responsenum};
1.691     raeburn  7958:         }
                   7959:         $current_line = $first + 1 ;
                   7960:         my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
1.503     raeburn  7961:         my $subcount = 1;
                   7962:         while ($subcount<$subquestion) {
                   7963:             $current_line += $subans[$subcount-1];
                   7964:             $subcount ++;
                   7965:         }
                   7966:         $lines = $subans[$subquestion-1];
                   7967:     } else {
1.691     raeburn  7968:         if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH')) {
                   7969:             $responsenum = $respnumlookup->{$question-1};
                   7970:             if (ref($startline) eq 'HASH') { 
                   7971:                 $first = $startline->{$question-1};
                   7972:             }
                   7973:         } else {
                   7974:             $responsenum = $question-1;
                   7975:             $first = $first_bubble_line{$responsenum};
                   7976:         }
                   7977:         $current_line = $first + 1;
                   7978:         $lines        = $bubble_lines_per_response{$responsenum};
1.503     raeburn  7979:     }
1.497     foxr     7980:     if ($lines > 1) {
1.503     raeburn  7981:         $r->print(&mt('The group of bubble lines below responds to a single question.').'<br />');
1.691     raeburn  7982:         if (($responsetype_per_response{$responsenum} eq 'essayresponse') ||
                   7983:             ($responsetype_per_response{$responsenum} eq 'formularesponse') ||
                   7984:             ($responsetype_per_response{$responsenum} eq 'stringresponse') ||
                   7985:             ($responsetype_per_response{$responsenum} eq 'imageresponse') ||
                   7986:             ($responsetype_per_response{$responsenum} eq 'reactionresponse') ||
                   7987:             ($responsetype_per_response{$responsenum} eq 'organicresponse')) {
1.684     bisitz   7988:             $r->print(
                   7989:                 &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)
                   7990:                .'<br /><br />'
                   7991:                .&mt('A non-zero score can be assigned to the student during bubblesheet grading by selecting a bubble in at least one line.')
                   7992:                .'<br />'
                   7993:                .&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.')
                   7994:                .'<br />'
                   7995:                .&mt("To assign a score of zero for this question, mark all lines as 'No bubble'.")
                   7996:                .'<br /><br />'
                   7997:             );
1.503     raeburn  7998:         } else {
                   7999:             $r->print(&mt("Select at most one bubble in a single line and select 'No Bubble' in all the other lines. ")."<br />");
                   8000:         }
1.497     foxr     8001:     }
                   8002:     for (my $i =0; $i < $lines; $i++) {
1.503     raeburn  8003:         my $selected = $$scan_record{"scantron.$current_line.answer"};
1.691     raeburn  8004: 	&scantron_bubble_selector($r,$scan_config,$current_line,
1.503     raeburn  8005: 	        		  $questionnum,$error,split('', $selected));
1.524     raeburn  8006:         push(@linenums,$current_line);
1.497     foxr     8007: 	$current_line++;
                   8008:     }
                   8009:     if ($lines > 1) {
                   8010: 	$r->print("<hr /><br />");
                   8011:     }
1.503     raeburn  8012:     return @linenums;
1.157     albertel 8013: }
1.423     albertel 8014: 
                   8015: =pod
                   8016: 
                   8017: =item scantron_bubble_selector
                   8018:   
                   8019:    Generates the html radiobuttons to correct a single bubble line
1.424     albertel 8020:    possibly showing the existing the selected bubbles if known
1.423     albertel 8021: 
                   8022:  Arguments:
                   8023:     $r           - Apache request object
                   8024:     $scan_config - hash from &get_scantron_config()
1.497     foxr     8025:     $line        - Number of the line being displayed.
1.503     raeburn  8026:     $questionnum - Question number (may include subquestion)
                   8027:     $error       - Type of error.
1.497     foxr     8028:     @selected    - Array of bubbles picked on this line.
1.423     albertel 8029: 
                   8030: =cut
                   8031: 
1.157     albertel 8032: sub scantron_bubble_selector {
1.503     raeburn  8033:     my ($r,$scan_config,$line,$questionnum,$error,@selected)=@_;
1.157     albertel 8034:     my $max=$$scan_config{'Qlength'};
1.274     albertel 8035: 
                   8036:     my $scmode=$$scan_config{'Qon'};
1.649     raeburn  8037:     if ($scmode eq 'number' || $scmode eq 'letter') { 
                   8038:         if (($$scan_config{'BubblesPerRow'} =~ /^\d+$/) &&
                   8039:             ($$scan_config{'BubblesPerRow'} > 0)) {
                   8040:             $max=$$scan_config{'BubblesPerRow'};
                   8041:             if (($scmode eq 'number') && ($max > 10)) {
                   8042:                 $max = 10;
                   8043:             } elsif (($scmode eq 'letter') && $max > 26) {
                   8044:                 $max = 26;
                   8045:             }
                   8046:         } else {
                   8047:             $max = 10;
                   8048:         }
                   8049:     }
1.274     albertel 8050: 
1.157     albertel 8051:     my @alphabet=('A'..'Z');
1.503     raeburn  8052:     $r->print(&Apache::loncommon::start_data_table().
                   8053:               &Apache::loncommon::start_data_table_row());
                   8054:     $r->print('<td rowspan="2" class="LC_leftcol_header">'.$line.'</td>');
1.497     foxr     8055:     for (my $i=0;$i<$max+1;$i++) {
                   8056: 	$r->print("\n".'<td align="center">');
                   8057: 	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
                   8058: 	else { $r->print('&nbsp;'); }
                   8059: 	$r->print('</td>');
                   8060:     }
1.503     raeburn  8061:     $r->print(&Apache::loncommon::end_data_table_row().
                   8062:               &Apache::loncommon::start_data_table_row());
1.497     foxr     8063:     for (my $i=0;$i<$max;$i++) {
                   8064: 	$r->print("\n".
                   8065: 		  '<td><label><input type="radio" name="scantron_correct_Q_'.
                   8066: 		  $line.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
                   8067:     }
1.503     raeburn  8068:     my $nobub_checked = ' ';
                   8069:     if ($error eq 'missingbubble') {
                   8070:         $nobub_checked = ' checked = "checked" ';
                   8071:     }
                   8072:     $r->print("\n".'<td><label><input type="radio" name="scantron_correct_Q_'.
                   8073: 	      $line.'" value="none"'.$nobub_checked.'/>'.&mt('No bubble').
                   8074:               '</label>'."\n".'<input type="hidden" name="scantron_questionnum_Q_'.
                   8075:               $line.'" value="'.$questionnum.'" /></td>');
                   8076:     $r->print(&Apache::loncommon::end_data_table_row().
                   8077:               &Apache::loncommon::end_data_table());
1.157     albertel 8078: }
                   8079: 
1.423     albertel 8080: =pod
                   8081: 
                   8082: =item num_matches
                   8083: 
1.424     albertel 8084:    Counts the number of characters that are the same between the two arguments.
                   8085: 
                   8086:  Arguments:
                   8087:    $orig - CODE from the scanline
                   8088:    $code - CODE to match against
                   8089: 
                   8090:  Returns:
                   8091:    $count - integer count of the number of same characters between the
                   8092:             two arguments
                   8093: 
1.423     albertel 8094: =cut
                   8095: 
1.194     albertel 8096: sub num_matches {
                   8097:     my ($orig,$code) = @_;
                   8098:     my @code=split(//,$code);
                   8099:     my @orig=split(//,$orig);
                   8100:     my $same=0;
                   8101:     for (my $i=0;$i<scalar(@code);$i++) {
                   8102: 	if ($code[$i] eq $orig[$i]) { $same++; }
                   8103:     }
                   8104:     return $same;
                   8105: }
                   8106: 
1.423     albertel 8107: =pod
                   8108: 
                   8109: =item scantron_get_closely_matching_CODEs
                   8110: 
1.424     albertel 8111:    Cycles through all CODEs and finds the set that has the greatest
                   8112:    number of same characters as the provided CODE
                   8113: 
                   8114:  Arguments:
                   8115:    $allcodes - hash ref returned by &get_codes()
                   8116:    $CODE     - CODE from the current scanline
                   8117: 
                   8118:  Returns:
                   8119:    2 element list
                   8120:     - first elements is number of how closely matching the best fit is 
                   8121:       (5 means best set has 5 matching characters)
                   8122:     - second element is an arrary ref containing the set of valid CODEs
                   8123:       that best fit the passed in CODE
                   8124: 
1.423     albertel 8125: =cut
                   8126: 
1.194     albertel 8127: sub scantron_get_closely_matching_CODEs {
                   8128:     my ($allcodes,$CODE)=@_;
                   8129:     my @CODEs;
                   8130:     foreach my $testcode (sort(keys(%{$allcodes}))) {
                   8131: 	push(@{$CODEs[&num_matches($CODE,$testcode)]},$testcode);
                   8132:     }
                   8133: 
                   8134:     return ($#CODEs,$CODEs[-1]);
                   8135: }
                   8136: 
1.423     albertel 8137: =pod
                   8138: 
                   8139: =item get_codes
                   8140: 
1.424     albertel 8141:    Builds a hash which has keys of all of the valid CODEs from the selected
                   8142:    set of remembered CODEs.
                   8143: 
                   8144:  Arguments:
                   8145:   $old_name - name of the set of remembered CODEs
                   8146:   $cdom     - domain of the course
                   8147:   $cnum     - internal course name
                   8148: 
                   8149:  Returns:
                   8150:   %allcodes - keys are the valid CODEs, values are all 1
                   8151: 
1.423     albertel 8152: =cut
                   8153: 
1.194     albertel 8154: sub get_codes {
1.280     foxr     8155:     my ($old_name, $cdom, $cnum) = @_;
                   8156:     if (!$old_name) {
                   8157: 	$old_name=$env{'form.scantron_CODElist'};
                   8158:     }
                   8159:     if (!$cdom) {
                   8160: 	$cdom =$env{'course.'.$env{'request.course.id'}.'.domain'};
                   8161:     }
                   8162:     if (!$cnum) {
                   8163: 	$cnum =$env{'course.'.$env{'request.course.id'}.'.num'};
                   8164:     }
1.278     albertel 8165:     my %result=&Apache::lonnet::get('CODEs',[$old_name,"type\0$old_name"],
                   8166: 				    $cdom,$cnum);
                   8167:     my %allcodes;
                   8168:     if ($result{"type\0$old_name"} eq 'number') {
                   8169: 	%allcodes=map {($_,1)} split(',',$result{$old_name});
                   8170:     } else {
                   8171: 	%allcodes=map {(&Apache::lonprintout::num_to_letters($_),1)} split(',',$result{$old_name});
                   8172:     }
1.194     albertel 8173:     return %allcodes;
                   8174: }
                   8175: 
1.423     albertel 8176: =pod
                   8177: 
                   8178: =item scantron_validate_CODE
                   8179: 
1.424     albertel 8180:    Validates all scanlines in the selected file to not have any
                   8181:    invalid or underspecified CODEs and that none of the codes are
                   8182:    duplicated if this was requested.
                   8183: 
1.423     albertel 8184: =cut
                   8185: 
1.157     albertel 8186: sub scantron_validate_CODE {
                   8187:     my ($r,$currentphase) = @_;
1.257     albertel 8188:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.186     albertel 8189:     if ($scantron_config{'CODElocation'} &&
                   8190: 	$scantron_config{'CODEstart'} &&
                   8191: 	$scantron_config{'CODElength'}) {
1.257     albertel 8192: 	if (!defined($env{'form.scantron_CODElist'})) {
1.186     albertel 8193: 	    &FIXME_blow_up()
                   8194: 	}
                   8195:     } else {
                   8196: 	return (0,$currentphase+1);
                   8197:     }
                   8198:     
                   8199:     my %usedCODEs;
                   8200: 
1.194     albertel 8201:     my %allcodes=&get_codes();
1.186     albertel 8202: 
1.582     raeburn  8203:     my $nav_error;
1.649     raeburn  8204:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the lines per response array.
1.582     raeburn  8205:     if ($nav_error) {
                   8206:         $r->print(&navmap_errormsg());
                   8207:         return(1,$currentphase);
                   8208:     }
1.447     foxr     8209: 
1.186     albertel 8210:     my ($scanlines,$scan_data)=&scantron_getfile();
                   8211:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8212: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.186     albertel 8213: 	if ($line=~/^[\s\cz]*$/) { next; }
                   8214: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
                   8215: 						 $scan_data);
                   8216: 	my $CODE=$$scan_record{'scantron.CODE'};
                   8217: 	my $error=0;
1.224     albertel 8218: 	if (!&Apache::lonnet::validCODE($CODE)) {
                   8219: 	    &scantron_get_correction($r,$i,$scan_record,
                   8220: 				     \%scantron_config,
                   8221: 				     $line,'incorrectCODE',\%allcodes);
                   8222: 	    return(1,$currentphase);
                   8223: 	}
1.221     albertel 8224: 	if (%allcodes && !exists($allcodes{$CODE}) 
                   8225: 	    && !$$scan_record{'scantron.useCODE'}) {
1.186     albertel 8226: 	    &scantron_get_correction($r,$i,$scan_record,
                   8227: 				     \%scantron_config,
1.194     albertel 8228: 				     $line,'incorrectCODE',\%allcodes);
                   8229: 	    return(1,$currentphase);
1.186     albertel 8230: 	}
1.214     albertel 8231: 	if (exists($usedCODEs{$CODE}) 
1.257     albertel 8232: 	    && $env{'form.scantron_CODEunique'} eq 'yes'
1.192     albertel 8233: 	    && !$$scan_record{'scantron.CODE_ignore_dup'}) {
1.186     albertel 8234: 	    &scantron_get_correction($r,$i,$scan_record,
                   8235: 				     \%scantron_config,
1.194     albertel 8236: 				     $line,'duplicateCODE',$usedCODEs{$CODE});
                   8237: 	    return(1,$currentphase);
1.186     albertel 8238: 	}
1.524     raeburn  8239: 	push(@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'});
1.186     albertel 8240:     }
1.157     albertel 8241:     return (0,$currentphase+1);
                   8242: }
                   8243: 
1.423     albertel 8244: =pod
                   8245: 
                   8246: =item scantron_validate_doublebubble
                   8247: 
1.424     albertel 8248:    Validates all scanlines in the selected file to not have any
                   8249:    bubble lines with multiple bubbles marked.
                   8250: 
1.423     albertel 8251: =cut
                   8252: 
1.157     albertel 8253: sub scantron_validate_doublebubble {
                   8254:     my ($r,$currentphase) = @_;
                   8255:     #get student info
                   8256:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8257:     my %idmap=&username_to_idmap($classlist);
1.691     raeburn  8258:     my (undef,undef,$sequence)=
                   8259:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
1.157     albertel 8260: 
                   8261:     #get scantron line setup
1.257     albertel 8262:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 8263:     my ($scanlines,$scan_data)=&scantron_getfile();
1.691     raeburn  8264: 
                   8265:     my $navmap = Apache::lonnavmaps::navmap->new();
                   8266:     unless (ref($navmap)) {
                   8267:         $r->print(&navmap_errormsg());
                   8268:         return(1,$currentphase);
                   8269:     }
                   8270:     my $map=$navmap->getResourceByUrl($sequence);
                   8271:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8272:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   8273:         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
                   8274:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8275: 
1.583     raeburn  8276:     my $nav_error;
1.691     raeburn  8277:     if (ref($map)) {
                   8278:         $randomorder = $map->randomorder();
                   8279:         $randompick = $map->randompick();
                   8280:         if ($randomorder || $randompick) {
                   8281:             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8282:             if ($nav_error) {
                   8283:                 $r->print(&navmap_errormsg());
                   8284:                 return(1,$currentphase);
                   8285:             }
                   8286:             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   8287:                                     \%grader_randomlists_by_symb,$bubbles_per_row);
                   8288:         }
                   8289:     } else {
                   8290:         $r->print(&navmap_errormsg());
                   8291:         return(1,$currentphase);
                   8292:     }
                   8293: 
1.649     raeburn  8294:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # parse needs the bubble line array.
1.583     raeburn  8295:     if ($nav_error) {
                   8296:         $r->print(&navmap_errormsg());
                   8297:         return(1,$currentphase);
                   8298:     }
1.447     foxr     8299: 
1.157     albertel 8300:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8301: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8302: 	if ($line=~/^[\s\cz]*$/) { next; }
                   8303: 	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
1.691     raeburn  8304: 						 $scan_data,undef,\%idmap,$randomorder,
                   8305:                                                  $randompick,$sequence,\@master_seq,
                   8306:                                                  \%symb_to_resource,\%grader_partids_by_symb,
                   8307:                                                  \%orderedforcode,\%respnumlookup,\%startline);
1.157     albertel 8308: 	if (!defined($$scan_record{'scantron.doubleerror'})) { next; }
                   8309: 	&scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line,
                   8310: 				 'doublebubble',
1.691     raeburn  8311: 				 $$scan_record{'scantron.doubleerror'},
                   8312:                                  $randomorder,$randompick,\%respnumlookup,\%startline);
1.157     albertel 8313:     	return (1,$currentphase);
                   8314:     }
                   8315:     return (0,$currentphase+1);
                   8316: }
                   8317: 
1.423     albertel 8318: 
1.503     raeburn  8319: sub scantron_get_maxbubble {
1.649     raeburn  8320:     my ($nav_error,$scantron_config) = @_;
1.257     albertel 8321:     if (defined($env{'form.scantron_maxbubble'}) &&
                   8322: 	$env{'form.scantron_maxbubble'}) {
1.447     foxr     8323: 	&restore_bubble_lines();
1.257     albertel 8324: 	return $env{'form.scantron_maxbubble'};
1.191     albertel 8325:     }
1.330     albertel 8326: 
1.447     foxr     8327:     my (undef, undef, $sequence) =
1.257     albertel 8328: 	&Apache::lonnet::decode_symb($env{'form.selectpage'});
1.330     albertel 8329: 
1.447     foxr     8330:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  8331:     unless (ref($navmap)) {
                   8332:         if (ref($nav_error)) {
                   8333:             $$nav_error = 1;
                   8334:         }
1.591     raeburn  8335:         return;
1.582     raeburn  8336:     }
1.191     albertel 8337:     my $map=$navmap->getResourceByUrl($sequence);
                   8338:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.649     raeburn  8339:     my $bubbles_per_row = &bubblesheet_bubbles_per_row($scantron_config);
1.330     albertel 8340: 
                   8341:     &Apache::lonxml::clear_problem_counter();
                   8342: 
1.557     raeburn  8343:     my $uname       = $env{'user.name'};
                   8344:     my $udom        = $env{'user.domain'};
1.435     foxr     8345:     my $cid         = $env{'request.course.id'};
                   8346:     my $total_lines = 0;
                   8347:     %bubble_lines_per_response = ();
1.447     foxr     8348:     %first_bubble_line         = ();
1.503     raeburn  8349:     %subdivided_bubble_lines   = ();
                   8350:     %responsetype_per_response = ();
1.691     raeburn  8351:     %masterseq_id_responsenum  = ();
1.554     raeburn  8352: 
1.447     foxr     8353:     my $response_number = 0;
                   8354:     my $bubble_line     = 0;
1.191     albertel 8355:     foreach my $resource (@resources) {
1.691     raeburn  8356:         my $resid = $resource->id(); 
1.672     raeburn  8357:         my ($analysis,$parts) = &scantron_partids_tograde($resource,$cid,$uname,
                   8358:                                                           $udom,undef,$bubbles_per_row);
1.542     raeburn  8359:         if ((ref($analysis) eq 'HASH') && (ref($parts) eq 'ARRAY')) {
                   8360: 	    foreach my $part_id (@{$parts}) {
                   8361:                 my $lines;
                   8362: 
                   8363: 	        # TODO - make this a persistent hash not an array.
                   8364: 
                   8365:                 # optionresponse, matchresponse and rankresponse type items 
                   8366:                 # render as separate sub-questions in exam mode.
                   8367:                 if (($analysis->{$part_id.'.type'} eq 'optionresponse') ||
                   8368:                     ($analysis->{$part_id.'.type'} eq 'matchresponse') ||
                   8369:                     ($analysis->{$part_id.'.type'} eq 'rankresponse')) {
                   8370:                     my ($numbub,$numshown);
                   8371:                     if ($analysis->{$part_id.'.type'} eq 'optionresponse') {
                   8372:                         if (ref($analysis->{$part_id.'.options'}) eq 'ARRAY') {
                   8373:                             $numbub = scalar(@{$analysis->{$part_id.'.options'}});
                   8374:                         }
                   8375:                     } elsif ($analysis->{$part_id.'.type'} eq 'matchresponse') {
                   8376:                         if (ref($analysis->{$part_id.'.items'}) eq 'ARRAY') {
                   8377:                             $numbub = scalar(@{$analysis->{$part_id.'.items'}});
                   8378:                         }
                   8379:                     } elsif ($analysis->{$part_id.'.type'} eq 'rankresponse') {
                   8380:                         if (ref($analysis->{$part_id.'.foils'}) eq 'ARRAY') {
                   8381:                             $numbub = scalar(@{$analysis->{$part_id.'.foils'}});
                   8382:                         }
                   8383:                     }
                   8384:                     if (ref($analysis->{$part_id.'.shown'}) eq 'ARRAY') {
                   8385:                         $numshown = scalar(@{$analysis->{$part_id.'.shown'}});
                   8386:                     }
1.649     raeburn  8387:                     my $bubbles_per_row =
                   8388:                         &bubblesheet_bubbles_per_row($scantron_config);
                   8389:                     my $inner_bubble_lines = int($numbub/$bubbles_per_row);
                   8390:                     if (($numbub % $bubbles_per_row) != 0) {
1.542     raeburn  8391:                         $inner_bubble_lines++;
                   8392:                     }
                   8393:                     for (my $i=0; $i<$numshown; $i++) {
                   8394:                         $subdivided_bubble_lines{$response_number} .= 
                   8395:                             $inner_bubble_lines.',';
                   8396:                     }
                   8397:                     $subdivided_bubble_lines{$response_number} =~ s/,$//;
                   8398:                     $lines = $numshown * $inner_bubble_lines;
                   8399:                 } else {
                   8400:                     $lines = $analysis->{"$part_id.bubble_lines"};
1.649     raeburn  8401:                 }
1.542     raeburn  8402: 
                   8403:                 $first_bubble_line{$response_number} = $bubble_line;
                   8404: 	        $bubble_lines_per_response{$response_number} = $lines;
                   8405:                 $responsetype_per_response{$response_number} = 
                   8406:                     $analysis->{$part_id.'.type'};
1.691     raeburn  8407:                 $masterseq_id_responsenum{$resid.'_'.$part_id} = $response_number;  
1.542     raeburn  8408: 	        $response_number++;
                   8409: 
                   8410: 	        $bubble_line +=  $lines;
                   8411: 	        $total_lines +=  $lines;
                   8412: 	    }
                   8413:         }
                   8414:     }
1.552     raeburn  8415:     &Apache::lonnet::delenv('scantron.');
1.542     raeburn  8416: 
                   8417:     &save_bubble_lines();
                   8418:     $env{'form.scantron_maxbubble'} =
                   8419: 	$total_lines;
                   8420:     return $env{'form.scantron_maxbubble'};
                   8421: }
1.523     raeburn  8422: 
1.649     raeburn  8423: sub bubblesheet_bubbles_per_row {
                   8424:     my ($scantron_config) = @_;
                   8425:     my $bubbles_per_row;
                   8426:     if (ref($scantron_config) eq 'HASH') {
                   8427:         $bubbles_per_row = $scantron_config->{'BubblesPerRow'};
                   8428:     }
                   8429:     if ((!$bubbles_per_row) || ($bubbles_per_row < 1)) {
                   8430:         $bubbles_per_row = 10;
                   8431:     }
                   8432:     return $bubbles_per_row;
                   8433: }
                   8434: 
1.157     albertel 8435: sub scantron_validate_missingbubbles {
                   8436:     my ($r,$currentphase) = @_;
                   8437:     #get student info
                   8438:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8439:     my %idmap=&username_to_idmap($classlist);
1.691     raeburn  8440:     my (undef,undef,$sequence)=
                   8441:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
1.157     albertel 8442: 
                   8443:     #get scantron line setup
1.257     albertel 8444:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.157     albertel 8445:     my ($scanlines,$scan_data)=&scantron_getfile();
1.691     raeburn  8446: 
                   8447:     my $navmap = Apache::lonnavmaps::navmap->new();
                   8448:     unless (ref($navmap)) {
                   8449:         $r->print(&navmap_errormsg());
                   8450:         return(1,$currentphase);
                   8451:     }
                   8452: 
                   8453:     my $map=$navmap->getResourceByUrl($sequence);
                   8454:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8455:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   8456:         %grader_randomlists_by_symb,%orderedforcode,%respnumlookup,%startline);
                   8457:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8458: 
1.582     raeburn  8459:     my $nav_error;
1.691     raeburn  8460:     if (ref($map)) {
                   8461:         $randomorder = $map->randomorder();
                   8462:         $randompick = $map->randompick();
                   8463:         if ($randomorder || $randompick) {
                   8464:             $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8465:             if ($nav_error) {
                   8466:                 $r->print(&navmap_errormsg());
                   8467:                 return(1,$currentphase);
                   8468:             }
                   8469:             &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   8470:                                     \%grader_randomlists_by_symb,$bubbles_per_row);
                   8471:         }
                   8472:     } else {
                   8473:         $r->print(&navmap_errormsg());
                   8474:         return(1,$currentphase);
                   8475:     }
                   8476: 
                   8477: 
1.649     raeburn  8478:     my $max_bubble=&scantron_get_maxbubble(\$nav_error,\%scantron_config);
1.582     raeburn  8479:     if ($nav_error) {
1.691     raeburn  8480:         $r->print(&navmap_errormsg());
1.693     raeburn  8481:         return(1,$currentphase);
1.582     raeburn  8482:     }
1.691     raeburn  8483: 
1.157     albertel 8484:     if (!$max_bubble) { $max_bubble=2**31; }
                   8485:     for (my $i=0;$i<=$scanlines->{'count'};$i++) {
1.200     albertel 8486: 	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8487: 	if ($line=~/^[\s\cz]*$/) { next; }
1.691     raeburn  8488: 	my $scan_record =
                   8489:             &scantron_parse_scanline($line,$i,\%scantron_config,$scan_data,undef,\%idmap,
                   8490: 				     $randomorder,$randompick,$sequence,\@master_seq,
                   8491:                                      \%symb_to_resource,\%grader_partids_by_symb,
                   8492:                                      \%orderedforcode,\%respnumlookup,\%startline);
1.157     albertel 8493: 	if (!defined($$scan_record{'scantron.missingerror'})) { next; }
                   8494: 	my @to_correct;
1.470     foxr     8495: 	
                   8496: 	# Probably here's where the error is...
                   8497: 
1.157     albertel 8498: 	foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) {
1.505     raeburn  8499:             my $lastbubble;
                   8500:             if ($missing =~ /^(\d+)\.(\d+)$/) {
                   8501:                my $question = $1;
                   8502:                my $subquestion = $2;
1.691     raeburn  8503:                my ($first,$responsenum);
                   8504:                if ($randomorder || $randompick) {
                   8505:                    $responsenum = $respnumlookup{$question-1};
                   8506:                    $first = $startline{$question-1};
                   8507:                } else {
                   8508:                    $responsenum = $question-1; 
                   8509:                    $first = $first_bubble_line{$responsenum};
                   8510:                }
                   8511:                if (!defined($first)) { next; }
                   8512:                my @subans = split(/,/,$subdivided_bubble_lines{$responsenum});
1.505     raeburn  8513:                my $subcount = 1;
                   8514:                while ($subcount<$subquestion) {
                   8515:                    $first += $subans[$subcount-1];
                   8516:                    $subcount ++;
                   8517:                }
                   8518:                my $count = $subans[$subquestion-1];
                   8519:                $lastbubble = $first + $count;
                   8520:             } else {
1.691     raeburn  8521:                my ($first,$responsenum);
                   8522:                if ($randomorder || $randompick) {
                   8523:                    $responsenum = $respnumlookup{$missing-1};
                   8524:                    $first = $startline{$missing-1};
                   8525:                } else {
                   8526:                    $responsenum = $missing-1;
                   8527:                    $first = $first_bubble_line{$responsenum};
                   8528:                }
                   8529:                if (!defined($first)) { next; }
                   8530:                $lastbubble = $first + $bubble_lines_per_response{$responsenum};
1.505     raeburn  8531:             }
                   8532:             if ($lastbubble > $max_bubble) { next; }
1.157     albertel 8533: 	    push(@to_correct,$missing);
                   8534: 	}
                   8535: 	if (@to_correct) {
                   8536: 	    &scantron_get_correction($r,$i,$scan_record,\%scantron_config,
1.691     raeburn  8537: 				     $line,'missingbubble',\@to_correct,
                   8538:                                      $randomorder,$randompick,\%respnumlookup,
                   8539:                                      \%startline);
1.157     albertel 8540: 	    return (1,$currentphase);
                   8541: 	}
                   8542: 
                   8543:     }
                   8544:     return (0,$currentphase+1);
                   8545: }
                   8546: 
1.663     raeburn  8547: sub hand_bubble_option {
                   8548:     my (undef, undef, $sequence) =
                   8549:         &Apache::lonnet::decode_symb($env{'form.selectpage'});
                   8550:     return if ($sequence eq '');
                   8551:     my $navmap = Apache::lonnavmaps::navmap->new();
                   8552:     unless (ref($navmap)) {
                   8553:         return;
                   8554:     }
                   8555:     my $needs_hand_bubbles;
                   8556:     my $map=$navmap->getResourceByUrl($sequence);
                   8557:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
                   8558:     foreach my $res (@resources) {
                   8559:         if (ref($res)) {
                   8560:             if ($res->is_problem()) {
                   8561:                 my $partlist = $res->parts();
                   8562:                 foreach my $part (@{ $partlist }) {
                   8563:                     my @types = $res->responseType($part);
                   8564:                     if (grep(/^(chem|essay|image|formula|math|string|functionplot)$/,@types)) {
                   8565:                         $needs_hand_bubbles = 1;
                   8566:                         last;
                   8567:                     }
                   8568:                 }
                   8569:             }
                   8570:         }
                   8571:     }
                   8572:     if ($needs_hand_bubbles) {
                   8573:         my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
                   8574:         my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
                   8575:         return &mt('The sequence to be graded contains response types which are handgraded.').'<p>'.
                   8576:                &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 />').
                   8577:                '<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;'.
1.722     raeburn  8578:                '<label><input type="radio" name="scantron_lastbubblepoints" value="0" />'.&mt('0 points').'</label></p>';
1.663     raeburn  8579:     }
                   8580:     return;
                   8581: }
1.423     albertel 8582: 
1.82      albertel 8583: sub scantron_process_students {
1.608     www      8584:     my ($r,$symb) = @_;
1.513     foxr     8585: 
1.257     albertel 8586:     my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($env{'form.selectpage'});
1.513     foxr     8587:     if (!$symb) {
                   8588: 	return '';
                   8589:     }
1.324     albertel 8590:     my $default_form_data=&defaultFormData($symb);
1.82      albertel 8591: 
1.257     albertel 8592:     my %scantron_config=&get_scantron_config($env{'form.scantron_format'});
1.691     raeburn  8593:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config); 
1.157     albertel 8594:     my ($scanlines,$scan_data)=&scantron_getfile();
1.82      albertel 8595:     my $classlist=&Apache::loncoursedata::get_classlist();
                   8596:     my %idmap=&username_to_idmap($classlist);
1.132     bowersj2 8597:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  8598:     unless (ref($navmap)) {
                   8599:         $r->print(&navmap_errormsg());
                   8600:         return '';
1.691     raeburn  8601:     }
1.83      albertel 8602:     my $map=$navmap->getResourceByUrl($sequence);
1.691     raeburn  8603:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
1.693     raeburn  8604:         %grader_randomlists_by_symb);
1.677     raeburn  8605:     if (ref($map)) {
                   8606:         $randomorder = $map->randomorder();
1.689     raeburn  8607:         $randompick = $map->randompick();
1.691     raeburn  8608:     } else {
                   8609:         $r->print(&navmap_errormsg());
                   8610:         return '';
1.677     raeburn  8611:     }
1.691     raeburn  8612:     my $nav_error;
1.83      albertel 8613:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.691     raeburn  8614:     if ($randomorder || $randompick) {
                   8615:         $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   8616:         if ($nav_error) {
                   8617:             $r->print(&navmap_errormsg());
                   8618:             return '';
                   8619:         }
                   8620:     }
1.557     raeburn  8621:     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
1.649     raeburn  8622:                             \%grader_randomlists_by_symb,$bubbles_per_row);
1.557     raeburn  8623: 
1.554     raeburn  8624:     my ($uname,$udom);
1.82      albertel 8625:     my $result= <<SCANTRONFORM;
1.81      albertel 8626: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="scantronupload">
                   8627:   <input type="hidden" name="command" value="scantron_configphase" />
                   8628:   $default_form_data
                   8629: SCANTRONFORM
1.82      albertel 8630:     $r->print($result);
                   8631: 
                   8632:     my @delayqueue;
1.542     raeburn  8633:     my (%completedstudents,%scandata);
1.140     albertel 8634:     
1.520     www      8635:     my $lock=&Apache::lonnet::set_lock(&mt('Grading bubblesheet exam'));
1.200     albertel 8636:     my $count=&get_todo_count($scanlines,$scan_data);
1.667     www      8637:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
                   8638:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
1.542     raeburn  8639:     $r->print('<br />');
1.140     albertel 8640:     my $start=&Time::HiRes::time();
1.158     albertel 8641:     my $i=-1;
1.542     raeburn  8642:     my $started;
1.447     foxr     8643: 
1.649     raeburn  8644:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
1.582     raeburn  8645:     if ($nav_error) {
                   8646:         $r->print(&navmap_errormsg());
                   8647:         return '';
                   8648:     }
                   8649: 
1.513     foxr     8650:     # If an ssi failed in scantron_get_maxbubble, put an error message out to
                   8651:     # the user and return.
                   8652: 
                   8653:     if ($ssi_error) {
                   8654: 	$r->print("</form>");
                   8655: 	&ssi_print_error($r);
1.520     www      8656:         &Apache::lonnet::remove_lock($lock);
1.513     foxr     8657: 	return '';		# Dunno why the other returns return '' rather than just returning.
                   8658:     }
1.447     foxr     8659: 
1.542     raeburn  8660:     my %lettdig = &letter_to_digits();
                   8661:     my $numletts = scalar(keys(%lettdig));
1.691     raeburn  8662:     my %orderedforcode;
1.542     raeburn  8663: 
1.157     albertel 8664:     while ($i<$scanlines->{'count'}) {
                   8665:  	($uname,$udom)=('','');
                   8666:  	$i++;
1.200     albertel 8667:  	my $line=&scantron_get_line($scanlines,$scan_data,$i);
1.157     albertel 8668:  	if ($line=~/^[\s\cz]*$/) { next; }
1.200     albertel 8669: 	if ($started) {
1.667     www      8670: 	    &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1.200     albertel 8671: 	}
                   8672: 	$started=1;
1.691     raeburn  8673:         my %respnumlookup = ();
                   8674:         my %startline = ();
                   8675:         my $total;
1.157     albertel 8676:  	my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config,
1.691     raeburn  8677:                                                  $scan_data,undef,\%idmap,$randomorder,
                   8678:                                                  $randompick,$sequence,\@master_seq,
                   8679:                                                  \%symb_to_resource,\%grader_partids_by_symb,
                   8680:                                                  \%orderedforcode,\%respnumlookup,\%startline,
                   8681:                                                  \$total);
1.157     albertel 8682:  	unless ($uname=&scantron_find_student($scan_record,$scan_data,
                   8683:  					      \%idmap,$i)) {
                   8684:   	    &scantron_add_delay(\@delayqueue,$line,
                   8685:  				'Unable to find a student that matches',1);
                   8686:  	    next;
                   8687:   	}
                   8688:  	if (exists $completedstudents{$uname}) {
                   8689:  	    &scantron_add_delay(\@delayqueue,$line,
                   8690:  				'Student '.$uname.' has multiple sheets',2);
                   8691:  	    next;
                   8692:  	}
1.677     raeburn  8693:         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
                   8694:         my $user = $uname.':'.$usec;
1.157     albertel 8695:   	($uname,$udom)=split(/:/,$uname);
1.330     albertel 8696: 
1.677     raeburn  8697:         my $scancode;
                   8698:         if ((exists($scan_record->{'scantron.CODE'})) &&
                   8699:             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
                   8700:             $scancode = $scan_record->{'scantron.CODE'};
                   8701:         } else {
                   8702:             $scancode = '';
                   8703:         }
                   8704: 
                   8705:         my @mapresources = @resources;
1.689     raeburn  8706:         if ($randomorder || $randompick) {
1.678     raeburn  8707:             @mapresources = 
1.691     raeburn  8708:                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                   8709:                              \%orderedforcode);
1.677     raeburn  8710:         }
1.586     raeburn  8711:         my (%partids_by_symb,$res_error);
1.677     raeburn  8712:         foreach my $resource (@mapresources) {
1.586     raeburn  8713:             my $ressymb;
                   8714:             if (ref($resource)) {
                   8715:                 $ressymb = $resource->symb();
                   8716:             } else {
                   8717:                 $res_error = 1;
                   8718:                 last;
                   8719:             }
1.557     raeburn  8720:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   8721:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
1.741     raeburn  8722:                 my $currcode;
                   8723:                 if (exists($grader_randomlists_by_symb{$ressymb})) {
                   8724:                     $currcode = $scancode;
                   8725:                 }
1.557     raeburn  8726:                 my ($analysis,$parts) =
1.672     raeburn  8727:                     &scantron_partids_tograde($resource,$env{'request.course.id'},
1.741     raeburn  8728:                                               $uname,$udom,undef,$bubbles_per_row,
                   8729:                                               $currcode);
1.557     raeburn  8730:                 $partids_by_symb{$ressymb} = $parts;
                   8731:             } else {
                   8732:                 $partids_by_symb{$ressymb} = $grader_partids_by_symb{$ressymb};
                   8733:             }
1.554     raeburn  8734:         }
                   8735: 
1.586     raeburn  8736:         if ($res_error) {
                   8737:             &scantron_add_delay(\@delayqueue,$line,
                   8738:                                 'An error occurred while grading student '.$uname,2);
                   8739:             next;
                   8740:         }
                   8741: 
1.330     albertel 8742: 	&Apache::lonxml::clear_problem_counter();
1.514     raeburn  8743:   	&Apache::lonnet::appenv($scan_record);
1.376     albertel 8744: 
                   8745: 	if (&scantron_clear_skip($scanlines,$scan_data,$i)) {
                   8746: 	    &scantron_putfile($scanlines,$scan_data);
                   8747: 	}
1.161     albertel 8748: 	
1.542     raeburn  8749:         if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
1.677     raeburn  8750:                                    \@mapresources,\%partids_by_symb,
1.691     raeburn  8751:                                    $bubbles_per_row,$randomorder,$randompick,
                   8752:                                    \%respnumlookup,\%startline) 
                   8753:             eq 'ssi_error') {
1.542     raeburn  8754:             $ssi_error = 0; # So end of handler error message does not trigger.
                   8755:             $r->print("</form>");
                   8756:             &ssi_print_error($r);
                   8757:             &Apache::lonnet::remove_lock($lock);
                   8758:             return '';      # Why return ''?  Beats me.
                   8759:         }
1.513     foxr     8760: 
1.692     raeburn  8761:         if (($scancode) && ($randomorder || $randompick)) {
                   8762:             my $parmresult =
                   8763:                 &Apache::lonparmset::storeparm_by_symb($symb,
                   8764:                                                        '0_examcode',2,$scancode,
                   8765:                                                        'string_examcode',$uname,
                   8766:                                                        $udom);
                   8767:         }
1.140     albertel 8768: 	$completedstudents{$uname}={'line'=>$line};
1.542     raeburn  8769:         if ($env{'form.verifyrecord'}) {
                   8770:             my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
1.691     raeburn  8771:             if ($randompick) {
                   8772:                 if ($total) {
                   8773:                     $lastpos = $total*$scantron_config{'Qlength'};
                   8774:                 }
                   8775:             }
                   8776: 
1.542     raeburn  8777:             my $studentdata = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
                   8778:             chomp($studentdata);
                   8779:             $studentdata =~ s/\r$//;
                   8780:             my $studentrecord = '';
                   8781:             my $counter = -1;
1.677     raeburn  8782:             foreach my $resource (@mapresources) {
1.554     raeburn  8783:                 my $ressymb = $resource->symb();
1.542     raeburn  8784:                 ($counter,my $recording) =
                   8785:                     &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
1.554     raeburn  8786:                                              $counter,$studentdata,$partids_by_symb{$ressymb},
1.691     raeburn  8787:                                              \%scantron_config,\%lettdig,$numletts,$randomorder,
                   8788:                                              $randompick,\%respnumlookup,\%startline);
1.542     raeburn  8789:                 $studentrecord .= $recording;
                   8790:             }
                   8791:             if ($studentrecord ne $studentdata) {
1.554     raeburn  8792:                 &Apache::lonxml::clear_problem_counter();
                   8793:                 if (&grade_student_bubbles($r,$uname,$udom,$scan_record,$scancode,
1.677     raeburn  8794:                                            \@mapresources,\%partids_by_symb,
1.691     raeburn  8795:                                            $bubbles_per_row,$randomorder,$randompick,
                   8796:                                            \%respnumlookup,\%startline) 
                   8797:                     eq 'ssi_error') {
1.554     raeburn  8798:                     $ssi_error = 0; # So end of handler error message does not trigger.
                   8799:                     $r->print("</form>");
                   8800:                     &ssi_print_error($r);
                   8801:                     &Apache::lonnet::remove_lock($lock);
                   8802:                     delete($completedstudents{$uname});
                   8803:                     return '';
                   8804:                 }
1.542     raeburn  8805:                 $counter = -1;
                   8806:                 $studentrecord = '';
1.677     raeburn  8807:                 foreach my $resource (@mapresources) {
1.554     raeburn  8808:                     my $ressymb = $resource->symb();
1.542     raeburn  8809:                     ($counter,my $recording) =
                   8810:                         &verify_scantron_grading($resource,$udom,$uname,$env{'request.course.id'},
1.554     raeburn  8811:                                                  $counter,$studentdata,$partids_by_symb{$ressymb},
1.691     raeburn  8812:                                                  \%scantron_config,\%lettdig,$numletts,
                   8813:                                                  $randomorder,$randompick,\%respnumlookup,
                   8814:                                                  \%startline);
1.542     raeburn  8815:                     $studentrecord .= $recording;
                   8816:                 }
                   8817:                 if ($studentrecord ne $studentdata) {
1.658     bisitz   8818:                     $r->print('<p><span class="LC_warning">');
1.542     raeburn  8819:                     if ($scancode eq '') {
1.658     bisitz   8820:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2].',
1.542     raeburn  8821:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'}));
                   8822:                     } else {
1.658     bisitz   8823:                         $r->print(&mt('Mismatch grading bubblesheet for user: [_1] with ID: [_2] and CODE: [_3].',
1.542     raeburn  8824:                                   $uname.':'.$udom,$scan_record->{'scantron.ID'},$scancode));
                   8825:                     }
                   8826:                     $r->print('</span><br />'.&Apache::loncommon::start_data_table()."\n".
                   8827:                               &Apache::loncommon::start_data_table_header_row()."\n".
                   8828:                               '<th>'.&mt('Source').'</th><th>'.&mt('Bubbled responses').'</th>'.
                   8829:                               &Apache::loncommon::end_data_table_header_row()."\n".
                   8830:                               &Apache::loncommon::start_data_table_row().
1.658     bisitz   8831:                               '<td>'.&mt('Bubblesheet').'</td>'.
1.707     bisitz   8832:                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentdata.'</tt></span></td>'.
1.542     raeburn  8833:                               &Apache::loncommon::end_data_table_row().
                   8834:                               &Apache::loncommon::start_data_table_row().
1.658     bisitz   8835:                               '<td>'.&mt('Stored submissions').'</td>'.
1.707     bisitz   8836:                               '<td><span class="LC_nobreak" style="white-space: pre;"><tt>'.$studentrecord.'</tt></span></td>'."\n".
1.542     raeburn  8837:                               &Apache::loncommon::end_data_table_row().
                   8838:                               &Apache::loncommon::end_data_table().'</p>');
                   8839:                 } else {
                   8840:                     $r->print('<br /><span class="LC_warning">'.
                   8841:                              &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 />'.
                   8842:                              &mt("As a consequence, this user's submission history records two tries.").
                   8843:                                  '</span><br />');
                   8844:                 }
                   8845:             }
                   8846:         }
1.543     raeburn  8847:         if (&Apache::loncommon::connection_aborted($r)) { last; }
1.140     albertel 8848:     } continue {
1.330     albertel 8849: 	&Apache::lonxml::clear_problem_counter();
1.552     raeburn  8850: 	&Apache::lonnet::delenv('scantron.');
1.82      albertel 8851:     }
1.140     albertel 8852:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
1.520     www      8853:     &Apache::lonnet::remove_lock($lock);
1.172     albertel 8854: #    my $lasttime = &Time::HiRes::time()-$start;
                   8855: #    $r->print("<p>took $lasttime</p>");
1.140     albertel 8856: 
1.200     albertel 8857:     $r->print("</form>");
1.157     albertel 8858:     return '';
1.75      albertel 8859: }
1.157     albertel 8860: 
1.557     raeburn  8861: sub graders_resources_pass {
1.649     raeburn  8862:     my ($resources,$grader_partids_by_symb,$grader_randomlists_by_symb,
                   8863:         $bubbles_per_row) = @_;
1.557     raeburn  8864:     if ((ref($resources) eq 'ARRAY') && (ref($grader_partids_by_symb)) && 
                   8865:         (ref($grader_randomlists_by_symb) eq 'HASH')) {
                   8866:         foreach my $resource (@{$resources}) {
                   8867:             my $ressymb = $resource->symb();
                   8868:             my ($analysis,$parts) =
                   8869:                 &scantron_partids_tograde($resource,$env{'request.course.id'},
1.672     raeburn  8870:                                           $env{'user.name'},$env{'user.domain'},
                   8871:                                           1,$bubbles_per_row);
1.557     raeburn  8872:             $grader_partids_by_symb->{$ressymb} = $parts;
                   8873:             if (ref($analysis) eq 'HASH') {
                   8874:                 if (ref($analysis->{'parts_withrandomlist'}) eq 'ARRAY') {
                   8875:                     $grader_randomlists_by_symb->{$ressymb} =
                   8876:                         $analysis->{'parts_withrandomlist'};
                   8877:                 }
                   8878:             }
                   8879:         }
                   8880:     }
                   8881:     return;
                   8882: }
                   8883: 
1.678     raeburn  8884: =pod
                   8885: 
                   8886: =item users_order
                   8887: 
                   8888:   Returns array of resources in current map, ordered based on either CODE,
                   8889:   if this is a CODEd exam, or based on student's identity if this is a 
                   8890:   "NAMEd" exam.
                   8891: 
1.691     raeburn  8892:   Should be used when randomorder and/or randompick applied when the 
                   8893:   corresponding exam was printed, prior to students completing bubblesheets 
                   8894:   for the version of the exam the student received.
1.678     raeburn  8895: 
                   8896: =cut
                   8897: 
                   8898: sub users_order  {
1.691     raeburn  8899:     my ($user,$scancode,$mapurl,$master_seq,$symb_to_resource,$orderedforcode) = @_;
1.678     raeburn  8900:     my @mapresources;
1.691     raeburn  8901:     unless ((ref($master_seq) eq 'ARRAY') && (ref($symb_to_resource) eq 'HASH')) {
1.678     raeburn  8902:         return @mapresources;
1.691     raeburn  8903:     }
                   8904:     if ($scancode) {
                   8905:         if ((ref($orderedforcode) eq 'HASH') && (ref($orderedforcode->{$scancode}) eq 'ARRAY')) {
                   8906:             @mapresources = @{$orderedforcode->{$scancode}};
                   8907:         } else {
                   8908:             $env{'form.CODE'} = $scancode;
                   8909:             my $actual_seq =
                   8910:                 &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                   8911:                                                                $master_seq,
                   8912:                                                                $user,$scancode,1);
                   8913:             if (ref($actual_seq) eq 'ARRAY') {
                   8914:                 @mapresources = map { $symb_to_resource->{$_}; } @{$actual_seq};
                   8915:                 if (ref($orderedforcode) eq 'HASH') {
                   8916:                     if (@mapresources > 0) { 
                   8917:                         $orderedforcode->{$scancode} = \@mapresources;
                   8918:                     }
                   8919:                 }
                   8920:             }
                   8921:             delete($env{'form.CODE'});
1.678     raeburn  8922:         }
                   8923:     } else {
                   8924:         my $actual_seq =
                   8925:             &Apache::lonprintout::master_seq_to_person_seq($mapurl,
                   8926:                                                            $master_seq,
1.688     raeburn  8927:                                                            $user,undef,1);
1.678     raeburn  8928:         if (ref($actual_seq) eq 'ARRAY') {
                   8929:             @mapresources = 
                   8930:                 map { $symb_to_resource->{$_}; } @{$actual_seq};
                   8931:         }
1.691     raeburn  8932:     }
                   8933:     return @mapresources;
1.678     raeburn  8934: }
                   8935: 
1.542     raeburn  8936: sub grade_student_bubbles {
1.691     raeburn  8937:     my ($r,$uname,$udom,$scan_record,$scancode,$resources,$parts,$bubbles_per_row,
                   8938:         $randomorder,$randompick,$respnumlookup,$startline) = @_;
                   8939:     my $uselookup = 0;
                   8940:     if (($randomorder || $randompick) && (ref($respnumlookup) eq 'HASH') &&
                   8941:         (ref($startline) eq 'HASH')) {
                   8942:         $uselookup = 1;
                   8943:     }
                   8944: 
1.554     raeburn  8945:     if (ref($resources) eq 'ARRAY') {
                   8946:         my $count = 0;
                   8947:         foreach my $resource (@{$resources}) {
                   8948:             my $ressymb = $resource->symb();
                   8949:             my %form = ('submitted'      => 'scantron',
                   8950:                         'grade_target'   => 'grade',
                   8951:                         'grade_username' => $uname,
                   8952:                         'grade_domain'   => $udom,
                   8953:                         'grade_courseid' => $env{'request.course.id'},
                   8954:                         'grade_symb'     => $ressymb,
                   8955:                         'CODE'           => $scancode
                   8956:                        );
1.649     raeburn  8957:             if ($bubbles_per_row ne '') {
                   8958:                 $form{'bubbles_per_row'} = $bubbles_per_row;
                   8959:             }
1.663     raeburn  8960:             if ($env{'form.scantron_lastbubblepoints'} ne '') {
                   8961:                 $form{'scantron_lastbubblepoints'} = $env{'form.scantron_lastbubblepoints'};
                   8962:             }
1.554     raeburn  8963:             if (ref($parts) eq 'HASH') {
                   8964:                 if (ref($parts->{$ressymb}) eq 'ARRAY') {
                   8965:                     foreach my $part (@{$parts->{$ressymb}}) {
1.691     raeburn  8966:                         if ($uselookup) {
                   8967:                             $form{'scantron_questnum_start.'.$part} = $startline->{$count} + 1;
                   8968:                         } else {
                   8969:                             $form{'scantron_questnum_start.'.$part} =
                   8970:                                 1+$env{'form.scantron.first_bubble_line.'.$count};
                   8971:                         }
1.554     raeburn  8972:                         $count++;
                   8973:                     }
                   8974:                 }
                   8975:             }
                   8976:             my $result=&ssi_with_retries($resource->src(),$ssi_retries,%form);
                   8977:             return 'ssi_error' if ($ssi_error);
                   8978:             last if (&Apache::loncommon::connection_aborted($r));
                   8979:         }
1.542     raeburn  8980:     }
                   8981:     return;
                   8982: }
                   8983: 
1.157     albertel 8984: sub scantron_upload_scantron_data {
1.608     www      8985:     my ($r,$symb)=@_;
1.565     raeburn  8986:     my $dom = $env{'request.role.domain'};
                   8987:     my $domdesc = &Apache::lonnet::domain($dom,'description');
                   8988:     $r->print(&Apache::loncommon::coursebrowser_javascript($dom));
1.157     albertel 8989:     my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid',
1.181     albertel 8990: 							  'domainid',
1.565     raeburn  8991: 							  'coursename',$dom);
                   8992:     my $syllabuslink = '<a href="javascript:ToSyllabus();">'.&mt('Syllabus').'</a>'.
                   8993:                        ('&nbsp'x2).&mt('(shows course personnel)'); 
1.608     www      8994:     my $default_form_data=&defaultFormData($symb);
1.579     raeburn  8995:     my $nofile_alert = &mt('Please use the browse button to select a file from your local directory.');
1.736     damieng  8996:     &js_escape(\$nofile_alert);
1.579     raeburn  8997:     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.736     damieng  8998:     &js_escape(\$nocourseid_alert);
1.597     wenzelju 8999:     $r->print(&Apache::lonhtmlcommon::scripttag('
1.157     albertel 9000:     function checkUpload(formname) {
                   9001: 	if (formname.upfile.value == "") {
1.579     raeburn  9002: 	    alert("'.$nofile_alert.'");
1.157     albertel 9003: 	    return false;
                   9004: 	}
1.565     raeburn  9005:         if (formname.courseid.value == "") {
1.579     raeburn  9006:             alert("'.$nocourseid_alert.'");
1.565     raeburn  9007:             return false;
                   9008:         }
1.157     albertel 9009: 	formname.submit();
                   9010:     }
1.565     raeburn  9011: 
                   9012:     function ToSyllabus() {
                   9013:         var cdom = '."'$dom'".';
                   9014:         var cnum = document.rules.courseid.value;
                   9015:         if (cdom == "" || cdom == null) {
                   9016:             return;
                   9017:         }
                   9018:         if (cnum == "" || cnum == null) {
                   9019:            return;
                   9020:         }
                   9021:         syllwin=window.open("/public/"+cdom+"/"+cnum+"/syllabus","LONCAPASyllabus",
                   9022:                             "height=350,width=350,scrollbars=yes,menubar=no");
                   9023:         return;
                   9024:     }
                   9025: 
1.597     wenzelju 9026: '));
                   9027:     $r->print('
1.648     bisitz   9028: <h3>'.&mt('Send bubblesheet data to a course').'</h3>
1.566     raeburn  9029: 
1.492     albertel 9030: <form enctype="multipart/form-data" action="/adm/grades" name="rules" method="post">
1.565     raeburn  9031: '.$default_form_data.
                   9032:   &Apache::lonhtmlcommon::start_pick_box().
                   9033:   &Apache::lonhtmlcommon::row_title(&mt('Course ID')).
                   9034:   '<input name="courseid" type="text" size="30" />'.$select_link.
                   9035:   &Apache::lonhtmlcommon::row_closure().
                   9036:   &Apache::lonhtmlcommon::row_title(&mt('Course Name')).
                   9037:   '<input name="coursename" type="text" size="30" />'.$syllabuslink.
                   9038:   &Apache::lonhtmlcommon::row_closure().
                   9039:   &Apache::lonhtmlcommon::row_title(&mt('Domain')).
                   9040:   '<input name="domainid" type="hidden" />'.$domdesc.
                   9041:   &Apache::lonhtmlcommon::row_closure().
                   9042:   &Apache::lonhtmlcommon::row_title(&mt('File to upload')).
                   9043:   '<input type="file" name="upfile" size="50" />'.
                   9044:   &Apache::lonhtmlcommon::row_closure(1).
                   9045:   &Apache::lonhtmlcommon::end_pick_box().'<br />
                   9046: 
1.492     albertel 9047: <input name="command" value="scantronupload_save" type="hidden" />
1.589     bisitz   9048: <input type="button" onclick="javascript:checkUpload(this.form);" value="'.&mt('Upload Bubblesheet Data').'" />
1.157     albertel 9049: </form>
1.492     albertel 9050: ');
1.157     albertel 9051:     return '';
                   9052: }
                   9053: 
1.423     albertel 9054: 
1.157     albertel 9055: sub scantron_upload_scantron_data_save {
1.608     www      9056:     my($r,$symb)=@_;
1.182     albertel 9057:     my $doanotherupload=
                   9058: 	'<br /><form action="/adm/grades" method="post">'."\n".
                   9059: 	'<input type="hidden" name="command" value="scantronupload" />'."\n".
1.492     albertel 9060: 	'<input type="submit" name="submit" value="'.&mt('Do Another Upload').'" />'."\n".
1.182     albertel 9061: 	'</form>'."\n";
1.257     albertel 9062:     if (!&Apache::lonnet::allowed('usc',$env{'form.domainid'}) &&
1.162     albertel 9063: 	!&Apache::lonnet::allowed('usc',
1.257     albertel 9064: 			    $env{'form.domainid'}.'_'.$env{'form.courseid'})) {
1.575     www      9065: 	$r->print(&mt("You are not allowed to upload bubblesheet data to the requested course.")."<br />");
1.614     www      9066: 	unless ($symb) {
1.182     albertel 9067: 	    $r->print($doanotherupload);
                   9068: 	}
1.162     albertel 9069: 	return '';
                   9070:     }
1.257     albertel 9071:     my %coursedata=&Apache::lonnet::coursedescription($env{'form.domainid'}.'_'.$env{'form.courseid'});
1.568     raeburn  9072:     my $uploadedfile;
1.710     bisitz   9073:     $r->print('<p>'.&mt('Uploading file to [_1]','"'.$coursedata{'description'}.'"').'</p>');
1.257     albertel 9074:     if (length($env{'form.upfile'}) < 2) {
1.710     bisitz   9075:         $r->print(
                   9076:             &Apache::lonhtmlcommon::confirm_success(
                   9077:                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                   9078:                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1));
1.183     albertel 9079:     } else {
1.568     raeburn  9080:         my $result = 
                   9081:             &Apache::lonnet::userfileupload('upfile','','scantron','','','',
                   9082:                                             $env{'form.courseid'},$env{'form.domainid'});
1.710     bisitz   9083:         if ($result =~ m{^/uploaded/}) {
                   9084:             $r->print(
                   9085:                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload successful')).'<br />'.
                   9086:                 &mt('Uploaded [_1] bytes of data into location: [_2]',
                   9087:                         (length($env{'form.upfile'})-1),
                   9088:                         '<span class="LC_filename">'.$result.'</span>'));
1.568     raeburn  9089:             ($uploadedfile) = ($result =~ m{/([^/]+)$});
1.567     raeburn  9090:             $r->print(&validate_uploaded_scantron_file($env{'form.domainid'},
1.568     raeburn  9091:                                                        $env{'form.courseid'},$uploadedfile));
1.710     bisitz   9092:         } else {
                   9093:             $r->print(
                   9094:                 &Apache::lonhtmlcommon::confirm_success(&mt('Upload failed'),1).'<br />'.
                   9095:                     &mt('An error ([_1]) occurred when attempting to upload the file: [_2]',
                   9096:                           $result,
1.568     raeburn  9097: 			  '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'));
1.183     albertel 9098: 	}
                   9099:     }
1.174     albertel 9100:     if ($symb) {
1.612     www      9101: 	$r->print(&scantron_selectphase($r,$uploadedfile,$symb));
1.174     albertel 9102:     } else {
1.182     albertel 9103: 	$r->print($doanotherupload);
1.174     albertel 9104:     }
1.157     albertel 9105:     return '';
                   9106: }
                   9107: 
1.567     raeburn  9108: sub validate_uploaded_scantron_file {
                   9109:     my ($cdom,$cname,$fname) = @_;
                   9110:     my $scanlines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'.$fname);
                   9111:     my @lines;
                   9112:     if ($scanlines ne '-1') {
                   9113:         @lines=split("\n",$scanlines,-1);
                   9114:     }
                   9115:     my $output;
                   9116:     if (@lines) {
                   9117:         my (%counts,$max_match_format);
1.710     bisitz   9118:         my ($found_match_count,$max_match_count,$max_match_pct) = (0,0,0);
1.567     raeburn  9119:         my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cname);
                   9120:         my %idmap = &username_to_idmap($classlist);
                   9121:         foreach my $key (keys(%idmap)) {
                   9122:             my $lckey = lc($key);
                   9123:             $idmap{$lckey} = $idmap{$key};
                   9124:         }
                   9125:         my %unique_formats;
                   9126:         my @formatlines = &get_scantronformat_file();
                   9127:         foreach my $line (@formatlines) {
                   9128:             chomp($line);
                   9129:             my @config = split(/:/,$line);
                   9130:             my $idstart = $config[5];
                   9131:             my $idlength = $config[6];
                   9132:             if (($idstart ne '') && ($idlength > 0)) {
                   9133:                 if (ref($unique_formats{$idstart.':'.$idlength}) eq 'ARRAY') {
                   9134:                     push(@{$unique_formats{$idstart.':'.$idlength}},$config[0].':'.$config[1]); 
                   9135:                 } else {
                   9136:                     $unique_formats{$idstart.':'.$idlength} = [$config[0].':'.$config[1]];
                   9137:                 }
                   9138:             }
                   9139:         }
                   9140:         foreach my $key (keys(%unique_formats)) {
                   9141:             my ($idstart,$idlength) = split(':',$key);
                   9142:             %{$counts{$key}} = (
                   9143:                                'found'   => 0,
                   9144:                                'total'   => 0,
                   9145:                               );
                   9146:             foreach my $line (@lines) {
                   9147:                 next if ($line =~ /^#/);
                   9148:                 next if ($line =~ /^[\s\cz]*$/);
                   9149:                 my $id = substr($line,$idstart-1,$idlength);
                   9150:                 $id = lc($id);
                   9151:                 if (exists($idmap{$id})) {
                   9152:                     $counts{$key}{'found'} ++;
                   9153:                 }
                   9154:                 $counts{$key}{'total'} ++;
                   9155:             }
                   9156:             if ($counts{$key}{'total'}) {
                   9157:                 my $percent_match = (100*$counts{$key}{'found'})/($counts{$key}{'total'});
                   9158:                 if (($max_match_format eq '') || ($percent_match > $max_match_pct)) {
                   9159:                     $max_match_pct = $percent_match;
                   9160:                     $max_match_format = $key;
1.710     bisitz   9161:                     $found_match_count = $counts{$key}{'found'};
1.567     raeburn  9162:                     $max_match_count = $counts{$key}{'total'};
                   9163:                 }
                   9164:             }
                   9165:         }
                   9166:         if (ref($unique_formats{$max_match_format}) eq 'ARRAY') {
                   9167:             my $format_descs;
                   9168:             my $numwithformat = @{$unique_formats{$max_match_format}};
                   9169:             for (my $i=0; $i<$numwithformat; $i++) {
                   9170:                 my ($name,$desc) = split(':',$unique_formats{$max_match_format}[$i]);
                   9171:                 if ($i<$numwithformat-2) {
                   9172:                     $format_descs .= '"<i>'.$desc.'</i>", ';
                   9173:                 } elsif ($i==$numwithformat-2) {
                   9174:                     $format_descs .= '"<i>'.$desc.'</i>" '.&mt('and').' ';
                   9175:                 } elsif ($i==$numwithformat-1) {
                   9176:                     $format_descs .= '"<i>'.$desc.'</i>"';
                   9177:                 }
                   9178:             }
                   9179:             my $showpct = sprintf("%.0f",$max_match_pct).'%';
1.710     bisitz   9180:             $output .= '<br />';
                   9181:             if ($found_match_count == $max_match_count) {
                   9182:                 # 100% matching entries
                   9183:                 $output .= &Apache::lonhtmlcommon::confirm_success(
                   9184:                      &mt('Comparison of student IDs: [_1] matching ([quant,_2,entry,entries])',
                   9185:                             '<b>'.$showpct.'</b>',$found_match_count)).'<br />'.
                   9186:                 &mt('Comparison of student IDs in the uploaded file with'.
                   9187:                     ' the course roster found matches for [_1] of the [_2] entries'.
                   9188:                     ' in the file (for the format defined for [_3]).',
                   9189:                         '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs);
                   9190:             } else {
                   9191:                 # Not all entries matching? -> Show warning and additional info
                   9192:                 $output .=
                   9193:                     &Apache::lonhtmlcommon::confirm_success(
                   9194:                         &mt('Comparison of student IDs: [_1] matching ([_2]/[quant,_3,entry,entries])',
                   9195:                                 '<b>'.$showpct.'</b>',$found_match_count,$max_match_count).'<br />'.
                   9196:                         &mt('Not all entries could be matched!'),1).'<br />'.
                   9197:                     &mt('Comparison of student IDs in the uploaded file with'.
                   9198:                         ' the course roster found matches for [_1] of the [_2] entries'.
                   9199:                         ' in the file (for the format defined for [_3]).',
                   9200:                             '<b>'.$showpct.'</b>','<b>'.$max_match_count.'</b>',$format_descs).
                   9201:                     '<p class="LC_info">'.
                   9202:                     &mt('A low percentage of matches results from one of the following:').
                   9203:                     '</p><ul>'.
                   9204:                     '<li>'.&mt('The file was uploaded to the wrong course.').'</li>'.
                   9205:                     '<li>'.&mt('The data is not in the format expected for the domain: [_1]',
                   9206:                                '<i>'.$cdom.'</i>').'</li>'.
                   9207:                     '<li>'.&mt('Students did not bubble their IDs, or mis-bubbled them').'</li>'.
                   9208:                     '<li>'.&mt('The course roster is not up to date.').'</li>'.
                   9209:                     '</ul>';
                   9210:             }
1.567     raeburn  9211:         }
                   9212:     } else {
1.710     bisitz   9213:         $output = '<p class="LC_warning">'.&mt('Uploaded file contained no data').'</p>';
1.567     raeburn  9214:     }
                   9215:     return $output;
                   9216: }
                   9217: 
1.202     albertel 9218: sub valid_file {
                   9219:     my ($requested_file)=@_;
                   9220:     foreach my $filename (sort(&scantron_filenames())) {
                   9221: 	if ($requested_file eq $filename) { return 1; }
                   9222:     }
                   9223:     return 0;
                   9224: }
                   9225: 
                   9226: sub scantron_download_scantron_data {
1.608     www      9227:     my ($r,$symb)=@_;
                   9228:     my $default_form_data=&defaultFormData($symb);
1.257     albertel 9229:     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
                   9230:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   9231:     my $file=$env{'form.scantron_selectfile'};
1.202     albertel 9232:     if (! &valid_file($file)) {
1.492     albertel 9233: 	$r->print('
1.202     albertel 9234: 	<p>
1.686     bisitz   9235: 	    '.&mt('The requested filename was invalid.').'
1.202     albertel 9236:         </p>
1.492     albertel 9237: ');
1.202     albertel 9238: 	return;
                   9239:     }
                   9240:     my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file;
                   9241:     my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file;
                   9242:     my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file;
                   9243:     &Apache::lonnet::allowuploaded('/adm/grades',$orig);
                   9244:     &Apache::lonnet::allowuploaded('/adm/grades',$corrected);
                   9245:     &Apache::lonnet::allowuploaded('/adm/grades',$skipped);
1.492     albertel 9246:     $r->print('
1.202     albertel 9247:     <p>
1.723     raeburn  9248: 	'.&mt('[_1]Original[_2] file as uploaded by the bubblesheet scanning office.',
1.492     albertel 9249: 	      '<a href="'.$orig.'">','</a>').'
1.202     albertel 9250:     </p>
                   9251:     <p>
1.492     albertel 9252: 	'.&mt('[_1]Corrections[_2], a file of corrected records that were used in grading.',
                   9253: 	      '<a href="'.$corrected.'">','</a>').'
1.202     albertel 9254:     </p>
                   9255:     <p>
1.492     albertel 9256: 	'.&mt('[_1]Skipped[_2], a file of records that were skipped.',
                   9257: 	      '<a href="'.$skipped.'">','</a>').'
1.202     albertel 9258:     </p>
1.492     albertel 9259: ');
1.202     albertel 9260:     return '';
                   9261: }
1.157     albertel 9262: 
1.523     raeburn  9263: sub checkscantron_results {
1.608     www      9264:     my ($r,$symb) = @_;
1.523     raeburn  9265:     if (!$symb) {return '';}
                   9266:     my $cid = $env{'request.course.id'};
1.542     raeburn  9267:     my %lettdig = &letter_to_digits();
1.523     raeburn  9268:     my $numletts = scalar(keys(%lettdig));
                   9269:     my $cnum = $env{'course.'.$cid.'.num'};
                   9270:     my $cdom = $env{'course.'.$cid.'.domain'};
                   9271:     my (undef, undef, $sequence) = &Apache::lonnet::decode_symb($env{'form.selectpage'});
                   9272:     my %record;
                   9273:     my %scantron_config =
                   9274:         &Apache::grades::get_scantron_config($env{'form.scantron_format'});
1.649     raeburn  9275:     my $bubbles_per_row = &bubblesheet_bubbles_per_row(\%scantron_config);
1.523     raeburn  9276:     my ($scanlines,$scan_data)=&Apache::grades::scantron_getfile();
                   9277:     my $classlist=&Apache::loncoursedata::get_classlist();
                   9278:     my %idmap=&Apache::grades::username_to_idmap($classlist);
                   9279:     my $navmap=Apache::lonnavmaps::navmap->new();
1.582     raeburn  9280:     unless (ref($navmap)) {
                   9281:         $r->print(&navmap_errormsg());
                   9282:         return '';
                   9283:     }
1.523     raeburn  9284:     my $map=$navmap->getResourceByUrl($sequence);
1.691     raeburn  9285:     my ($randomorder,$randompick,@master_seq,%symb_to_resource,%grader_partids_by_symb,
                   9286:         %grader_randomlists_by_symb,%orderedforcode);
1.677     raeburn  9287:     if (ref($map)) { 
                   9288:         $randomorder=$map->randomorder();
1.689     raeburn  9289:         $randompick=$map->randompick();
1.677     raeburn  9290:     }
1.557     raeburn  9291:     my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0);
1.691     raeburn  9292:     my $nav_error = &get_master_seq(\@resources,\@master_seq,\%symb_to_resource);
                   9293:     if ($nav_error) {
                   9294:         $r->print(&navmap_errormsg());
                   9295:         return '';
1.678     raeburn  9296:     }
1.673     raeburn  9297:     &graders_resources_pass(\@resources,\%grader_partids_by_symb,
                   9298:                             \%grader_randomlists_by_symb,$bubbles_per_row);
1.554     raeburn  9299:     my ($uname,$udom);
1.523     raeburn  9300:     my (%scandata,%lastname,%bylast);
                   9301:     $r->print('
                   9302: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="checkscantron">'."\n");
                   9303: 
                   9304:     my @delayqueue;
                   9305:     my %completedstudents;
                   9306: 
1.691     raeburn  9307:     my $count=&get_todo_count($scanlines,$scan_data);
1.667     www      9308:     my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,$count);
1.706     raeburn  9309:     my ($username,$domain,$started);
1.649     raeburn  9310:     &scantron_get_maxbubble(\$nav_error,\%scantron_config); # Need the bubble lines array to parse.
1.582     raeburn  9311:     if ($nav_error) {
                   9312:         $r->print(&navmap_errormsg());
                   9313:         return '';
                   9314:     }
1.523     raeburn  9315: 
1.667     www      9316:     &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,'Processing first student');
1.523     raeburn  9317:     my $start=&Time::HiRes::time();
                   9318:     my $i=-1;
                   9319: 
                   9320:     while ($i<$scanlines->{'count'}) {
                   9321:         ($username,$domain,$uname)=('','','');
                   9322:         $i++;
                   9323:         my $line=&Apache::grades::scantron_get_line($scanlines,$scan_data,$i);
                   9324:         if ($line=~/^[\s\cz]*$/) { next; }
                   9325:         if ($started) {
1.667     www      9326:             &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state,'last student');
1.523     raeburn  9327:         }
                   9328:         $started=1;
                   9329:         my $scan_record=
                   9330:             &Apache::grades::scantron_parse_scanline($line,$i,\%scantron_config,
                   9331:                                                      $scan_data);
1.693     raeburn  9332:         unless ($uname=&scantron_find_student($scan_record,$scan_data,
                   9333:                                               \%idmap,$i)) {
1.523     raeburn  9334:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                   9335:                                 'Unable to find a student that matches',1);
                   9336:             next;
                   9337:         }
                   9338:         if (exists $completedstudents{$uname}) {
                   9339:             &Apache::grades::scantron_add_delay(\@delayqueue,$line,
                   9340:                                 'Student '.$uname.' has multiple sheets',2);
                   9341:             next;
                   9342:         }
                   9343:         my $pid = $scan_record->{'scantron.ID'};
                   9344:         $lastname{$pid} = $scan_record->{'scantron.LastName'};
                   9345:         push(@{$bylast{$lastname{$pid}}},$pid);
1.678     raeburn  9346:         my $usec = $classlist->{$uname}->[&Apache::loncoursedata::CL_SECTION];
                   9347:         my $user = $uname.':'.$usec;
1.523     raeburn  9348:         ($username,$domain)=split(/:/,$uname);
1.677     raeburn  9349: 
1.678     raeburn  9350:         my $scancode;
1.677     raeburn  9351:         if ((exists($scan_record->{'scantron.CODE'})) &&
                   9352:             (&Apache::lonnet::validCODE($scan_record->{'scantron.CODE'}))) {
                   9353:             $scancode = $scan_record->{'scantron.CODE'};
                   9354:         } else {
                   9355:             $scancode = '';
                   9356:         }
                   9357: 
                   9358:         my @mapresources = @resources;
1.691     raeburn  9359:         my $lastpos = $env{'form.scantron_maxbubble'}*$scantron_config{'Qlength'};
                   9360:         my %respnumlookup=();
                   9361:         my %startline=();
1.689     raeburn  9362:         if ($randomorder || $randompick) {
1.678     raeburn  9363:             @mapresources =
1.691     raeburn  9364:                 &users_order($user,$scancode,$sequence,\@master_seq,\%symb_to_resource,
                   9365:                              \%orderedforcode);
                   9366:             my $total = &get_respnum_lookups($sequence,$scan_data,\%idmap,$line,
                   9367:                                              $scan_record,\@master_seq,\%symb_to_resource,
                   9368:                                              \%grader_partids_by_symb,\%orderedforcode,
                   9369:                                              \%respnumlookup,\%startline);
                   9370:             if ($randompick && $total) {
                   9371:                 $lastpos = $total*$scantron_config{'Qlength'};
                   9372:             }
1.677     raeburn  9373:         }
1.691     raeburn  9374:         $scandata{$pid} = substr($line,$scantron_config{'Qstart'}-1,$lastpos);
                   9375:         chomp($scandata{$pid});
                   9376:         $scandata{$pid} =~ s/\r$//;
                   9377: 
1.523     raeburn  9378:         my $counter = -1;
1.677     raeburn  9379:         foreach my $resource (@mapresources) {
1.557     raeburn  9380:             my $parts;
1.554     raeburn  9381:             my $ressymb = $resource->symb();
1.557     raeburn  9382:             if ((exists($grader_randomlists_by_symb{$ressymb})) ||
                   9383:                 (ref($grader_partids_by_symb{$ressymb}) ne 'ARRAY')) {
1.741     raeburn  9384:                 my $currcode;
                   9385:                 if (exists($grader_randomlists_by_symb{$ressymb})) {
                   9386:                     $currcode = $scancode;
                   9387:                 }
1.557     raeburn  9388:                 (my $analysis,$parts) =
1.672     raeburn  9389:                     &scantron_partids_tograde($resource,$env{'request.course.id'},
                   9390:                                               $username,$domain,undef,
1.741     raeburn  9391:                                               $bubbles_per_row,$currcode);
1.557     raeburn  9392:             } else {
                   9393:                 $parts = $grader_partids_by_symb{$ressymb};
                   9394:             }
1.542     raeburn  9395:             ($counter,my $recording) =
                   9396:                 &verify_scantron_grading($resource,$domain,$username,$cid,$counter,
1.554     raeburn  9397:                                          $scandata{$pid},$parts,
1.691     raeburn  9398:                                          \%scantron_config,\%lettdig,$numletts,
                   9399:                                          $randomorder,$randompick,
                   9400:                                          \%respnumlookup,\%startline);
1.542     raeburn  9401:             $record{$pid} .= $recording;
1.523     raeburn  9402:         }
                   9403:     }
                   9404:     &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state);
                   9405:     $r->print('<br />');
                   9406:     my ($okstudents,$badstudents,$numstudents,$passed,$failed);
                   9407:     $passed = 0;
                   9408:     $failed = 0;
                   9409:     $numstudents = 0;
                   9410:     foreach my $last (sort(keys(%bylast))) {
                   9411:         if (ref($bylast{$last}) eq 'ARRAY') {
                   9412:             foreach my $pid (sort(@{$bylast{$last}})) {
                   9413:                 my $showscandata = $scandata{$pid};
                   9414:                 my $showrecord = $record{$pid};
                   9415:                 $showscandata =~ s/\s/&nbsp;/g;
                   9416:                 $showrecord =~ s/\s/&nbsp;/g;
                   9417:                 if ($scandata{$pid} eq $record{$pid}) {
                   9418:                     my $css_class = ($passed % 2)?'LC_odd_row':'LC_even_row';
                   9419:                     $okstudents .= '<tr class="'.$css_class.'">'.
1.581     www      9420: '<td>'.&mt('Bubblesheet').'</td><td>'.$showscandata.'</td><td rowspan="2">'.$last.'</td><td rowspan="2">'.$pid.'</td>'."\n".
1.523     raeburn  9421: '</tr>'."\n".
                   9422: '<tr class="'.$css_class.'">'."\n".
1.721     bisitz   9423: '<td>'.&mt('Submissions').'</td><td>'.$showrecord.'</td></tr>'."\n";
1.523     raeburn  9424:                     $passed ++;
                   9425:                 } else {
                   9426:                     my $css_class = ($failed % 2)?'LC_odd_row':'LC_even_row';
1.581     www      9427:                     $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  9428: '</tr>'."\n".
                   9429: '<tr class="'.$css_class.'">'."\n".
1.721     bisitz   9430: '<td>'.&mt('Submissions').'</td><td><span class="LC_nobreak">'.$record{$pid}.'</span></td>'."\n".
1.523     raeburn  9431: '</tr>'."\n";
                   9432:                     $failed ++;
                   9433:                 }
                   9434:                 $numstudents ++;
                   9435:             }
                   9436:         }
                   9437:     }
1.648     bisitz   9438:     $r->print(
                   9439:         '<p>'
                   9440:        .&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).',
                   9441:             '<b>',
                   9442:             $numstudents,
                   9443:             '</b>',
                   9444:             $env{'form.scantron_maxbubble'})
                   9445:        .'</p>'
                   9446:     );
1.682     raeburn  9447:     $r->print('<p>'
1.683     raeburn  9448:              .&mt('Exact matches for [_1][quant,_2,student][_3].','<b>',$passed,'</b>')
1.682     raeburn  9449:              .'<br />'
                   9450:              .&mt('Discrepancies detected for [_1][quant,_2,student][_3].','<b>',$failed,'</b>')
                   9451:              .'</p>'
                   9452:     );
1.523     raeburn  9453:     if ($passed) {
1.572     www      9454:         $r->print(&mt('Students with exact correspondence between bubblesheet data and submissions are as follows:').'<br /><br />');
1.523     raeburn  9455:         $r->print(&Apache::loncommon::start_data_table()."\n".
                   9456:                  &Apache::loncommon::start_data_table_header_row()."\n".
                   9457:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                   9458:                  &Apache::loncommon::end_data_table_header_row()."\n".
                   9459:                  $okstudents."\n".
                   9460:                  &Apache::loncommon::end_data_table().'<br />');
                   9461:     }
                   9462:     if ($failed) {
1.572     www      9463:         $r->print(&mt('Students with differences between bubblesheet data and submissions are as follows:').'<br /><br />');
1.523     raeburn  9464:         $r->print(&Apache::loncommon::start_data_table()."\n".
                   9465:                  &Apache::loncommon::start_data_table_header_row()."\n".
                   9466:                  '<th>'.&mt('Source').'</th><th>'.&mt('Bubble records').'</th><th>'.&mt('Name').'</th><th>'.&mt('ID').'</th>'.
                   9467:                  &Apache::loncommon::end_data_table_header_row()."\n".
                   9468:                  $badstudents."\n".
                   9469:                  &Apache::loncommon::end_data_table()).'<br />'.
1.572     www      9470:                  &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  9471:     }
1.614     www      9472:     $r->print('</form><br />');
1.523     raeburn  9473:     return;
                   9474: }
                   9475: 
1.542     raeburn  9476: sub verify_scantron_grading {
1.554     raeburn  9477:     my ($resource,$domain,$username,$cid,$counter,$scandata,$partids,
1.691     raeburn  9478:         $scantron_config,$lettdig,$numletts,$randomorder,$randompick,
                   9479:         $respnumlookup,$startline) = @_;
1.542     raeburn  9480:     my ($record,%expected,%startpos);
                   9481:     return ($counter,$record) if (!ref($resource));
                   9482:     return ($counter,$record) if (!$resource->is_problem());
                   9483:     my $symb = $resource->symb();
1.554     raeburn  9484:     return ($counter,$record) if (ref($partids) ne 'ARRAY');
                   9485:     foreach my $part_id (@{$partids}) {
1.542     raeburn  9486:         $counter ++;
                   9487:         $expected{$part_id} = 0;
1.691     raeburn  9488:         my $respnum = $counter;
                   9489:         if ($randomorder || $randompick) {
                   9490:             $respnum = $respnumlookup->{$counter};
                   9491:             $startpos{$part_id} = $startline->{$counter} + 1;
                   9492:         } else {
                   9493:             $startpos{$part_id} = $env{"form.scantron.first_bubble_line.$counter"};
                   9494:         }
                   9495:         if ($env{"form.scantron.sub_bubblelines.$respnum"}) {
                   9496:             my @sub_lines = split(/,/,$env{"form.scantron.sub_bubblelines.$respnum"});
1.542     raeburn  9497:             foreach my $item (@sub_lines) {
                   9498:                 $expected{$part_id} += $item;
                   9499:             }
                   9500:         } else {
1.691     raeburn  9501:             $expected{$part_id} = $env{"form.scantron.bubblelines.$respnum"};
1.542     raeburn  9502:         }
                   9503:     }
                   9504:     if ($symb) {
                   9505:         my %recorded;
                   9506:         my (%returnhash) = &Apache::lonnet::restore($symb,$cid,$domain,$username);
                   9507:         if ($returnhash{'version'}) {
                   9508:             my %lasthash=();
                   9509:             my $version;
                   9510:             for ($version=1;$version<=$returnhash{'version'};$version++) {
                   9511:                 foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
                   9512:                     $lasthash{$key}=$returnhash{$version.':'.$key};
                   9513:                 }
                   9514:             }
                   9515:             foreach my $key (keys(%lasthash)) {
                   9516:                 if ($key =~ /\.scantron$/) {
                   9517:                     my $value = &unescape($lasthash{$key});
                   9518:                     my ($part_id) = ($key =~ /^resource\.(.+)\.scantron$/);
                   9519:                     if ($value eq '') {
                   9520:                         for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9521:                             for (my $j=0; $j<$scantron_config->{'length'}; $j++) {
                   9522:                                 $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9523:                             }
                   9524:                         }
                   9525:                     } else {
                   9526:                         my @tocheck;
                   9527:                         my @items = split(//,$value);
                   9528:                         if (($scantron_config->{'Qon'} eq 'letter') ||
                   9529:                             ($scantron_config->{'Qon'} eq 'number')) {
                   9530:                             if (@items < $expected{$part_id}) {
                   9531:                                 my $fragment = substr($scandata,$startpos{$part_id},$expected{$part_id});
                   9532:                                 my @singles = split(//,$fragment);
                   9533:                                 foreach my $pos (@singles) {
                   9534:                                     if ($pos eq ' ') {
                   9535:                                         push(@tocheck,$pos);
                   9536:                                     } else {
                   9537:                                         my $next = shift(@items);
                   9538:                                         push(@tocheck,$next);
                   9539:                                     }
                   9540:                                 }
                   9541:                             } else {
                   9542:                                 @tocheck = @items;
                   9543:                             }
                   9544:                             foreach my $letter (@tocheck) {
                   9545:                                 if ($scantron_config->{'Qon'} eq 'letter') {
                   9546:                                     if ($letter !~ /^[A-J]$/) {
                   9547:                                         $letter = $scantron_config->{'Qoff'};
                   9548:                                     }
                   9549:                                     $recorded{$part_id} .= $letter;
                   9550:                                 } elsif ($scantron_config->{'Qon'} eq 'number') {
                   9551:                                     my $digit;
                   9552:                                     if ($letter !~ /^[A-J]$/) {
                   9553:                                         $digit = $scantron_config->{'Qoff'};
                   9554:                                     } else {
                   9555:                                         $digit = $lettdig->{$letter};
                   9556:                                     }
                   9557:                                     $recorded{$part_id} .= $digit;
                   9558:                                 }
                   9559:                             }
                   9560:                         } else {
                   9561:                             @tocheck = @items;
                   9562:                             for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9563:                                 my $curr_sub = shift(@tocheck);
                   9564:                                 my $digit;
                   9565:                                 if ($curr_sub =~ /^[A-J]$/) {
                   9566:                                     $digit = $lettdig->{$curr_sub}-1;
                   9567:                                 }
                   9568:                                 if ($curr_sub eq 'J') {
                   9569:                                     $digit += scalar($numletts);
                   9570:                                 }
                   9571:                                 for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                   9572:                                     if ($j == $digit) {
                   9573:                                         $recorded{$part_id} .= $scantron_config->{'Qon'};
                   9574:                                     } else {
                   9575:                                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9576:                                     }
                   9577:                                 }
                   9578:                             }
                   9579:                         }
                   9580:                     }
                   9581:                 }
                   9582:             }
                   9583:         }
1.554     raeburn  9584:         foreach my $part_id (@{$partids}) {
1.542     raeburn  9585:             if ($recorded{$part_id} eq '') {
                   9586:                 for (my $i=0; $i<$expected{$part_id}; $i++) {
                   9587:                     for (my $j=0; $j<$scantron_config->{'Qlength'}; $j++) {
                   9588:                         $recorded{$part_id} .= $scantron_config->{'Qoff'};
                   9589:                     }
                   9590:                 }
                   9591:             }
                   9592:             $record .= $recorded{$part_id};
                   9593:         }
                   9594:     }
                   9595:     return ($counter,$record);
                   9596: }
                   9597: 
1.691     raeburn  9598: sub letter_to_digits {
1.542     raeburn  9599:     my %lettdig = (
                   9600:                     A => 1,
                   9601:                     B => 2,
                   9602:                     C => 3,
                   9603:                     D => 4,
                   9604:                     E => 5,
                   9605:                     F => 6,
                   9606:                     G => 7,
                   9607:                     H => 8,
                   9608:                     I => 9,
                   9609:                     J => 0,
                   9610:                   );
                   9611:     return %lettdig;
                   9612: }
                   9613: 
1.423     albertel 9614: 
1.75      albertel 9615: #-------- end of section for handling grading scantron forms -------
                   9616: #
                   9617: #-------------------------------------------------------------------
                   9618: 
1.72      ng       9619: #-------------------------- Menu interface -------------------------
                   9620: #
1.614     www      9621: #--- Href with symb and command ---
                   9622: 
                   9623: sub href_symb_cmd {
                   9624:     my ($symb,$cmd)=@_;
1.669     raeburn  9625:     return '/adm/grades?symb='.&HTML::Entities::encode(&Apache::lonenc::check_encrypt($symb),'<>&"').'&amp;command='.$cmd;
1.72      ng       9626: }
                   9627: 
1.443     banghart 9628: sub grading_menu {
1.608     www      9629:     my ($request,$symb) = @_;
1.443     banghart 9630:     if (!$symb) {return '';}
                   9631: 
                   9632:     my %fields = ('symb'=>&Apache::lonenc::check_encrypt($symb),
1.618     www      9633:                   'command'=>'individual');
1.538     schulted 9634:     
1.598     www      9635:     my $url1a = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9636: 
                   9637:     $fields{'command'}='ungraded';
                   9638:     my $url1b=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9639: 
                   9640:     $fields{'command'}='table';
                   9641:     my $url1c=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9642: 
                   9643:     $fields{'command'}='all_for_one';
                   9644:     my $url1d=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9645: 
1.621     www      9646:     $fields{'command'}='downloadfilesselect';
                   9647:     my $url1e=&Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9648: 
1.443     banghart 9649:     $fields{'command'} = 'csvform';
1.538     schulted 9650:     my $url2 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9651:     
1.443     banghart 9652:     $fields{'command'} = 'processclicker';
1.538     schulted 9653:     my $url3 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
                   9654:     
1.443     banghart 9655:     $fields{'command'} = 'scantron_selectphase';
1.538     schulted 9656:     my $url4 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
1.602     www      9657: 
                   9658:     $fields{'command'} = 'initialverifyreceipt';
                   9659:     my $url5 = &Apache::lonhtmlcommon::build_url('grades/',\%fields);
1.538     schulted 9660:     
1.598     www      9661:     my @menu = ({	categorytitle=>'Hand Grading',
1.538     schulted 9662:             items =>[
1.598     www      9663:                         {	linktext => 'Select individual students to grade',
                   9664:                     		url => $url1a,
1.538     schulted 9665:                     		permission => 'F',
1.636     wenzelju 9666:                     		icon => 'grade_students.png',
1.598     www      9667:                     		linktitle => 'Grade current resource for a selection of students.'
                   9668:                         }, 
                   9669:                         {       linktext => 'Grade ungraded submissions.',
                   9670:                                 url => $url1b,
                   9671:                                 permission => 'F',
1.636     wenzelju 9672:                                 icon => 'ungrade_sub.png',
1.598     www      9673:                                 linktitle => 'Grade all submissions that have not been graded yet.'
1.538     schulted 9674:                         },
1.598     www      9675: 
                   9676:                         {       linktext => 'Grading table',
                   9677:                                 url => $url1c,
                   9678:                                 permission => 'F',
1.636     wenzelju 9679:                                 icon => 'grading_table.png',
1.598     www      9680:                                 linktitle => 'Grade current resource for all students.'
                   9681:                         },
1.615     www      9682:                         {       linktext => 'Grade page/folder for one student',
1.598     www      9683:                                 url => $url1d,
                   9684:                                 permission => 'F',
1.636     wenzelju 9685:                                 icon => 'grade_PageFolder.png',
1.598     www      9686:                                 linktitle => 'Grade all resources in current page/sequence/folder for one student.'
1.621     www      9687:                         },
                   9688:                         {       linktext => 'Download submissions',
                   9689:                                 url => $url1e,
                   9690:                                 permission => 'F',
1.636     wenzelju 9691:                                 icon => 'download_sub.png',
1.621     www      9692:                                 linktitle => 'Download all students submissions.'
1.598     www      9693:                         }]},
                   9694:                          { categorytitle=>'Automated Grading',
                   9695:                items =>[
                   9696: 
1.538     schulted 9697:                 	    {	linktext => 'Upload Scores',
                   9698:                     		url => $url2,
                   9699:                     		permission => 'F',
                   9700:                     		icon => 'uploadscores.png',
                   9701:                     		linktitle => 'Specify a file containing the class scores for current resource.'
                   9702:                 	    },
                   9703:                 	    {	linktext => 'Process Clicker',
                   9704:                     		url => $url3,
                   9705:                     		permission => 'F',
                   9706:                     		icon => 'addClickerInfoFile.png',
                   9707:                     		linktitle => 'Specify a file containing the clicker information for this resource.'
                   9708:                 	    },
1.587     raeburn  9709:                 	    {	linktext => 'Grade/Manage/Review Bubblesheets',
1.538     schulted 9710:                     		url => $url4,
                   9711:                     		permission => 'F',
1.636     wenzelju 9712:                     		icon => 'bubblesheet.png',
1.648     bisitz   9713:                     		linktitle => 'Grade bubblesheet exams, upload/download bubblesheet data files, and review previously graded bubblesheet exams.'
1.602     www      9714:                 	    },
1.616     www      9715:                             {   linktext => 'Verify Receipt Number',
1.602     www      9716:                                 url => $url5,
                   9717:                                 permission => 'F',
1.636     wenzelju 9718:                                 icon => 'receipt_number.png',
1.602     www      9719:                                 linktitle => 'Verify a system-generated receipt number for correct problem solution.'
                   9720:                             }
                   9721: 
1.538     schulted 9722:                     ]
                   9723:             });
                   9724: 
1.443     banghart 9725:     # Create the menu
                   9726:     my $Str;
1.445     banghart 9727:     $Str .= '<form method="post" action="" name="gradingMenu">';
                   9728:     $Str .= '<input type="hidden" name="command" value="" />'.
1.618     www      9729:     	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
1.445     banghart 9730: 
1.602     www      9731:     $Str .= &Apache::lonhtmlcommon::generate_menu(@menu);
1.443     banghart 9732:     return $Str;    
                   9733: }
                   9734: 
1.598     www      9735: 
                   9736: sub ungraded {
                   9737:     my ($request)=@_;
                   9738:     &submit_options($request);
                   9739: }
                   9740: 
1.599     www      9741: sub submit_options_sequence {
1.608     www      9742:     my ($request,$symb) = @_;
1.599     www      9743:     if (!$symb) {return '';}
1.600     www      9744:     &commonJSfunctions($request);
                   9745:     my $result;
1.599     www      9746: 
1.600     www      9747:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
1.618     www      9748:         '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
1.632     www      9749:     $result.=&selectfield(0).
1.601     www      9750:             '<input type="hidden" name="command" value="pickStudentPage" />
1.600     www      9751:             <div>
                   9752:               <input type="submit" value="'.&mt('Next').' &rarr;" />
                   9753:             </div>
                   9754:         </div>
                   9755:   </form>';
                   9756:     return $result;
                   9757: }
                   9758: 
                   9759: sub submit_options_table {
1.608     www      9760:     my ($request,$symb) = @_;
1.600     www      9761:     if (!$symb) {return '';}
1.599     www      9762:     &commonJSfunctions($request);
1.746   ! raeburn  9763:     my $is_tool = ($symb =~ /ext\.tool$/);
1.599     www      9764:     my $result;
                   9765: 
                   9766:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
1.618     www      9767:         '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
1.599     www      9768: 
1.745     raeburn  9769:     $result.=&selectfield(1,$is_tool).
1.601     www      9770:             '<input type="hidden" name="command" value="viewgrades" />
1.599     www      9771:             <div>
                   9772:               <input type="submit" value="'.&mt('Next').' &rarr;" />
                   9773:             </div>
                   9774:         </div>
                   9775:   </form>';
                   9776:     return $result;
                   9777: }
1.443     banghart 9778: 
1.621     www      9779: sub submit_options_download {
                   9780:     my ($request,$symb) = @_;
                   9781:     if (!$symb) {return '';}
                   9782: 
1.746   ! raeburn  9783:     my $is_tool = ($symb =~ /ext\.tool$/);
1.621     www      9784:     &commonJSfunctions($request);
                   9785: 
                   9786:     my $result='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
                   9787:         '<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
                   9788:     $result.='
                   9789: <h2>
                   9790:   '.&mt('Select Students for Which to Download Submissions').'
1.745     raeburn  9791: </h2>'.&selectfield(1,$is_tool).'
1.621     www      9792:                 <input type="hidden" name="command" value="downloadfileslink" /> 
                   9793:               <input type="submit" value="'.&mt('Next').' &rarr;" />
                   9794:             </div>
                   9795:           </div>
1.600     www      9796: 
                   9797: 
1.621     www      9798:   </form>';
                   9799:     return $result;
                   9800: }
                   9801: 
1.443     banghart 9802: #--- Displays the submissions first page -------
                   9803: sub submit_options {
1.608     www      9804:     my ($request,$symb) = @_;
1.72      ng       9805:     if (!$symb) {return '';}
                   9806: 
1.746   ! raeburn  9807:     my $is_tool = ($symb =~ /ext\.tool$/);
1.118     ng       9808:     &commonJSfunctions($request);
1.473     albertel 9809:     my $result;
1.533     bisitz   9810: 
1.72      ng       9811:     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
1.618     www      9812: 	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n";
1.745     raeburn  9813:     $result.=&selectfield(1,$is_tool).'
1.601     www      9814:                 <input type="hidden" name="command" value="submission" /> 
                   9815: 	      <input type="submit" value="'.&mt('Next').' &rarr;" />
                   9816:             </div>
                   9817:           </div>
                   9818: 
                   9819: 
                   9820:   </form>';
                   9821:     return $result;
                   9822: }
1.533     bisitz   9823: 
1.601     www      9824: sub selectfield {
1.745     raeburn  9825:    my ($full,$is_tool)=@_;
                   9826:    my %options;
                   9827:    if ($is_tool) {
                   9828:        %options =
                   9829:            (&transtatus_options,
                   9830:             'select_form_order' => ['yes','incorrect','all']);
                   9831:    } else {
                   9832:        %options = 
                   9833:            (&substatus_options,
                   9834:             'select_form_order' => ['yes','queued','graded','incorrect','all']);
                   9835:    }
1.601     www      9836:    my $result='<div class="LC_columnSection">
1.537     harmsja  9837:   
1.533     bisitz   9838:     <fieldset>
                   9839:       <legend>
                   9840:        '.&mt('Sections').'
                   9841:       </legend>
1.601     www      9842:       '.&Apache::lonstatistics::SectionSelect('section','multiple',5).'
1.533     bisitz   9843:     </fieldset>
1.537     harmsja  9844:   
1.533     bisitz   9845:     <fieldset>
                   9846:       <legend>
                   9847:         '.&mt('Groups').'
                   9848:       </legend>
                   9849:       '.&Apache::lonstatistics::GroupSelect('group','multiple',5).'
                   9850:     </fieldset>
1.537     harmsja  9851:   
1.533     bisitz   9852:     <fieldset>
                   9853:       <legend>
                   9854:         '.&mt('Access Status').'
                   9855:       </legend>
1.601     www      9856:       '.&Apache::lonhtmlcommon::StatusOptions(undef,undef,5,undef,'mult').'
                   9857:     </fieldset>';
                   9858:     if ($full) {
1.745     raeburn  9859:         my $heading = &mt('Submission Status');
                   9860:         if ($is_tool) {
                   9861:             $heading = &mt('Transaction Status');
                   9862:         }
                   9863:         $result.='
1.533     bisitz   9864:     <fieldset>
                   9865:       <legend>
1.745     raeburn  9866:         '.$heading.'
1.601     www      9867:       </legend>'.
1.635     raeburn  9868:        &Apache::loncommon::select_form('all','submitonly',\%options).
1.601     www      9869:    '</fieldset>';
                   9870:     }
                   9871:     $result.='</div><br />';
1.44      ng       9872:     return $result;
1.2       albertel 9873: }
                   9874: 
1.738     raeburn  9875: sub substatus_options {
                   9876:     return &Apache::lonlocal::texthash(
                   9877:                                       'yes'       => 'with submissions',
                   9878:                                       'queued'    => 'in grading queue',
                   9879:                                       'graded'    => 'with ungraded submissions',
                   9880:                                       'incorrect' => 'with incorrect submissions',
1.740     raeburn  9881:                                       'all'       => 'with any status',
                   9882:                                       );
1.738     raeburn  9883: }
                   9884: 
1.745     raeburn  9885: sub transtatus_options {
                   9886:     return &Apache::lonlocal::texthash(
                   9887:                                        'yes'       => 'with score transactions',
                   9888:                                        'incorrect' => 'with less than full credit',
                   9889:                                        'all'       => 'with any status',
                   9890:                                       );
                   9891: }
                   9892: 
1.285     albertel 9893: sub reset_perm {
                   9894:     undef(%perm);
                   9895: }
                   9896: 
                   9897: sub init_perm {
                   9898:     &reset_perm();
1.300     albertel 9899:     foreach my $test_perm ('vgr','mgr','opa') {
                   9900: 
                   9901: 	my $scope = $env{'request.course.id'};
                   9902: 	if (!($perm{$test_perm}=&Apache::lonnet::allowed($test_perm,$scope))) {
                   9903: 
                   9904: 	    $scope .= '/'.$env{'request.course.sec'};
                   9905: 	    if ( $perm{$test_perm}=
                   9906: 		 &Apache::lonnet::allowed($test_perm,$scope)) {
                   9907: 		$perm{$test_perm.'_section'}=$env{'request.course.sec'};
                   9908: 	    } else {
                   9909: 		delete($perm{$test_perm});
                   9910: 	    }
1.285     albertel 9911: 	}
                   9912:     }
                   9913: }
                   9914: 
1.674     raeburn  9915: sub init_old_essays {
                   9916:     my ($symb,$apath,$adom,$aname) = @_;
                   9917:     if ($symb ne '') {
                   9918:         my %essays = &Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname);
                   9919:         if (keys(%essays) > 0) {
                   9920:             $old_essays{$symb} = \%essays;
                   9921:         }
                   9922:     }
                   9923:     return;
                   9924: }
                   9925: 
                   9926: sub reset_old_essays {
                   9927:     undef(%old_essays);
                   9928: }
                   9929: 
1.400     www      9930: sub gather_clicker_ids {
1.408     albertel 9931:     my %clicker_ids;
1.400     www      9932: 
                   9933:     my $classlist = &Apache::loncoursedata::get_classlist();
                   9934: 
                   9935:     # Set up a couple variables.
1.407     albertel 9936:     my $username_idx = &Apache::loncoursedata::CL_SNAME();
                   9937:     my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
1.438     www      9938:     my $status_idx   = &Apache::loncoursedata::CL_STATUS();
1.400     www      9939: 
1.407     albertel 9940:     foreach my $student (keys(%$classlist)) {
1.438     www      9941:         if ($classlist->{$student}->[$status_idx] ne 'Active') { next; }
1.407     albertel 9942:         my $username = $classlist->{$student}->[$username_idx];
                   9943:         my $domain   = $classlist->{$student}->[$domain_idx];
1.400     www      9944:         my $clickers =
1.408     albertel 9945: 	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
1.400     www      9946:         foreach my $id (split(/\,/,$clickers)) {
1.414     www      9947:             $id=~s/^[\#0]+//;
1.421     www      9948:             $id=~s/[\-\:]//g;
1.407     albertel 9949:             if (exists($clicker_ids{$id})) {
1.408     albertel 9950: 		$clicker_ids{$id}.=','.$username.':'.$domain;
1.400     www      9951:             } else {
1.408     albertel 9952: 		$clicker_ids{$id}=$username.':'.$domain;
1.400     www      9953:             }
                   9954:         }
                   9955:     }
1.407     albertel 9956:     return %clicker_ids;
1.400     www      9957: }
                   9958: 
1.402     www      9959: sub gather_adv_clicker_ids {
1.408     albertel 9960:     my %clicker_ids;
1.402     www      9961:     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
                   9962:     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
                   9963:     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
1.409     albertel 9964:     foreach my $element (sort(keys(%coursepersonnel))) {
1.402     www      9965:         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
                   9966:             my ($puname,$pudom)=split(/\:/,$person);
                   9967:             my $clickers =
1.408     albertel 9968: 		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
1.405     www      9969:             foreach my $id (split(/\,/,$clickers)) {
1.414     www      9970: 		$id=~s/^[\#0]+//;
1.421     www      9971:                 $id=~s/[\-\:]//g;
1.408     albertel 9972: 		if (exists($clicker_ids{$id})) {
                   9973: 		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
                   9974: 		} else {
                   9975: 		    $clicker_ids{$id}=$puname.':'.$pudom;
                   9976: 		}
1.405     www      9977:             }
1.402     www      9978:         }
                   9979:     }
1.407     albertel 9980:     return %clicker_ids;
1.402     www      9981: }
                   9982: 
1.413     www      9983: sub clicker_grading_parameters {
                   9984:     return ('gradingmechanism' => 'scalar',
                   9985:             'upfiletype' => 'scalar',
                   9986:             'specificid' => 'scalar',
                   9987:             'pcorrect' => 'scalar',
                   9988:             'pincorrect' => 'scalar');
                   9989: }
                   9990: 
1.400     www      9991: sub process_clicker {
1.608     www      9992:     my ($r,$symb)=@_;
1.400     www      9993:     if (!$symb) {return '';}
                   9994:     my $result=&checkforfile_js();
1.632     www      9995:     $result.=&Apache::loncommon::start_data_table().
                   9996:              &Apache::loncommon::start_data_table_header_row().
                   9997:              '<th>'.&mt('Specify a file containing clicker information and set grading options.').'</th>'.
                   9998:              &Apache::loncommon::end_data_table_header_row().
                   9999:              &Apache::loncommon::start_data_table_row()."<td>\n";
1.413     www      10000: # Attempt to restore parameters from last session, set defaults if not present
                   10001:     my %Saveable_Parameters=&clicker_grading_parameters();
                   10002:     &Apache::loncommon::restore_course_settings('grades_clicker',
                   10003:                                                  \%Saveable_Parameters);
                   10004:     if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
                   10005:     if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
                   10006:     if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
                   10007:     if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
                   10008: 
                   10009:     my %checked;
1.521     www      10010:     foreach my $gradingmechanism ('attendance','personnel','specific','given') {
1.413     www      10011:        if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
1.569     bisitz   10012:           $checked{$gradingmechanism}=' checked="checked"';
1.413     www      10013:        }
                   10014:     }
                   10015: 
1.632     www      10016:     my $upload=&mt("Evaluate File");
1.400     www      10017:     my $type=&mt("Type");
1.402     www      10018:     my $attendance=&mt("Award points just for participation");
                   10019:     my $personnel=&mt("Correctness determined from response by course personnel");
1.414     www      10020:     my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
1.521     www      10021:     my $given=&mt("Correctness determined from given list of answers").' '.
                   10022:               '<font size="-2"><tt>('.&mt("Provide comma-separated list. Use '*' for any answer correct, '-' for skip").')</tt></font>';
1.402     www      10023:     my $pcorrect=&mt("Percentage points for correct solution");
                   10024:     my $pincorrect=&mt("Percentage points for incorrect solution");
1.413     www      10025:     my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
1.635     raeburn  10026: 						   {'iclicker' => 'i>clicker',
1.666     www      10027:                                                     'interwrite' => 'interwrite PRS',
                   10028:                                                     'turning' => 'Turning Technologies'});
1.418     albertel 10029:     $symb = &Apache::lonenc::check_encrypt($symb);
1.597     wenzelju 10030:     $result.= &Apache::lonhtmlcommon::scripttag(<<ENDUPFORM);
1.402     www      10031: function sanitycheck() {
                   10032: // Accept only integer percentages
                   10033:    document.forms.gradesupload.pcorrect.value=Math.round(document.forms.gradesupload.pcorrect.value);
                   10034:    document.forms.gradesupload.pincorrect.value=Math.round(document.forms.gradesupload.pincorrect.value);
                   10035: // Find out grading choice
                   10036:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
                   10037:       if (document.forms.gradesupload.gradingmechanism[i].checked) {
                   10038:          gradingchoice=document.forms.gradesupload.gradingmechanism[i].value;
                   10039:       }
                   10040:    }
                   10041: // By default, new choice equals user selection
                   10042:    newgradingchoice=gradingchoice;
                   10043: // Not good to give more points for false answers than correct ones
                   10044:    if (Math.round(document.forms.gradesupload.pcorrect.value)<Math.round(document.forms.gradesupload.pincorrect.value)) {
                   10045:       document.forms.gradesupload.pcorrect.value=document.forms.gradesupload.pincorrect.value;
                   10046:    }
                   10047: // If new choice is attendance only, and old choice was correctness-based, restore defaults
                   10048:    if ((gradingchoice=='attendance') && (document.forms.gradesupload.waschecked.value!='attendance')) {
                   10049:       document.forms.gradesupload.pcorrect.value=100;
                   10050:       document.forms.gradesupload.pincorrect.value=100;
                   10051:    }
                   10052: // If the values are different, cannot be attendance only
                   10053:    if ((Math.round(document.forms.gradesupload.pcorrect.value)!=Math.round(document.forms.gradesupload.pincorrect.value)) &&
                   10054:        (gradingchoice=='attendance')) {
                   10055:        newgradingchoice='personnel';
                   10056:    }
                   10057: // Change grading choice to new one
                   10058:    for (i=0; i<document.forms.gradesupload.gradingmechanism.length; i++) {
                   10059:       if (document.forms.gradesupload.gradingmechanism[i].value==newgradingchoice) {
                   10060:          document.forms.gradesupload.gradingmechanism[i].checked=true;
                   10061:       } else {
                   10062:          document.forms.gradesupload.gradingmechanism[i].checked=false;
                   10063:       }
                   10064:    }
                   10065: // Remember the old state
                   10066:    document.forms.gradesupload.waschecked.value=newgradingchoice;
                   10067: }
1.597     wenzelju 10068: ENDUPFORM
                   10069:     $result.= <<ENDUPFORM;
1.400     www      10070: <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
                   10071: <input type="hidden" name="symb" value="$symb" />
                   10072: <input type="hidden" name="command" value="processclickerfile" />
                   10073: <input type="file" name="upfile" size="50" />
                   10074: <br /><label>$type: $selectform</label>
1.632     www      10075: ENDUPFORM
                   10076:     $result.='</td>'.&Apache::loncommon::end_data_table_row().
                   10077:                      &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDGRADINGFORM);
                   10078:       <label><input type="radio" name="gradingmechanism" value="attendance"$checked{'attendance'} onclick="sanitycheck()" />$attendance </label>
1.589     bisitz   10079: <br /><label><input type="radio" name="gradingmechanism" value="personnel"$checked{'personnel'} onclick="sanitycheck()" />$personnel</label>
                   10080: <br /><label><input type="radio" name="gradingmechanism" value="specific"$checked{'specific'} onclick="sanitycheck()" />$specific </label>
1.414     www      10081: <input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
1.589     bisitz   10082: <br /><label><input type="radio" name="gradingmechanism" value="given"$checked{'given'} onclick="sanitycheck()" />$given </label>
1.521     www      10083: <br />&nbsp;&nbsp;&nbsp;
                   10084: <input type="text" name="givenanswer" size="50" />
1.413     www      10085: <input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
1.632     www      10086: ENDGRADINGFORM
                   10087:          $result.='</td>'.&Apache::loncommon::end_data_table_row().
                   10088:                      &Apache::loncommon::start_data_table_row().'<td>'.(<<ENDPERCFORM);
                   10089:       <label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onchange="sanitycheck()" /></label>
1.589     bisitz   10090: <br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onchange="sanitycheck()" /></label>
                   10091: <br /><input type="button" onclick="javascript:checkUpload(this.form);" value="$upload" />
1.597     wenzelju 10092: </form>'
1.632     www      10093: ENDPERCFORM
                   10094:     $result.='</td>'.
                   10095:              &Apache::loncommon::end_data_table_row().
                   10096:              &Apache::loncommon::end_data_table();
1.400     www      10097:     return $result;
                   10098: }
                   10099: 
                   10100: sub process_clicker_file {
1.608     www      10101:     my ($r,$symb)=@_;
1.400     www      10102:     if (!$symb) {return '';}
1.413     www      10103: 
                   10104:     my %Saveable_Parameters=&clicker_grading_parameters();
                   10105:     &Apache::loncommon::store_course_settings('grades_clicker',
                   10106:                                               \%Saveable_Parameters);
1.598     www      10107:     my $result='';
1.404     www      10108:     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
1.408     albertel 10109: 	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
1.614     www      10110: 	return $result;
1.404     www      10111:     }
1.522     www      10112:     if (($env{'form.gradingmechanism'} eq 'given') && ($env{'form.givenanswer'}!~/\S/)) {
1.521     www      10113:         $result.='<span class="LC_error">'.&mt('You need to specify the correct answer').'</span>';
1.614     www      10114:         return $result;
1.521     www      10115:     }
1.522     www      10116:     my $foundgiven=0;
1.521     www      10117:     if ($env{'form.gradingmechanism'} eq 'given') {
                   10118:         $env{'form.givenanswer'}=~s/^\s*//gs;
                   10119:         $env{'form.givenanswer'}=~s/\s*$//gs;
1.644     www      10120:         $env{'form.givenanswer'}=~s/[^a-zA-Z0-9\.\*\-\+]+/\,/g;
1.521     www      10121:         $env{'form.givenanswer'}=uc($env{'form.givenanswer'});
1.522     www      10122:         my @answers=split(/\,/,$env{'form.givenanswer'});
                   10123:         $foundgiven=$#answers+1;
1.521     www      10124:     }
1.407     albertel 10125:     my %clicker_ids=&gather_clicker_ids();
1.408     albertel 10126:     my %correct_ids;
1.404     www      10127:     if ($env{'form.gradingmechanism'} eq 'personnel') {
1.408     albertel 10128: 	%correct_ids=&gather_adv_clicker_ids();
1.404     www      10129:     }
                   10130:     if ($env{'form.gradingmechanism'} eq 'specific') {
1.414     www      10131: 	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
                   10132: 	   $correct_id=~tr/a-z/A-Z/;
                   10133: 	   $correct_id=~s/\s//gs;
                   10134: 	   $correct_id=~s/^[\#0]+//;
1.421     www      10135:            $correct_id=~s/[\-\:]//g;
1.414     www      10136:            if ($correct_id) {
                   10137: 	      $correct_ids{$correct_id}='specified';
                   10138:            }
                   10139:         }
1.400     www      10140:     }
1.404     www      10141:     if ($env{'form.gradingmechanism'} eq 'attendance') {
1.408     albertel 10142: 	$result.=&mt('Score based on attendance only');
1.521     www      10143:     } elsif ($env{'form.gradingmechanism'} eq 'given') {
1.522     www      10144:         $result.=&mt('Score based on [_1] ([_2] answers)','<tt>'.$env{'form.givenanswer'}.'</tt>',$foundgiven);
1.404     www      10145:     } else {
1.408     albertel 10146: 	my $number=0;
1.411     www      10147: 	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
1.408     albertel 10148: 	foreach my $id (sort(keys(%correct_ids))) {
1.411     www      10149: 	    $result.='<br /><tt>'.$id.'</tt> - ';
1.408     albertel 10150: 	    if ($correct_ids{$id} eq 'specified') {
                   10151: 		$result.=&mt('specified');
                   10152: 	    } else {
                   10153: 		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
                   10154: 		$result.=&Apache::loncommon::plainname($uname,$udom);
                   10155: 	    }
                   10156: 	    $number++;
                   10157: 	}
1.411     www      10158:         $result.="</p>\n";
1.710     bisitz   10159:         if ($number==0) {
                   10160:             $result .=
                   10161:                  &Apache::lonhtmlcommon::confirm_success(
                   10162:                      &mt('No IDs found to determine correct answer'),1);
                   10163:             return $result;
                   10164:         }
1.404     www      10165:     }
1.405     www      10166:     if (length($env{'form.upfile'}) < 2) {
1.710     bisitz   10167:         $result .=
                   10168:             &Apache::lonhtmlcommon::confirm_success(
                   10169:                 &mt('The file: [_1] you attempted to upload contained no information. Please check that you entered the correct filename.',
                   10170:                         '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>'),1);
1.614     www      10171:         return $result;
1.405     www      10172:     }
1.410     www      10173: 
                   10174: # Were able to get all the info needed, now analyze the file
                   10175: 
1.411     www      10176:     $result.=&Apache::loncommon::studentbrowser_javascript();
1.418     albertel 10177:     $symb = &Apache::lonenc::check_encrypt($symb);
1.632     www      10178:     $result.=&Apache::loncommon::start_data_table().
                   10179:              &Apache::loncommon::start_data_table_header_row().
                   10180:              '<th>'.&mt('Evaluate clicker file').'</th>'.
                   10181:              &Apache::loncommon::end_data_table_header_row().
                   10182:              &Apache::loncommon::start_data_table_row().(<<ENDHEADER);
                   10183: <td>
1.410     www      10184: <form method="post" action="/adm/grades" name="clickeranalysis">
                   10185: <input type="hidden" name="symb" value="$symb" />
                   10186: <input type="hidden" name="command" value="assignclickergrades" />
1.411     www      10187: <input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
                   10188: <input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
                   10189: <input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
1.410     www      10190: ENDHEADER
1.522     www      10191:     if ($env{'form.gradingmechanism'} eq 'given') {
                   10192:        $result.='<input type="hidden" name="correct:given" value="'.$env{'form.givenanswer'}.'" />';
                   10193:     } 
1.408     albertel 10194:     my %responses;
                   10195:     my @questiontitles;
1.405     www      10196:     my $errormsg='';
                   10197:     my $number=0;
                   10198:     if ($env{'form.upfiletype'} eq 'iclicker') {
1.408     albertel 10199: 	($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
1.406     www      10200:     }
1.419     www      10201:     if ($env{'form.upfiletype'} eq 'interwrite') {
                   10202:         ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
                   10203:     }
1.666     www      10204:     if ($env{'form.upfiletype'} eq 'turning') {
                   10205:         ($errormsg,$number)=&turning_eval(\@questiontitles,\%responses);
                   10206:     }
1.411     www      10207:     $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
                   10208:              '<input type="hidden" name="number" value="'.$number.'" />'.
                   10209:              &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
                   10210:                  $env{'form.pcorrect'},$env{'form.pincorrect'}).
                   10211:              '<br />';
1.522     www      10212:     if (($env{'form.gradingmechanism'} eq 'given') && ($number!=$foundgiven)) {
                   10213:        $result.='<span class="LC_error">'.&mt('Number of given answers does not agree with number of questions in file.').'</span>';
1.614     www      10214:        return $result;
1.522     www      10215:     } 
1.414     www      10216: # Remember Question Titles
                   10217: # FIXME: Possibly need delimiter other than ":"
                   10218:     for (my $i=0;$i<$number;$i++) {
                   10219:         $result.='<input type="hidden" name="question:'.$i.'" value="'.
                   10220:                  &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
                   10221:     }
1.411     www      10222:     my $correct_count=0;
                   10223:     my $student_count=0;
                   10224:     my $unknown_count=0;
1.414     www      10225: # Match answers with usernames
                   10226: # FIXME: Possibly need delimiter other than ":"
1.409     albertel 10227:     foreach my $id (keys(%responses)) {
1.410     www      10228:        if ($correct_ids{$id}) {
1.414     www      10229:           $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
1.411     www      10230:           $correct_count++;
1.410     www      10231:        } elsif ($clicker_ids{$id}) {
1.437     www      10232:           if ($clicker_ids{$id}=~/\,/) {
                   10233: # More than one user with the same clicker!
1.632     www      10234:              $result.="</td>".&Apache::loncommon::end_data_table_row().
                   10235:                            &Apache::loncommon::start_data_table_row()."<td>".
                   10236:                        &mt('Clicker registered more than once').": <tt>".$id."</tt><br />";
1.437     www      10237:              $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                   10238:                            "<select name='multi".$id."'>";
                   10239:              foreach my $reguser (sort(split(/\,/,$clicker_ids{$id}))) {
                   10240:                  $result.="<option value='".$reguser."'>".&Apache::loncommon::plainname(split(/\:/,$reguser)).' ('.$reguser.')</option>';
                   10241:              }
                   10242:              $result.='</select>';
                   10243:              $unknown_count++;
                   10244:           } else {
                   10245: # Good: found one and only one user with the right clicker
                   10246:              $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
                   10247:              $student_count++;
                   10248:           }
1.410     www      10249:        } else {
1.632     www      10250:           $result.="</td>".&Apache::loncommon::end_data_table_row().
                   10251:                            &Apache::loncommon::start_data_table_row()."<td>".
                   10252:                     &mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
1.411     www      10253:           $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
                   10254:                    "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
                   10255:                    "\n".&mt("Domain").": ".
                   10256:                    &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
1.643     www      10257:                    &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,0,$id);
1.411     www      10258:           $unknown_count++;
1.410     www      10259:        }
1.405     www      10260:     }
1.412     www      10261:     $result.='<hr />'.
                   10262:              &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
1.521     www      10263:     if (($env{'form.gradingmechanism'} ne 'attendance') && ($env{'form.gradingmechanism'} ne 'given')) {
1.412     www      10264:        if ($correct_count==0) {
1.696     bisitz   10265:           $errormsg.="Found no correct answers for grading!";
1.412     www      10266:        } elsif ($correct_count>1) {
1.414     www      10267:           $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
1.412     www      10268:        }
                   10269:     }
1.428     www      10270:     if ($number<1) {
                   10271:        $errormsg.="Found no questions.";
                   10272:     }
1.412     www      10273:     if ($errormsg) {
                   10274:        $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
                   10275:     } else {
                   10276:        $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
                   10277:     }
1.632     www      10278:     $result.='</form></td>'.
                   10279:              &Apache::loncommon::end_data_table_row().
                   10280:              &Apache::loncommon::end_data_table();
1.614     www      10281:     return $result;
1.400     www      10282: }
                   10283: 
1.405     www      10284: sub iclicker_eval {
1.406     www      10285:     my ($questiontitles,$responses)=@_;
1.405     www      10286:     my $number=0;
                   10287:     my $errormsg='';
                   10288:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
1.410     www      10289:         my %components=&Apache::loncommon::record_sep($line);
                   10290:         my @entries=map {$components{$_}} (sort(keys(%components)));
1.408     albertel 10291: 	if ($entries[0] eq 'Question') {
                   10292: 	    for (my $i=3;$i<$#entries;$i+=6) {
                   10293: 		$$questiontitles[$number]=$entries[$i];
                   10294: 		$number++;
                   10295: 	    }
                   10296: 	}
                   10297: 	if ($entries[0]=~/^\#/) {
                   10298: 	    my $id=$entries[0];
                   10299: 	    my @idresponses;
                   10300: 	    $id=~s/^[\#0]+//;
                   10301: 	    for (my $i=0;$i<$number;$i++) {
                   10302: 		my $idx=3+$i*6;
1.644     www      10303:                 $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+]+//g;
1.408     albertel 10304: 		push(@idresponses,$entries[$idx]);
                   10305: 	    }
                   10306: 	    $$responses{$id}=join(',',@idresponses);
                   10307: 	}
1.405     www      10308:     }
                   10309:     return ($errormsg,$number);
                   10310: }
                   10311: 
1.419     www      10312: sub interwrite_eval {
                   10313:     my ($questiontitles,$responses)=@_;
                   10314:     my $number=0;
                   10315:     my $errormsg='';
1.420     www      10316:     my $skipline=1;
                   10317:     my $questionnumber=0;
                   10318:     my %idresponses=();
1.419     www      10319:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
                   10320:         my %components=&Apache::loncommon::record_sep($line);
                   10321:         my @entries=map {$components{$_}} (sort(keys(%components)));
1.420     www      10322:         if ($entries[1] eq 'Time') { $skipline=0; next; }
                   10323:         if ($entries[1] eq 'Response') { $skipline=1; }
                   10324:         next if $skipline;
                   10325:         if ($entries[0]!=$questionnumber) {
                   10326:            $questionnumber=$entries[0];
                   10327:            $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
                   10328:            $number++;
1.419     www      10329:         }
1.420     www      10330:         my $id=$entries[4];
                   10331:         $id=~s/^[\#0]+//;
1.421     www      10332:         $id=~s/^v\d*\://i;
                   10333:         $id=~s/[\-\:]//g;
1.420     www      10334:         $idresponses{$id}[$number]=$entries[6];
                   10335:     }
1.524     raeburn  10336:     foreach my $id (keys(%idresponses)) {
1.420     www      10337:        $$responses{$id}=join(',',@{$idresponses{$id}});
                   10338:        $$responses{$id}=~s/^\s*\,//;
1.419     www      10339:     }
                   10340:     return ($errormsg,$number);
                   10341: }
                   10342: 
1.666     www      10343: sub turning_eval {
                   10344:     my ($questiontitles,$responses)=@_;
                   10345:     my $number=0;
                   10346:     my $errormsg='';
                   10347:     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
                   10348:         my %components=&Apache::loncommon::record_sep($line);
                   10349:         my @entries=map {$components{$_}} (sort(keys(%components)));
                   10350:         if ($#entries>$number) { $number=$#entries; }
                   10351:         my $id=$entries[0];
                   10352:         my @idresponses;
                   10353:         $id=~s/^[\#0]+//;
                   10354:         unless ($id) { next; }
                   10355:         for (my $idx=1;$idx<=$#entries;$idx++) {
                   10356:             $entries[$idx]=~s/\,/\;/g;
                   10357:             $entries[$idx]=~s/[^a-zA-Z0-9\.\*\-\+\;]+//g;
                   10358:             push(@idresponses,$entries[$idx]);
                   10359:         }
                   10360:         $$responses{$id}=join(',',@idresponses);
                   10361:     }
                   10362:     for (my $i=1; $i<=$number; $i++) {
                   10363:         $$questiontitles[$i]=&mt('Question [_1]',$i);
                   10364:     }
                   10365:     return ($errormsg,$number);
                   10366: }
                   10367: 
                   10368: 
1.414     www      10369: sub assign_clicker_grades {
1.608     www      10370:     my ($r,$symb)=@_;
1.414     www      10371:     if (!$symb) {return '';}
1.416     www      10372: # See which part we are saving to
1.582     raeburn  10373:     my $res_error;
                   10374:     my ($partlist,$handgrade,$responseType) = &response_type($symb,\$res_error);
                   10375:     if ($res_error) {
                   10376:         return &navmap_errormsg();
                   10377:     }
1.416     www      10378: # FIXME: This should probably look for the first handgradeable part
                   10379:     my $part=$$partlist[0];
                   10380: # Start screen output
1.632     www      10381:     my $result=&Apache::loncommon::start_data_table().
                   10382:              &Apache::loncommon::start_data_table_header_row().
                   10383:              '<th>'.&mt('Assigning grades based on clicker file').'</th>'.
                   10384:              &Apache::loncommon::end_data_table_header_row().
                   10385:              &Apache::loncommon::start_data_table_row().'<td>';
1.414     www      10386: # Get correct result
                   10387: # FIXME: Possibly need delimiter other than ":"
                   10388:     my @correct=();
1.415     www      10389:     my $gradingmechanism=$env{'form.gradingmechanism'};
                   10390:     my $number=$env{'form.number'};
                   10391:     if ($gradingmechanism ne 'attendance') {
1.414     www      10392:        foreach my $key (keys(%env)) {
                   10393:           if ($key=~/^form\.correct\:/) {
                   10394:              my @input=split(/\,/,$env{$key});
                   10395:              for (my $i=0;$i<=$#input;$i++) {
                   10396:                  if (($correct[$i]) && ($input[$i]) &&
                   10397:                      ($correct[$i] ne $input[$i])) {
                   10398:                     $result.='<br /><span class="LC_warning">'.
                   10399:                              &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
                   10400:                                  $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
1.644     www      10401:                  } elsif (($input[$i]) || ($input[$i] eq '0')) {
1.414     www      10402:                     $correct[$i]=$input[$i];
                   10403:                  }
                   10404:              }
                   10405:           }
                   10406:        }
1.415     www      10407:        for (my $i=0;$i<$number;$i++) {
1.644     www      10408:           if ((!$correct[$i]) && ($correct[$i] ne '0')) {
1.414     www      10409:              $result.='<br /><span class="LC_error">'.
                   10410:                       &mt('No correct result given for question "[_1]"!',
                   10411:                           $env{'form.question:'.$i}).'</span>';
                   10412:           }
                   10413:        }
1.644     www      10414:        $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ((($_) || ($_ eq '0'))?$_:'-') } @correct));
1.414     www      10415:     }
                   10416: # Start grading
1.415     www      10417:     my $pcorrect=$env{'form.pcorrect'};
                   10418:     my $pincorrect=$env{'form.pincorrect'};
1.416     www      10419:     my $storecount=0;
1.632     www      10420:     my %users=();
1.415     www      10421:     foreach my $key (keys(%env)) {
1.420     www      10422:        my $user='';
1.415     www      10423:        if ($key=~/^form\.student\:(.*)$/) {
1.420     www      10424:           $user=$1;
                   10425:        }
                   10426:        if ($key=~/^form\.unknown\:(.*)$/) {
                   10427:           my $id=$1;
                   10428:           if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
                   10429:              $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
1.437     www      10430:           } elsif ($env{'form.multi'.$id}) {
                   10431:              $user=$env{'form.multi'.$id};
1.420     www      10432:           }
                   10433:        }
1.632     www      10434:        if ($user) {
                   10435:           if ($users{$user}) {
                   10436:              $result.='<br /><span class="LC_warning">'.
1.696     bisitz   10437:                       &mt('More than one entry found for [_1]!','<tt>'.$user.'</tt>').
1.632     www      10438:                       '</span><br />';
                   10439:           }
                   10440:           $users{$user}=1; 
1.415     www      10441:           my @answer=split(/\,/,$env{$key});
                   10442:           my $sum=0;
1.522     www      10443:           my $realnumber=$number;
1.415     www      10444:           for (my $i=0;$i<$number;$i++) {
1.576     www      10445:              if  ($correct[$i] eq '-') {
                   10446:                 $realnumber--;
1.644     www      10447:              } elsif (($answer[$i]) || ($answer[$i]=~/^[0\.]+$/))  {
1.415     www      10448:                 if ($gradingmechanism eq 'attendance') {
                   10449:                    $sum+=$pcorrect;
1.576     www      10450:                 } elsif ($correct[$i] eq '*') {
1.522     www      10451:                    $sum+=$pcorrect;
1.415     www      10452:                 } else {
1.644     www      10453: # We actually grade if correct or not
                   10454:                    my $increment=$pincorrect;
                   10455: # Special case: numerical answer "0"
                   10456:                    if ($correct[$i] eq '0') {
                   10457:                       if ($answer[$i]=~/^[0\.]+$/) {
                   10458:                          $increment=$pcorrect;
                   10459:                       }
                   10460: # General numerical answer, both evaluate to something non-zero
                   10461:                    } elsif ((1.0*$correct[$i]!=0) && (1.0*$answer[$i]!=0)) {
                   10462:                       if (1.0*$correct[$i]==1.0*$answer[$i]) {
                   10463:                          $increment=$pcorrect;
                   10464:                       }
                   10465: # Must be just alphanumeric
                   10466:                    } elsif ($answer[$i] eq $correct[$i]) {
                   10467:                       $increment=$pcorrect;
1.415     www      10468:                    }
1.644     www      10469:                    $sum+=$increment;
1.415     www      10470:                 }
                   10471:              }
                   10472:           }
1.522     www      10473:           my $ave=$sum/(100*$realnumber);
1.416     www      10474: # Store
                   10475:           my ($username,$domain)=split(/\:/,$user);
                   10476:           my %grades=();
                   10477:           $grades{"resource.$part.solved"}='correct_by_override';
                   10478:           $grades{"resource.$part.awarded"}=$ave;
                   10479:           $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
                   10480:           my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
                   10481:                                                  $env{'request.course.id'},
                   10482:                                                  $domain,$username);
                   10483:           if ($returncode ne 'ok') {
                   10484:              $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
                   10485:           } else {
                   10486:              $storecount++;
                   10487:           }
1.415     www      10488:        }
                   10489:     }
                   10490: # We are done
1.549     hauer    10491:     $result.='<br />'.&mt('Successfully stored grades for [quant,_1,student].',$storecount).
1.632     www      10492:              '</td>'.
                   10493:              &Apache::loncommon::end_data_table_row().
                   10494:              &Apache::loncommon::end_data_table();
1.614     www      10495:     return $result;
1.414     www      10496: }
                   10497: 
1.582     raeburn  10498: sub navmap_errormsg {
                   10499:     return '<div class="LC_error">'.
                   10500:            &mt('An error occurred retrieving information about resources in the course.').'<br />'.
1.595     raeburn  10501:            &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  10502:            '</div>';
                   10503: }
1.607     droeschl 10504: 
1.609     www      10505: sub startpage {
1.671     raeburn  10506:     my ($r,$symb,$crumbs,$onlyfolderflag,$nodisplayflag,$stuvcurrent,$stuvdisp,$nomenu,$js) = @_;
                   10507:     if ($nomenu) {
                   10508:         $r->print(&Apache::loncommon::start_page("Student's Version",$js,{'only_body' => '1'}));
                   10509:     } else {
                   10510:         unshift(@$crumbs,{href=>&href_symb_cmd($symb,'gradingmenu'),text=>"Grading"});
                   10511:         $r->print(&Apache::loncommon::start_page('Grading',$js,
                   10512:                                                  {'bread_crumbs' => $crumbs}));
                   10513:         &Apache::lonquickgrades::startGradeScreen($r,($env{'form.symb'}?'probgrading':'grading'));
                   10514:     }
1.613     www      10515:     unless ($nodisplayflag) {
1.671     raeburn  10516:        $r->print(&Apache::lonhtmlcommon::resource_info_box($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp));
1.613     www      10517:     }
1.607     droeschl 10518: }
1.582     raeburn  10519: 
1.622     www      10520: sub select_problem {
                   10521:     my ($r)=@_;
1.632     www      10522:     $r->print('<h3>'.&mt('Select the problem or one of the problems you want to grade').'</h3><form action="/adm/grades">');
1.745     raeburn  10523:     $r->print(&Apache::lonstathelpers::problem_selector('.',undef,1,undef,undef,undef,undef,1));
1.622     www      10524:     $r->print('<input type="hidden" name="command" value="gradingmenu" />');
                   10525:     $r->print('<input type="submit" value="'.&mt('Next').' &rarr;" /></form>');
                   10526: }
                   10527: 
1.1       albertel 10528: sub handler {
1.41      ng       10529:     my $request=$_[0];
1.434     albertel 10530:     &reset_caches();
1.646     raeburn  10531:     if ($request->header_only) {
                   10532:         &Apache::loncommon::content_type($request,'text/html');
                   10533:         $request->send_http_header;
                   10534:         return OK;
                   10535:     }
                   10536:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'});
                   10537: 
1.664     raeburn  10538: # see what command we need to execute
                   10539: 
                   10540:     my @commands=&Apache::loncommon::get_env_multiple('form.command');
                   10541:     my $command=$commands[0];
                   10542: 
1.646     raeburn  10543:     &init_perm();
                   10544:     if (!$env{'request.course.id'}) {
1.664     raeburn  10545:         unless ((&Apache::lonnet::allowed('usc',$env{'request.role.domain'})) &&
                   10546:                 ($command =~ /^scantronupload/)) {
                   10547:             # Not in a course.
                   10548:             $env{'user.error.msg'}="/adm/grades::vgr:0:0:Cannot display grades page outside course context";
                   10549:             return HTTP_NOT_ACCEPTABLE;
                   10550:         }
1.646     raeburn  10551:     } elsif (!%perm) {
                   10552:         $request->internal_redirect('/adm/quickgrades');
1.687     raeburn  10553:         return OK;
1.41      ng       10554:     }
1.646     raeburn  10555:     &Apache::loncommon::content_type($request,'text/html');
1.41      ng       10556:     $request->send_http_header;
1.646     raeburn  10557: 
1.160     albertel 10558:     if ($#commands > 0) {
                   10559: 	&Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands));
                   10560:     }
1.608     www      10561: 
                   10562: # see what the symb is
                   10563: 
                   10564:     my $symb=$env{'form.symb'};
                   10565:     unless ($symb) {
                   10566:        (my $url=$env{'form.url'}) =~ s-^https*://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
                   10567:        $symb=&Apache::lonnet::symbread($url);
                   10568:     }
1.646     raeburn  10569:     &Apache::lonenc::check_decrypt(\$symb);
1.608     www      10570: 
1.513     foxr     10571:     $ssi_error = 0;
1.637     www      10572:     if (($symb eq '' || $command eq '') && ($env{'request.course.id'})) {
1.601     www      10573: #
1.637     www      10574: # Not called from a resource, but inside a course
1.601     www      10575: #    
1.622     www      10576:         &startpage($request,undef,[],1,1);
                   10577:         &select_problem($request);
1.41      ng       10578:     } else {
1.104     albertel 10579: 	if ($command eq 'submission' && $perm{'vgr'}) {
1.671     raeburn  10580:             my ($stuvcurrent,$stuvdisp,$versionform,$js);
                   10581:             if (($env{'form.student'} ne '') && ($env{'form.userdom'} ne '')) {
                   10582:                 ($stuvcurrent,$stuvdisp,$versionform,$js) =
                   10583:                     &choose_task_version_form($symb,$env{'form.student'},
                   10584:                                               $env{'form.userdom'});
                   10585:             }
                   10586:             &startpage($request,$symb,[{href=>"", text=>"Student Submissions"}],undef,undef,$stuvcurrent,$stuvdisp,undef,$js);
                   10587:             if ($versionform) {
                   10588:                 $request->print($versionform);
                   10589:             }
                   10590:             $request->print('<br clear="all" />');
1.611     www      10591: 	    ($env{'form.student'} eq '' ? &listStudents($request,$symb) : &submission($request,0,0,$symb));
1.671     raeburn  10592:         } elsif ($command eq 'versionsub' && $perm{'vgr'}) {
                   10593:             my ($stuvcurrent,$stuvdisp,$versionform,$js) =
                   10594:                 &choose_task_version_form($symb,$env{'form.student'},
                   10595:                                           $env{'form.userdom'},
                   10596:                                           $env{'form.inhibitmenu'});
                   10597:             &startpage($request,$symb,[{href=>"", text=>"Previous Student Version"}],undef,undef,$stuvcurrent,$stuvdisp,$env{'form.inhibitmenu'},$js);
                   10598:             if ($versionform) {
                   10599:                 $request->print($versionform);
                   10600:             }
                   10601:             $request->print('<br clear="all" />');
                   10602:             $request->print(&show_previous_task_version($request,$symb));
1.103     albertel 10603: 	} elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) {
1.615     www      10604:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                   10605:                                        {href=>'',text=>'Select student'}],1,1);
1.608     www      10606: 	    &pickStudentPage($request,$symb);
1.103     albertel 10607: 	} elsif ($command eq 'displayPage' && $perm{'vgr'}) {
1.615     www      10608:             &startpage($request,$symb,
                   10609:                                       [{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                   10610:                                        {href=>'',text=>'Select student'},
                   10611:                                        {href=>'',text=>'Grade student'}],1,1);
1.608     www      10612: 	    &displayPage($request,$symb);
1.104     albertel 10613: 	} elsif ($command eq 'gradeByPage' && $perm{'mgr'}) {
1.616     www      10614:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'all_for_one'),text=>'Grade page/folder for one student'},
                   10615:                                        {href=>'',text=>'Select student'},
                   10616:                                        {href=>'',text=>'Grade student'},
                   10617:                                        {href=>'',text=>'Store grades'}],1,1);
1.608     www      10618: 	    &updateGradeByPage($request,$symb);
1.104     albertel 10619: 	} elsif ($command eq 'processGroup' && $perm{'vgr'}) {
1.619     www      10620:             &startpage($request,$symb,[{href=>'',text=>'...'},
                   10621:                                        {href=>'',text=>'Modify grades'}]);
1.608     www      10622: 	    &processGroup($request,$symb);
1.104     albertel 10623: 	} elsif ($command eq 'gradingmenu' && $perm{'vgr'}) {
1.608     www      10624:             &startpage($request,$symb);
                   10625: 	    $request->print(&grading_menu($request,$symb));
1.598     www      10626: 	} elsif ($command eq 'individual' && $perm{'vgr'}) {
1.617     www      10627:             &startpage($request,$symb,[{href=>'',text=>'Select individual students to grade'}]);
1.608     www      10628: 	    $request->print(&submit_options($request,$symb));
1.598     www      10629:         } elsif ($command eq 'ungraded' && $perm{'vgr'}) {
1.617     www      10630:             &startpage($request,$symb,[{href=>'',text=>'Grade ungraded submissions'}]);
                   10631:             $request->print(&listStudents($request,$symb,'graded'));
1.598     www      10632:         } elsif ($command eq 'table' && $perm{'vgr'}) {
1.614     www      10633:             &startpage($request,$symb,[{href=>"", text=>"Grading table"}]);
1.611     www      10634:             $request->print(&submit_options_table($request,$symb));
1.598     www      10635:         } elsif ($command eq 'all_for_one' && $perm{'vgr'}) {
1.615     www      10636:             &startpage($request,$symb,[{href=>'',text=>'Grade page/folder for one student'}],1,1);
1.608     www      10637:             $request->print(&submit_options_sequence($request,$symb));
1.104     albertel 10638: 	} elsif ($command eq 'viewgrades' && $perm{'vgr'}) {
1.614     www      10639:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},{href=>'', text=>"Modify grades"}]);
1.608     www      10640: 	    $request->print(&viewgrades($request,$symb));
1.104     albertel 10641: 	} elsif ($command eq 'handgrade' && $perm{'mgr'}) {
1.620     www      10642:             &startpage($request,$symb,[{href=>'',text=>'...'},
                   10643:                                        {href=>'',text=>'Store grades'}]);
1.608     www      10644: 	    $request->print(&processHandGrade($request,$symb));
1.106     albertel 10645: 	} elsif ($command eq 'editgrades' && $perm{'mgr'}) {
1.614     www      10646:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"table"), text=>"Grading table"},
                   10647:                                        {href=>&href_symb_cmd($symb,'viewgrades').'&group=all&section=all&Status=Active',
                   10648:                                                                              text=>"Modify grades"},
                   10649:                                        {href=>'', text=>"Store grades"}]);
1.608     www      10650: 	    $request->print(&editgrades($request,$symb));
1.602     www      10651:         } elsif ($command eq 'initialverifyreceipt' && $perm{'vgr'}) {
1.616     www      10652:             &startpage($request,$symb,[{href=>'',text=>'Verify Receipt Number'}]);
1.611     www      10653:             $request->print(&initialverifyreceipt($request,$symb));
1.106     albertel 10654: 	} elsif ($command eq 'verify' && $perm{'vgr'}) {
1.616     www      10655:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,"initialverifyreceipt"),text=>'Verify Receipt Number'},
                   10656:                                        {href=>'',text=>'Verification Result'}]);
1.608     www      10657: 	    $request->print(&verifyreceipt($request,$symb));
1.400     www      10658:         } elsif ($command eq 'processclicker' && $perm{'mgr'}) {
1.615     www      10659:             &startpage($request,$symb,[{href=>'', text=>'Process clicker'}]);
1.608     www      10660:             $request->print(&process_clicker($request,$symb));
1.400     www      10661:         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
1.615     www      10662:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
                   10663:                                        {href=>'', text=>'Process clicker file'}]);
1.608     www      10664:             $request->print(&process_clicker_file($request,$symb));
1.414     www      10665:         } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
1.615     www      10666:             &startpage($request,$symb,[{href=>&href_symb_cmd($symb,'processclicker'), text=>'Process clicker'},
                   10667:                                        {href=>'', text=>'Process clicker file'},
                   10668:                                        {href=>'', text=>'Store grades'}]);
1.608     www      10669:             $request->print(&assign_clicker_grades($request,$symb));
1.106     albertel 10670: 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
1.627     www      10671:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10672: 	    $request->print(&upcsvScores_form($request,$symb));
1.106     albertel 10673: 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {
1.627     www      10674:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10675: 	    $request->print(&csvupload($request,$symb));
1.106     albertel 10676: 	} elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) {
1.627     www      10677:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10678: 	    $request->print(&csvuploadmap($request,$symb));
1.246     albertel 10679: 	} elsif ($command eq 'csvuploadoptions' && $perm{'mgr'}) {
1.257     albertel 10680: 	    if ($env{'form.associate'} ne 'Reverse Association') {
1.627     www      10681:                 &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10682: 		$request->print(&csvuploadoptions($request,$symb));
1.41      ng       10683: 	    } else {
1.257     albertel 10684: 		if ( $env{'form.upfile_associate'} ne 'reverse' ) {
                   10685: 		    $env{'form.upfile_associate'} = 'reverse';
1.41      ng       10686: 		} else {
1.257     albertel 10687: 		    $env{'form.upfile_associate'} = 'forward';
1.41      ng       10688: 		}
1.627     www      10689:                 &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10690: 		$request->print(&csvuploadmap($request,$symb));
1.41      ng       10691: 	    }
1.246     albertel 10692: 	} elsif ($command eq 'csvuploadassign' && $perm{'mgr'} ) {
1.627     www      10693:             &startpage($request,$symb,[{href=>'', text=>'Upload Scores'}],1,1);
1.608     www      10694: 	    $request->print(&csvuploadassign($request,$symb));
1.106     albertel 10695: 	} elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) {
1.616     www      10696:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.612     www      10697: 	    $request->print(&scantron_selectphase($request,undef,$symb));
1.203     albertel 10698:  	} elsif ($command eq 'scantron_warning' && $perm{'mgr'}) {
1.616     www      10699:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10700:  	    $request->print(&scantron_do_warning($request,$symb));
1.142     albertel 10701: 	} elsif ($command eq 'scantron_validate' && $perm{'mgr'}) {
1.616     www      10702:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10703: 	    $request->print(&scantron_validate_file($request,$symb));
1.106     albertel 10704: 	} elsif ($command eq 'scantron_process' && $perm{'mgr'}) {
1.616     www      10705:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10706: 	    $request->print(&scantron_process_students($request,$symb));
1.157     albertel 10707:  	} elsif ($command eq 'scantronupload' && 
1.257     albertel 10708:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
                   10709: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
1.616     www      10710:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10711:  	    $request->print(&scantron_upload_scantron_data($request,$symb)); 
1.157     albertel 10712:  	} elsif ($command eq 'scantronupload_save' &&
1.257     albertel 10713:  		 (&Apache::lonnet::allowed('usc',$env{'request.role.domain'})||
                   10714: 		  &Apache::lonnet::allowed('usc',$env{'request.course.id'}))) {
1.616     www      10715:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10716:  	    $request->print(&scantron_upload_scantron_data_save($request,$symb));
1.202     albertel 10717:  	} elsif ($command eq 'scantron_download' &&
1.257     albertel 10718: 		 &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
1.616     www      10719:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.608     www      10720:  	    $request->print(&scantron_download_scantron_data($request,$symb));
1.523     raeburn  10721:         } elsif ($command eq 'checksubmissions' && $perm{'vgr'}) {
1.616     www      10722:             &startpage($request,$symb,[{href=>'', text=>'Grade/Manage/Review Bubblesheets'}],1,1);
1.621     www      10723:             $request->print(&checkscantron_results($request,$symb));
                   10724:         } elsif ($command eq 'downloadfilesselect' && $perm{'vgr'}) {
                   10725:             &startpage($request,$symb,[{href=>'', text=>'Select which submissions to download'}]);
                   10726:             $request->print(&submit_options_download($request,$symb));
                   10727:          } elsif ($command eq 'downloadfileslink' && $perm{'vgr'}) {
                   10728:             &startpage($request,$symb,
                   10729:    [{href=>&href_symb_cmd($symb,'downloadfilesselect'), text=>'Select which submissions to download'},
                   10730:     {href=>'', text=>'Download submissions'}]);
                   10731:             &submit_download_link($request,$symb);
1.106     albertel 10732: 	} elsif ($command) {
1.620     www      10733:             &startpage($request,$symb,[{href=>'', text=>'Access denied'}]);
1.562     bisitz   10734: 	    $request->print('<p class="LC_error">'.&mt('Access Denied ([_1])',$command).'</p>');
1.26      albertel 10735: 	}
1.2       albertel 10736:     }
1.513     foxr     10737:     if ($ssi_error) {
                   10738: 	&ssi_print_error($request);
                   10739:     }
1.671     raeburn  10740:     if ($env{'form.inhibitmenu'}) {
                   10741:         $request->print(&Apache::loncommon::end_page());
                   10742:     } else {
                   10743:         &Apache::lonquickgrades::endGradeScreen($request);
                   10744:     }
1.434     albertel 10745:     &reset_caches();
1.646     raeburn  10746:     return OK;
1.44      ng       10747: }
                   10748: 
1.1       albertel 10749: 1;
                   10750: 
1.13      albertel 10751: __END__;
1.531     jms      10752: 
                   10753: 
                   10754: =head1 NAME
                   10755: 
                   10756: Apache::grades
                   10757: 
                   10758: =head1 SYNOPSIS
                   10759: 
                   10760: Handles the viewing of grades.
                   10761: 
                   10762: This is part of the LearningOnline Network with CAPA project
                   10763: described at http://www.lon-capa.org.
                   10764: 
                   10765: =head1 OVERVIEW
                   10766: 
                   10767: Do an ssi with retries:
1.715     bisitz   10768: While I'd love to factor out this with the version in lonprintout,
1.531     jms      10769: 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
                   10770: I'm not quite ready to invent (e.g. an ssi_with_retry object).
                   10771: 
                   10772: At least the logic that drives this has been pulled out into loncommon.
                   10773: 
                   10774: 
                   10775: 
                   10776: ssi_with_retries - Does the server side include of a resource.
                   10777:                      if the ssi call returns an error we'll retry it up to
                   10778:                      the number of times requested by the caller.
1.715     bisitz   10779:                      If we still have a problem, no text is appended to the
1.531     jms      10780:                      output and we set some global variables.
                   10781:                      to indicate to the caller an SSI error occurred.  
                   10782:                      All of this is supposed to deal with the issues described
1.715     bisitz   10783:                      in LON-CAPA BZ 5631 see:
1.531     jms      10784:                      http://bugs.lon-capa.org/show_bug.cgi?id=5631
                   10785:                      by informing the user that this happened.
                   10786: 
                   10787: Parameters:
                   10788:   resource   - The resource to include.  This is passed directly, without
                   10789:                interpretation to lonnet::ssi.
                   10790:   form       - The form hash parameters that guide the interpretation of the resource
                   10791:                
                   10792:   retries    - Number of retries allowed before giving up completely.
                   10793: Returns:
                   10794:   On success, returns the rendered resource identified by the resource parameter.
                   10795: Side Effects:
                   10796:   The following global variables can be set:
                   10797:    ssi_error                - If an unrecoverable error occurred this becomes true.
                   10798:                               It is up to the caller to initialize this to false
                   10799:                               if desired.
                   10800:    ssi_error_resource  - If an unrecoverable error occurred, this is the value
                   10801:                               of the resource that could not be rendered by the ssi
                   10802:                               call.
                   10803:    ssi_error_message   - The error string fetched from the ssi response
                   10804:                               in the event of an error.
                   10805: 
                   10806: 
                   10807: =head1 HANDLER SUBROUTINE
                   10808: 
                   10809: ssi_with_retries()
                   10810: 
                   10811: =head1 SUBROUTINES
                   10812: 
                   10813: =over
                   10814: 
1.671     raeburn  10815: =head1 Routines to display previous version of a Task for a specific student
                   10816: 
                   10817: Tasks are graded pass/fail. Students who have yet to pass a particular Task
                   10818: can receive another opportunity. Access to tasks is slot-based. If a slot
                   10819: requires a proctor to check-in the student, a new version of the Task will
                   10820: be created when the student is checked in to the new opportunity.
                   10821: 
                   10822: If a particular student has tried two or more versions of a particular task,
                   10823: the submission screen provides a user with vgr privileges (e.g., a Course
                   10824: Coordinator) the ability to display a previous version worked on by the
                   10825: student.  By default, the current version is displayed. If a previous version
                   10826: has been selected for display, submission data are only shown that pertain
                   10827: to that particular version, and the interface to submit grades is not shown.
                   10828: 
                   10829: =over 4
                   10830: 
                   10831: =item show_previous_task_version()
                   10832: 
                   10833: Displays a specified version of a student's Task, as the student sees it.
                   10834: 
                   10835: Inputs: 2
                   10836:         request - request object
                   10837:         symb    - unique symb for current instance of resource
                   10838: 
                   10839: Output: None.
                   10840: 
                   10841: Side Effects: calls &show_problem() to print version of Task, with
                   10842:               version contained in form item: $env{'form.previousversion'}
                   10843: 
                   10844: =item choose_task_version_form()
                   10845: 
                   10846: Displays a web form used to select which version of a student's view of a
                   10847: Task should be displayed.  Either launches a pop-up window, or replaces
                   10848: content in existing pop-up, or replaces page in main window.
                   10849: 
                   10850: Inputs: 4
                   10851:         symb    - unique symb for current instance of resource
                   10852:         uname   - username of student
                   10853:         udom    - domain of student
                   10854:         nomenu  - 1 if display is in a pop-up window, and hence no menu
                   10855:                   breadcrumbs etc., are displayed
                   10856: 
                   10857: Output: 4
                   10858:         current   - student's current version
                   10859:         displayed - student's version being displayed
                   10860:         result    - scalar containing HTML for web form used to switch to
                   10861:                     a different version (or a link to close window, if pop-up).
                   10862:         js        - javascript for processing selection in versions web form
                   10863: 
                   10864: Side Effects: None.
                   10865: 
                   10866: =item previous_display_javascript()
                   10867: 
                   10868: Inputs: 2
                   10869:         nomenu  - 1 if display is in a pop-up window, and hence no menu
                   10870:                   breadcrumbs etc., are displayed.
                   10871:         current - student's current version number.
                   10872: 
                   10873: Output: 1
                   10874:         js      - javascript for processing selection in versions web form.
                   10875: 
                   10876: Side Effects: None.
                   10877: 
                   10878: =back
                   10879: 
                   10880: =head1 Routines to process bubblesheet data.
                   10881: 
                   10882: =over 4
                   10883: 
1.531     jms      10884: =item scantron_get_correction() : 
                   10885: 
                   10886:    Builds the interface screen to interact with the operator to fix a
                   10887:    specific error condition in a specific scanline
                   10888: 
                   10889:  Arguments:
                   10890:     $r           - Apache request object
                   10891:     $i           - number of the current scanline
                   10892:     $scan_record - hash ref as returned from &scantron_parse_scanline()
                   10893:     $scan_config - hash ref as returned from &get_scantron_config()
                   10894:     $line        - full contents of the current scanline
                   10895:     $error       - error condition, valid values are
                   10896:                    'incorrectCODE', 'duplicateCODE',
                   10897:                    'doublebubble', 'missingbubble',
                   10898:                    'duplicateID', 'incorrectID'
                   10899:     $arg         - extra information needed
                   10900:        For errors:
                   10901:          - duplicateID   - paper number that this studentID was seen before on
                   10902:          - duplicateCODE - array ref of the paper numbers this CODE was
                   10903:                            seen on before
                   10904:          - incorrectCODE - current incorrect CODE 
                   10905:          - doublebubble  - array ref of the bubble lines that have double
                   10906:                            bubble errors
                   10907:          - missingbubble - array ref of the bubble lines that have missing
                   10908:                            bubble errors
                   10909: 
1.691     raeburn  10910:    $randomorder - True if exam folder has randomorder set
                   10911:    $randompick  - True if exam folder has randompick set
                   10912:    $respnumlookup - Reference to HASH mapping question numbers in bubble lines
                   10913:                      for current line to question number used for same question
                   10914:                      in "Master Seqence" (as seen by Course Coordinator).
                   10915:    $startline   - Reference to hash where key is question number (0 is first)
                   10916:                   and value is number of first bubble line for current student
                   10917:                   or code-based randompick and/or randomorder.
                   10918: 
                   10919: 
                   10920: 
1.531     jms      10921: =item  scantron_get_maxbubble() : 
                   10922: 
1.582     raeburn  10923:    Arguments:
                   10924:        $nav_error  - Reference to scalar which is a flag to indicate a
                   10925:                       failure to retrieve a navmap object.
                   10926:        if $nav_error is set to 1 by scantron_get_maxbubble(), the 
                   10927:        calling routine should trap the error condition and display the warning
                   10928:        found in &navmap_errormsg().
                   10929: 
1.649     raeburn  10930:        $scantron_config - Reference to bubblesheet format configuration hash.
                   10931: 
1.531     jms      10932:    Returns the maximum number of bubble lines that are expected to
                   10933:    occur. Does this by walking the selected sequence rendering the
                   10934:    resource and then checking &Apache::lonxml::get_problem_counter()
                   10935:    for what the current value of the problem counter is.
                   10936: 
                   10937:    Caches the results to $env{'form.scantron_maxbubble'},
                   10938:    $env{'form.scantron.bubble_lines.n'}, 
                   10939:    $env{'form.scantron.first_bubble_line.n'} and
                   10940:    $env{"form.scantron.sub_bubblelines.n"}
1.691     raeburn  10941:    which are the total number of bubble lines, the number of bubble
1.531     jms      10942:    lines for response n and number of the first bubble line for response n,
                   10943:    and a comma separated list of numbers of bubble lines for sub-questions
                   10944:    (for optionresponse, matchresponse, and rankresponse items), for response n.  
                   10945: 
                   10946: 
                   10947: =item  scantron_validate_missingbubbles() : 
                   10948: 
                   10949:    Validates all scanlines in the selected file to not have any
                   10950:     answers that don't have bubbles that have not been verified
                   10951:     to be bubble free.
                   10952: 
                   10953: =item  scantron_process_students() : 
                   10954: 
1.659     raeburn  10955:    Routine that does the actual grading of the bubblesheet information.
1.531     jms      10956: 
                   10957:    The parsed scanline hash is added to %env 
                   10958: 
                   10959:    Then foreach unskipped scanline it does an &Apache::lonnet::ssi()
                   10960:    foreach resource , with the form data of
                   10961: 
                   10962: 	'submitted'     =>'scantron' 
                   10963: 	'grade_target'  =>'grade',
                   10964: 	'grade_username'=> username of student
                   10965: 	'grade_domain'  => domain of student
                   10966: 	'grade_courseid'=> of course
                   10967: 	'grade_symb'    => symb of resource to grade
                   10968: 
                   10969:     This triggers a grading pass. The problem grading code takes care
                   10970:     of converting the bubbled letter information (now in %env) into a
                   10971:     valid submission.
                   10972: 
                   10973: =item  scantron_upload_scantron_data() :
                   10974: 
1.659     raeburn  10975:     Creates the screen for adding a new bubblesheet data file to a course.
1.531     jms      10976: 
                   10977: =item  scantron_upload_scantron_data_save() : 
                   10978: 
                   10979:    Adds a provided bubble information data file to the course if user
                   10980:    has the correct privileges to do so. 
                   10981: 
                   10982: =item  valid_file() :
                   10983: 
                   10984:    Validates that the requested bubble data file exists in the course.
                   10985: 
                   10986: =item  scantron_download_scantron_data() : 
                   10987: 
                   10988:    Shows a list of the three internal files (original, corrected,
1.659     raeburn  10989:    skipped) for a specific bubblesheet data file that exists in the
1.531     jms      10990:    course.
                   10991: 
                   10992: =item  scantron_validate_ID() : 
                   10993: 
                   10994:    Validates all scanlines in the selected file to not have any
1.556     weissno  10995:    invalid or underspecified student/employee IDs
1.531     jms      10996: 
1.582     raeburn  10997: =item navmap_errormsg() :
                   10998: 
                   10999:    Returns HTML mark-up inside a <div></div> with a link to re-initialize the course.
1.671     raeburn  11000:    Should be called whenever the request to instantiate a navmap object fails.
                   11001: 
                   11002: =back
1.582     raeburn  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.