--- loncom/homework/grades.pm	2007/06/15 22:10:15	1.407
+++ loncom/homework/grades.pm	2007/07/19 09:52:59	1.422
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.407 2007/06/15 22:10:15 albertel Exp $
+# $Id: grades.pm,v 1.422 2007/07/19 09:52:59 foxr Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -93,6 +93,7 @@ sub get_symb {
 	    return ();
 	}
     }
+    &Apache::lonenc::check_decrypt(\$symb);
     return ($symb);
 }
 
@@ -492,7 +493,7 @@ sub jscriptNform {
 	'    }'."\n".
 	'</script>'."\n";
     $jscript.= '<form action="/adm/grades" method="post" name="onestudent">'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n".
 	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
@@ -617,7 +618,7 @@ sub verifyreceipt {
 	    if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) {
 		$contents.='<tr bgcolor="#ffffe6"><td>&nbsp;'."\n".
 		    '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
-		    '\')"; TARGET=_self>'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
+		    '\');" target="_self">'.$$fullname{$_}.'</a>&nbsp;</td>'."\n".
 		    '<td>&nbsp;'.$uname.'&nbsp;</td>'.
 		    '<td>&nbsp;'.$udom.'&nbsp;</td>';
 		if ($receiptparts) {
@@ -742,7 +743,7 @@ LISTJAVASCRIPT
 	'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" /><br />'."\n".
 	'<input type="hidden" name="saveState"   value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="probTitle"   value="'.$env{'form.probTitle'}.'" />'."\n".
-	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveStatusOld" value="'.$saveStatus.'" />'."\n";
 
     if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) {
@@ -1751,7 +1752,7 @@ sub submission {
 			'<input type="hidden" name="refresh"    value="off" />'."\n".
 			'<input type="hidden" name="studentNo"  value="" />'."\n".
 			'<input type="hidden" name="gradeOpt"   value="" />'."\n".
-			'<input type="hidden" name="symb"       value="'.$symb.'" />'."\n".
+			'<input type="hidden" name="symb"       value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 			'<input type="hidden" name="showgrading" value="'.$env{'form.showgrading'}.'" />'."\n".
 			'<input type="hidden" name="vProb"      value="'.$env{'form.vProb'}.'" />'."\n".
 			'<input type="hidden" name="vAns"       value="'.$env{'form.vAns'}.'" />'."\n".
@@ -1792,10 +1793,10 @@ sub submission {
 #
 	    $request->print(<<KEYWORDS);
 &nbsp;<b>Keyword Options:</b>&nbsp;
-<a href="javascript:keywords(document.SCORE)"; TARGET=_self>List</a>&nbsp; &nbsp;
+<a href="javascript:keywords(document.SCORE);" target="_self">List</a>&nbsp; &nbsp;
 <a href="#" onMouseDown="javascript:getSel(); return false"
  CLASS="page">Paste Selection to List</a>&nbsp; &nbsp;
-<a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br />
+<a href="javascript:kwhighlight();" target="_self">Highlight Attribute</a><br /><br />
 KEYWORDS
 #
 # Load the other essays for similarity check
@@ -1921,7 +1922,7 @@ KEYWORDS
 			' <b>Collaborative submission by:</b> '.
 			'<a href="javascript:viewSubmitter(\''.
 			$env{"form.$uname:$udom:$partid:submitted_by"}.
-			'\')"; TARGET=_self>'.
+			'\');" target="_self">'.
 			$$fullname{$env{"form.$uname:$udom:$partid:submitted_by"}}.'</a><br />';
 		    $request->print($submitby);
 		    next;
@@ -1999,7 +2000,7 @@ KEYWORDS
     if ($env{'form.showgrading'} eq '' || (!&canmodify($usec))) {
 	my $toGrade.='<input type="button" value="Grade Student" '.
 	    'onClick="javascript:checksubmit(this.form,\'Grade Student\',\''
-	    .$counter.'\');" TARGET=_self> &nbsp;'."\n" if (&canmodify($usec));
+	    .$counter.'\');" target="_self" /> &nbsp;'."\n" if (&canmodify($usec));
 	$toGrade.='</td></tr></table></td></tr></table>'."\n";
 	if (($env{'form.command'} eq 'submission') || 
 	    ($env{'form.command'} eq 'processGroup' && $counter == $total)) {
@@ -2023,7 +2024,7 @@ KEYWORDS
 	$result='<input type="hidden" name="includemsg'.$counter.'" value="" />'."\n".
 	    '<input type="hidden" name="newmsg'.$counter.'" value="" />'."\n";
 	$result.='&nbsp;<a href="javascript:msgCenter(document.SCORE,'.$counter.
-	    ',\''.$msgfor.'\')"; TARGET=_self>'.
+	    ',\''.$msgfor.'\');" target="_self">'.
 	    &mt('Compose message to student').(scalar(@col_fullnames) >= 1 ? 's' : '').'</a><label> ('.
 	    &mt('incl. grades').' <input type="checkbox" name="withgrades'.$counter.'" /></label>)'.
 	    '<img src="'.$request->dir_config('lonIconsURL').
@@ -2075,7 +2076,7 @@ KEYWORDS
 	my $endform='<table border="0"><tr><td>'."\n";
 	$endform.='<input type="button" value="Save & Next" '.
 	    'onClick="javascript:checksubmit(this.form,\'Save & Next\','.
-	    $total.','.scalar(@partlist).');" TARGET=_self> &nbsp;'."\n";
+	    $total.','.scalar(@partlist).');" target="_self" /> &nbsp;'."\n";
 	my $ntstu ='<select name="NTSTU">'.
 	    '<option>1</option><option>2</option>'.
 	    '<option>3</option><option>5</option>'.
@@ -2084,9 +2085,9 @@ KEYWORDS
 	$ntstu =~ s/<option>$nsel</<option selected="selected">$nsel</;
 	$endform.=$ntstu.'student(s) &nbsp;&nbsp;';
 	$endform.='<input type="button" value="Previous" '.
-	    'onClick="javascript:checksubmit(this.form,\'Previous\');" TARGET=_self> &nbsp;'."\n".
+	    'onClick="javascript:checksubmit(this.form,\'Previous\');" target="_self" /> &nbsp;'."\n".
 	    '<input type="button" value="Next" '.
-	    'onClick="javascript:checksubmit(this.form,\'Next\');" TARGET=_self> &nbsp;';
+	    'onClick="javascript:checksubmit(this.form,\'Next\');" target="_self" /> &nbsp;';
 	$endform.='(Next and Previous (student) do not save the scores.)'."\n" ;
         $endform.="<input type='hidden' value='".&get_increment().
             "' name='increment' />";
@@ -2168,18 +2169,10 @@ sub processHandGrade {
 	    }
 	    my $includemsg = $env{'form.includemsg'.$ctr};
 	    my ($subject,$message,$msgstatus) = ('','','');
-            my $restitle = &Apache::lonnet::gettitle($symb);
-            my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
-                                                $symb,$udom,$uname);
-            my ($feedurl,$baseurl,$showsymb,$messagetail);
-            $feedurl = &Apache::lonnet::clutter($url);
-            if ($encrypturl =~ /^yes$/i) {
-                $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                $showsymb = &Apache::lonenc::encrypted($symb,1);
-            } else {
-                $baseurl = $feedurl;
-                $showsymb = $symb;
-            }
+	    my $restitle = &Apache::lonnet::gettitle($symb);
+            my ($feedurl,$showsymb) =
+		&get_feedurl_and_symb($symb,$uname,$udom);
+	    my $messagetail;
 	    if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) {
 		$subject = $env{'form.msgsub'} if ($includemsg =~ /msgsub/);
 		unless ($subject=~/\w/) { $subject=&mt('Grading Feedback'); }
@@ -2192,12 +2185,12 @@ sub processHandGrade {
 		if ($env{'form.withgrades'.$ctr}) {
 		    $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt;
 		    $messagetail = " for <a href=\"".
-		                   $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
+		                   $feedurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
 		}
 		$msgstatus = 
                     &Apache::lonmsg::user_normal_msg($uname,$udom,$subject,
 						     $message.$messagetail,
-                                                     undef,$baseurl,undef,
+                                                     undef,$feedurl,undef,
                                                      undef,undef,$showsymb,
                                                      $restitle);
 		$request->print('<br />'.&mt('Sending message to [_1]:[_2]',$uname,$udom).': '.
@@ -2214,26 +2207,16 @@ sub processHandGrade {
 			if ($errorflag eq 'not_allowed') {
 			    $request->print("<span class=\"LC_error\">".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."</span>");
 			    next;
-			} else {
-			    if ($message ne '') {
-                                $encrypturl=
-                                  &Apache::lonnet::EXT('resource.0.encrypturl',
-                                                       $symb,$udom,$collaborator);
-                                if ($encrypturl =~ /^yes$/i) {
-                                    $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                                    $showsymb = &Apache::lonenc::encrypted($symb,1);
-                                } else {
-                                    $baseurl = $feedurl;
-                                    $showsymb = $symb;
-                                }
-                                if ($env{'form.withgrades'.$ctr}) {
-                                    $messagetail = " for <a href=\"".
+			} elsif ($message ne '') {
+			    my ($baseurl,$showsymb) = 
+				&get_feedurl_and_symb($symb,$collaborator,
+						      $udom);
+			    if ($env{'form.withgrades'.$ctr}) {
+				$messagetail = " for <a href=\"".
                                     $baseurl."?symb=$showsymb\">$env{'form.probTitle'}</a>";
-
-                                }
-				$msgstatus = 
-                                    &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 			    }
+			    $msgstatus = 
+				&Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
 			}
 		    }
 		}
@@ -2587,28 +2570,31 @@ sub handback_files {
 		$message .= "<strong>".&Apache::lonnet::gettitle($symb)."</strong><br />";
 		$message .= ' The returned file(s) are named: '. $file_msg;
 		$message .= " and can be found in your portfolio space.";
-		my $url = (&Apache::lonnet::decode_symb($symb))[2];
-		my $feedurl = &Apache::lonnet::clutter($url);
-                my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
-                                                    $symb,$domain,$stuname);
-                my ($baseurl,$showsymb);
-                if ($encrypturl =~ /^yes$/i) {
-                    $baseurl = &Apache::lonenc::encrypted($feedurl,1);
-                    $showsymb = &Apache::lonenc::encrypted($symb,1);
-                } else {
-                    $baseurl = $feedurl;
-                    $showsymb = $symb;
-                }
+		my ($feedurl,$showsymb) = 
+		    &get_feedurl_and_symb($symb,$domain,$stuname);
                 my $restitle = &Apache::lonnet::gettitle($symb);
 		my $msgstatus = 
                    &Apache::lonmsg::user_normal_msg($stuname,$domain,$subject.
 			 ' (File Returned) ['.$restitle.']',$message,undef,
-                         $baseurl,undef,undef,undef,$showsymb,$restitle);
+                         $feedurl,undef,undef,undef,$showsymb,$restitle);
             }
         }
     return;
 }
 
+sub get_feedurl_and_symb {
+    my ($symb,$uname,$udom) = @_;
+    my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
+    $url = &Apache::lonnet::clutter($url);
+    my $encrypturl=&Apache::lonnet::EXT('resource.0.encrypturl',
+					$symb,$udom,$uname);
+    if ($encrypturl =~ /^yes$/i) {
+	&Apache::lonenc::encrypted(\$url,1);
+	&Apache::lonenc::encrypted(\$symb,1);
+    }
+    return ($url,$symb);
+}
+
 sub get_submitted_files {
     my ($udom,$uname,$partid,$respid,$record) = @_;
     my @files;
@@ -2969,7 +2955,7 @@ sub viewgrades {
 
     #beginning of class grading form
     $result.= '<form action="/adm/grades" method="post" name="classgrade">'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="command" value="editgrades" />'."\n".
 	'<input type="hidden" name="section" value="'.$env{'form.section'}.'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
@@ -3036,7 +3022,7 @@ sub viewgrades {
     $result.='</table>'.'</td></tr></table>'.'</td></tr></table>'."\n".
 	'<input type="hidden" name="totalparts" value="'.$ctsparts.'" />';
     $result.='<input type="button" value="Revert to Default" '.
-	'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>';
+	'onClick="javascript:resetEntry('.$ctsparts.');" target="_self" />';
 
     #table listing all the students in a section/class
     #header of table
@@ -3087,7 +3073,7 @@ sub viewgrades {
     $result.='</table></td></tr></table>';
     $result.='<input type="hidden" name="total" value="'.$ctr.'" />'."\n";
     $result.='<input type="button" value="Save" '.
-	'onClick="javascript:submit();" TARGET=_self /></form>'."\n";
+	'onClick="javascript:submit();" target="_self" /></form>'."\n";
     if (scalar(%$fullname) eq 0) {
 	my $colspan=3+scalar(@parts);
 	$result='<span class="LC_warning">There are no students in section "'.$env{'form.section'}.
@@ -3107,7 +3093,7 @@ sub viewstudentgrade {
 	'<input type="hidden" name="ctr'.($ctr-1).'" value="'.$student.'" />'.
 	"\n".$ctr.'&nbsp;</td><td>&nbsp;'.
 	'<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom.
-	'\')"; TARGET=_self>'.$fullname.'</a> '.
+	'\');" target="_self">'.$fullname.'</a> '.
 	'<span class="LC_internal_info">('.$uname.($env{'user.domain'} eq $udom ? '' : ':'.$udom).')</span></td>'."\n";
     $student=~s/:/_/; # colon doen't work in javascript for names
     foreach my $apart (@$parts) {
@@ -3471,6 +3457,7 @@ sub csvuploadmap_header {
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
     my $checked=(($env{'form.noFirstLine'})?' checked="checked"':'');
     my $ignore=&mt('Ignore First Line');
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $request->print(<<ENDPICK);
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <h3><span class="LC_info">Uploading Class Grades</span></h3>
@@ -3565,6 +3552,7 @@ sub upcsvScores_form {
     my $upload=&mt("Upload Scores");
     my $upfile_select=&Apache::loncommon::upfile_select_html();
     my $ignore=&mt('Ignore First Line');
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;
 <form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload">
 <input type="hidden" name="symb" value="$symb" />
@@ -3873,7 +3861,7 @@ LISTJAVASCRIPT
     $result.='<input type="hidden" name="section"     value="'.$getsec.'" />'."\n".
 	'<input type="hidden" name="Status"  value="'.$env{'form.Status'}.'" />'."\n".
 	'<input type="hidden" name="command" value="displayPage" />'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."<br />\n";
 
     $result.='&nbsp;<b>'.&mt('Use CODE:').' </b>'.
@@ -3997,7 +3985,7 @@ sub displayPage {
 	'<input type="hidden" name="student" value="'.$env{'form.student'}.'" />'."\n".
 	'<input type="hidden" name="page"    value="'.$pageTitle.'" />'."\n".
 	'<input type="hidden" name="title"   value="'.$env{'form.title'}.'" />'."\n".
-	'<input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="overRideScore" value="no" />'."\n".
 	'<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n";
 
@@ -4366,14 +4354,18 @@ sub updateGradeByPage {
 #
 #------ start of section for handling grading by page/sequence ---------
 
+# Create the hidden field entries used to hold context/default values.
+
 sub defaultFormData {
     my ($symb)=@_;
     return '
-      <input type="hidden" name="symb"    value="'.$symb.'" />'."\n".
+      <input type="hidden" name="symb"    value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
      '<input type="hidden" name="saveState" value="'.$env{'form.saveState'}.'" />'."\n".
      '<input type="hidden" name="probTitle" value="'.$env{'form.probTitle'}.'" />'."\n";
 }
 
+# Make a drop down of the sequences
+
 sub getSequenceDropDown {
     my ($request,$symb)=@_;
     my $result='<select name="selectpage">'."\n";
@@ -4391,6 +4383,8 @@ sub getSequenceDropDown {
     return $result;
 }
 
+# Returns a list of the scantron files that have been uploaded to date.
+
 sub scantron_filenames {
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cname=$env{'course.'.$env{'request.course.id'}.'.num'};
@@ -4406,6 +4400,9 @@ sub scantron_filenames {
     return @possiblenames;
 }
 
+# Returns the html required for a drop-down list of scantron
+# files that have been uploaded.
+
 sub scantron_uploads {
     my ($file2grade) = @_;
     my $result=	'<select name="scantron_selectfile">';
@@ -4417,6 +4414,9 @@ sub scantron_uploads {
     return $result;
 }
 
+# Returns the html for a drop down list of the scantron formats in the
+# scantronformat.tab file.
+
 sub scantron_scantab {
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
     my $result='<select name="scantron_format">'."\n";
@@ -4431,6 +4431,9 @@ sub scantron_scantab {
     return $result;
 }
 
+#  Returns the html for the options in the
+#  saved codes dropdown.
+
 sub scantron_CODElist {
     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
@@ -4445,6 +4448,8 @@ sub scantron_CODElist {
     return $namechoice;
 }
 
+# Returns the HTML for "Each CODE to be used once" radio.
+
 sub scantron_CODEunique {
     my $result='<span style="white-space: nowrap;">
                  <label><input type="radio" name="scantron_CODEunique"
@@ -4456,6 +4461,11 @@ sub scantron_CODEunique {
                 </span>';
     return $result;
 }
+#
+#    Display the first scantron file selection form.
+# Paramters:
+#    r           - The apache request object
+#    file2grade  - The name of the scantron file to be graded(?).
 
 sub scantron_selectphase {
     my ($r,$file2grade) = @_;
@@ -4471,6 +4481,9 @@ sub scantron_selectphase {
     my $result;
     #FIXME allow instructor to be able to download the scantron file
     # and to upload it,
+
+    # Chunk of form to prompt for a file to grade and how:
+
     $result.= <<SCANTRONFORM;
     <table width="100%" border="0">
     <tr>
@@ -4523,6 +4536,8 @@ SCANTRONFORM
     if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
         &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
 
+	# Chunk of form to prompt for a scantron file upload.
+
         $r->print(<<SCANTRONFORM);
     <tr>
       <td bgcolor="#777777">
@@ -4568,6 +4583,10 @@ UPLOAD
     </tr>
 SCANTRONFORM
     }
+
+    # Chunk of the form that prompts to view a scoring office file,
+    # corrected file, skipped records in a file.
+
     $r->print(<<SCANTRONFORM);
     <tr>
       <form action='/adm/grades' name='scantron_download'>
@@ -4602,6 +4621,14 @@ SCANTRONFORM
     return
 }
 
+# Parse and return the scantron configuration line selected as a
+# hash of configuration file fields.
+#
+# Parameters:
+#   which - the name of the configuration to parse from the file.
+#           If the named configuration is not in the file, an empty
+#           hash is returned.
+
 sub get_scantron_config {
     my ($which) = @_;
     my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab');
@@ -4634,6 +4661,15 @@ sub get_scantron_config {
     return %config;
 }
 
+#  creates a hash keyed by student id that conains
+#  the corresponding student username:domain.
+# Parameters:
+#   reference to the class list hash. This is a hash
+#   keyed by student name:domain  whose elements are references
+#   to arrays containng various chunks of information
+#   about the student. (See loncoursedata for more info).
+#
+# 
 sub username_to_idmap {
     my ($classlist)= @_;
     my %idmap;
@@ -4643,9 +4679,22 @@ sub username_to_idmap {
     }
     return %idmap;
 }
+#
+# Make a correction in a scantron line?
+# Parameters:
+#   scantron_config    - Format of the scantron file
+#   scan_data          - Hash of line by line info about the scan(?).
+#   line               - Scantron line to edit?
+#   whichline
+#   field  
+#   args               - Keyword/value hash of additional parameters.
+#
 
 sub scantron_fixup_scanline {
     my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_;
+    #
+    # ID field, args->{'newid'} is the new value of the ID field.
+    #
     if ($field eq 'ID') {
 	if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) {
 	    return ($line,1,'New value too large');
@@ -4660,6 +4709,11 @@ sub scantron_fixup_scanline {
 	    &scan_data($scan_data,"$whichline.user",
 		       $args->{'username'}.':'.$args->{'domain'});
 	}
+	# CODE Field, 
+	#   args->{CODE_ignore_dup} is true if duplicates should be ignored.
+	#   args->{CODE} is new code or 'use_unfound' if an unfound code should
+	#                be used as is?
+	#
     } elsif ($field eq 'CODE') {
 	if ($args->{'CODE_ignore_dup'}) {
 	    &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1');
@@ -4675,6 +4729,11 @@ sub scantron_fixup_scanline {
 	    substr($line,$$scantron_config{'CODEstart'}-1,
 		   $$scantron_config{'CODElength'})=$args->{'CODE'};
 	}
+	#
+	# Edit the answer field.
+	#     args->{'response'} - new answer or 'none' if blank.
+	#     args->{'question'} - the question (number?)?.
+	#
     } elsif ($field eq 'answer') {
 	my $length=$scantron_config->{'Qlength'};
 	my $off=$scantron_config->{'Qoff'};
@@ -4701,7 +4760,16 @@ sub scantron_fixup_scanline {
     }
     return $line;
 }
-
+# Edit or look up  an item in the scan_data hash.
+# Parameters:
+#   scan_data   - The hash.
+#   key         - shorthand of the key to edit (actual key is
+#                 scatronfilename_key.
+#   data        - New value of the hash entry.
+#   delete      - If defined, the entry is removed from the table.
+# Returns:
+#   The new value of the hash table field (undefined if deleted).
+#
 sub scan_data {
     my ($scan_data,$key,$value,$delete)=@_;
     my $filename=$env{'form.scantron_selectfile'};
@@ -4711,12 +4779,23 @@ sub scan_data {
     if ($delete) { delete($scan_data->{$filename.'_'.$key}); }
     return $scan_data->{$filename.'_'.$key};
 }
-
+#
+#  Decode a line on the uploaded scantron file:
+#  Arguments:
+#    line             - The text of the  scantron file line to process
+#    whichline        - Line number(?)
+#    scantron_config  - Hash describing the format of the scantron lines.
+#    scan_data        - Hash being built up of the entire scantron file.
+#    justHeader       - True if should not process question answers but only
+#                       the stuff to the left of the answers.
+# Returns:
+#   Hash of data from the line?
+#
 sub scantron_parse_scanline {
     my ($line,$whichline,$scantron_config,$scan_data,$justHeader)=@_;
     my %record;
-    my $questions=substr($line,$$scantron_config{'Qstart'}-1);
-    my $data=substr($line,0,$$scantron_config{'Qstart'}-1);
+    my $questions=substr($line,$$scantron_config{'Qstart'}-1);  # Answers
+    my $data=substr($line,0,$$scantron_config{'Qstart'}-1);     # earlier stuff
     if (!($$scantron_config{'CODElocation'} eq 0 ||
 	  $$scantron_config{'CODElocation'} eq 'none')) {
 	if ($$scantron_config{'CODElocation'} < 0 ||
@@ -5468,7 +5547,8 @@ ENDSCRIPT
 	$r->print("<p>Please indicate which bubble should be used for grading</p>");
 	foreach my $question (@{$arg}) {
 	    my $selected=$$scan_record{"scantron.$question.answer"};
-	    &scantron_bubble_selector($r,$scan_config,$question,split('',$selected));
+	    &scantron_bubble_selector($r,$scan_config,$question,
+				      split('',$selected));
 	}
     } elsif ($error eq 'missingbubble') {
 	$r->print("<p>There have been <b>no</b> bubbles scanned for some question(s)</p>\n");
@@ -5487,31 +5567,74 @@ ENDSCRIPT
     $r->print("\n</li></ul>");
 
 }
-
+#
+#  Ask the grader to select the actual bubble
+#  
+# Arguments:
+#    r           - Apache request.
+#    scan_config - Hash of the scantron format selected.
+#    quest       - Question being evaluated
+#    selected    - array of selected bubbles
+#    lines       - if present, number of bubble lines in questions.
 sub scantron_bubble_selector {
-    my ($r,$scan_config,$quest,@selected)=@_;
+    my ($r,$scan_config,$quest,@selected, $lines)=@_;
     my $max=$$scan_config{'Qlength'};
 
     my $scmode=$$scan_config{'Qon'};
     if ($scmode eq 'number' || $scmode eq 'letter') { $max=10; }	     
 
-    my @alphabet=('A'..'Z');
-    $r->print("<table border='1'><tr><td rowspan='2'>$quest</td>");
-    for (my $i=0;$i<$max+1;$i++) {
-	$r->print("\n".'<td align="center">');
-	if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) }
-	else { $r->print('&nbsp;'); }
-	$r->print('</td>');
-    }
-    $r->print('</tr><tr>');
-    for (my $i=0;$i<$max;$i++) {
-	$r->print("\n".
-		  '<td><label><input type="radio" name="scantron_correct_Q_'.
-		  $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
+
+    if (!defined($lines)) {
+	$lines = 1;
     }
-    $r->print('<td><label><input type="radio" name="scantron_correct_Q_'.
+    my $total_lines = $lines*2;
+    my @alphabet=('A'..'Z');
+    $r->print("<table border='1'><tr><td rowspan='".$total_lines."'>$quest</td>");
+
+    for (my $l = 0; $l < $lines; $l++) {
+	if ($l != 0) {
+	    $r->print('<tr>');
+	}
+
+	# FIXME:  This loop probably has to be considerably more clever for
+	#  multiline bubbles: User can multibubble by having bubbles in
+	#  several lines.  User can skip lines legitimately etc. etc.
+
+	for (my $i=0;$i<$max;$i++) {
+	    $r->print("\n".'<td align="center">');
+	    if ($selected[0] eq $alphabet[$i]) { 
+		$r->print('X'); 
+		shift(@selected) ;
+	    } else { 
+		$r->print('&nbsp;'); 
+	    }
+	    $r->print('</td>');
+	    
+	}
+
+	if ($l == 0) {
+	    my $lspan = $total_lines * 2;   #  2 table rows per bubble line.
+
+	    $r->print('<td rowspan='.$lspan.'><label><input type="radio" name="scantron_correct_Q_'.
 	      $quest.'" value="none" /> No bubble </label></td>');
-    $r->print('</tr></table>');
+	
+	}
+
+	$r->print('</tr><tr>');
+
+	# FIXME: This may have to be a bit more clever for
+	#        multiline questions (different values e.g..).
+
+	for (my $i=0;$i<$max;$i++) {
+	    $r->print("\n".
+		      '<td><label><input type="radio" name="scantron_correct_Q_'.
+		      $quest.'" value="'.$i.'" />'.$alphabet[$i]."</label></td>");
+	}
+	$r->print('</tr>');
+
+	    
+    }
+    $r->print('</table>');
 }
 
 sub num_matches {
@@ -5938,7 +6061,7 @@ DOWNLOAD
 sub show_grading_menu_form {
     my ($symb)=@_;
     my $result.='<br /><form action="/adm/grades" method="post">'."\n".
-	'<input type="hidden" name="symb" value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 	'<input type="hidden" name="command" value="gradingmenu" />'."\n".
 	'<input type="submit" name="submit" value="Grading Menu" />'."\n".
@@ -6013,7 +6136,7 @@ GRADINGMENUJS
     my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'});
 
     $result.='<form action="/adm/grades" method="post" name="gradingMenu">'."\n".
-	'<input type="hidden" name="symb"        value="'.$symb.'" />'."\n".
+	'<input type="hidden" name="symb"        value="'.&Apache::lonenc::check_encrypt($symb).'" />'."\n".
 	'<input type="hidden" name="handgrade"   value="'.$hdgrade.'" />'."\n".
 	'<input type="hidden" name="probTitle"   value="'.$probTitle.'" />'."\n".
 	'<input type="hidden" name="command"     value="" />'."\n".
@@ -6129,7 +6252,7 @@ sub init_perm {
 }
 
 sub gather_clicker_ids {
-    my %clicker_ids=();
+    my %clicker_ids;
 
     my $classlist = &Apache::loncoursedata::get_classlist();
 
@@ -6142,13 +6265,14 @@ sub gather_clicker_ids {
         my $username = $classlist->{$student}->[$username_idx];
         my $domain   = $classlist->{$student}->[$domain_idx];
         my $clickers =
-	   (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
+	    (&Apache::lonnet::userenvironment($domain,$username,'clickers'))[1];
         foreach my $id (split(/\,/,$clickers)) {
-            $id=~s/^0+//;
+            $id=~s/^[\#0]+//;
+            $id=~s/[\-\:]//g;
             if (exists($clicker_ids{$id})) {
-               $clicker_ids{$id}.=','.$username.':'.$domain;
+		$clicker_ids{$id}.=','.$username.':'.$domain;
             } else {
-               $clicker_ids{$id}=$username.':'.$domain;
+		$clicker_ids{$id}=$username.':'.$domain;
             }
         }
     }
@@ -6156,28 +6280,37 @@ sub gather_clicker_ids {
 }
 
 sub gather_adv_clicker_ids {
-    my %clicker_ids=();
+    my %clicker_ids;
     my $cnum=$env{'course.'.$env{'request.course.id'}.'.num'};
     my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'};
     my %coursepersonnel=&Apache::lonnet::get_course_adv_roles($cdom.'/'.$cnum);
-    foreach my $element (sort keys %coursepersonnel) {
+    foreach my $element (sort(keys(%coursepersonnel))) {
         foreach my $person (split(/\,/,$coursepersonnel{$element})) {
             my ($puname,$pudom)=split(/\:/,$person);
             my $clickers =
-               (&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
+		(&Apache::lonnet::userenvironment($pudom,$puname,'clickers'))[1];
             foreach my $id (split(/\,/,$clickers)) {
-               $id=~s/^0+//;
-               if (exists($clicker_ids{$id})) {
-                  $clicker_ids{$id}.=','.$puname.':'.$pudom;
-               } else {
-                  $clicker_ids{$id}=$puname.':'.$pudom;
-               }
+		$id=~s/^[\#0]+//;
+                $id=~s/[\-\:]//g;
+		if (exists($clicker_ids{$id})) {
+		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
+		} else {
+		    $clicker_ids{$id}=$puname.':'.$pudom;
+		}
             }
         }
     }
     return %clicker_ids;
 }
 
+sub clicker_grading_parameters {
+    return ('gradingmechanism' => 'scalar',
+            'upfiletype' => 'scalar',
+            'specificid' => 'scalar',
+            'pcorrect' => 'scalar',
+            'pincorrect' => 'scalar');
+}
+
 sub process_clicker {
     my ($r)=@_;
     my ($symb)=&get_symb($r);
@@ -6191,16 +6324,33 @@ sub process_clicker {
     $result.='&nbsp;<b>'.&mt('Specify a file containing the clicker information for this resource').
         '.</b></td></tr>'."\n";
     $result.='<tr bgcolor=#ffffe6><td>'."\n";
+# Attempt to restore parameters from last session, set defaults if not present
+    my %Saveable_Parameters=&clicker_grading_parameters();
+    &Apache::loncommon::restore_course_settings('grades_clicker',
+                                                 \%Saveable_Parameters);
+    if (!$env{'form.pcorrect'}) { $env{'form.pcorrect'}=100; }
+    if (!$env{'form.pincorrect'}) { $env{'form.pincorrect'}=100; }
+    if (!$env{'form.gradingmechanism'}) { $env{'form.gradingmechanism'}='attendance'; }
+    if (!$env{'form.upfiletype'}) { $env{'form.upfiletype'}='iclicker'; }
+
+    my %checked;
+    foreach my $gradingmechanism ('attendance','personnel','specific') {
+       if ($env{'form.gradingmechanism'} eq $gradingmechanism) {
+          $checked{$gradingmechanism}="checked='checked'";
+       }
+    }
+
     my $upload=&mt("Upload File");
     my $type=&mt("Type");
     my $attendance=&mt("Award points just for participation");
     my $personnel=&mt("Correctness determined from response by course personnel");
-    my $specific=&mt("Correctness determined from response with clicker ID"); 
+    my $specific=&mt("Correctness determined from response with clicker ID(s)"); 
     my $pcorrect=&mt("Percentage points for correct solution");
     my $pincorrect=&mt("Percentage points for incorrect solution");
-    my $selectform=&Apache::loncommon::select_form('iclicker','upfiletype',
-                                  ('iclicker' => 'i>clicker'));
-
+    my $selectform=&Apache::loncommon::select_form($env{'form.upfiletype'},'upfiletype',
+						   ('iclicker' => 'i>clicker',
+                                                    'interwrite' => 'interwrite PRS'));
+    $symb = &Apache::lonenc::check_encrypt($symb);
     $result.=<<ENDUPFORM;
 <script type="text/javascript">
 function sanitycheck() {
@@ -6248,13 +6398,13 @@ function sanitycheck() {
 <input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
 <input type="file" name="upfile" size="50" />
 <br /><label>$type: $selectform</label>
-<br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" checked="checked" onClick="sanitycheck()" /></label>
-<br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" onClick="sanitycheck()" /></label>
-<br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" onClick="sanitycheck()" /></label>
-<input type="text" name="specificid" size="15" />
-<input type="hidden" name="waschecked" value="attendance" />
-<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="100" onChange="sanitycheck()" /></label>
-<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="100" onChange="sanitycheck()" /></label>
+<br /><label>$attendance: <input type="radio" name="gradingmechanism" value="attendance" $checked{'attendance'} onClick="sanitycheck()" /></label>
+<br /><label>$personnel: <input type="radio" name="gradingmechanism" value="personnel" $checked{'personnel'} onClick="sanitycheck()" /></label>
+<br /><label>$specific: <input type="radio" name="gradingmechanism" value="specific" $checked{'specific'} onClick="sanitycheck()" /></label>
+<input type="text" name="specificid" value="$env{'form.specificid'}" size="20" />
+<input type="hidden" name="waschecked" value="$env{'form.gradingmechanism'}" />
+<br /><label>$pcorrect: <input type="text" name="pcorrect" size="4" value="$env{'form.pcorrect'}" onChange="sanitycheck()" /></label>
+<br /><label>$pincorrect: <input type="text" name="pincorrect" size="4" value="$env{'form.pincorrect'}" onChange="sanitycheck()" /></label>
 <br /><input type="button" onClick="javascript:checkUpload(this.form);" value="$upload" />
 </form>
 ENDUPFORM
@@ -6268,43 +6418,52 @@ sub process_clicker_file {
     my ($r)=@_;
     my ($symb)=&get_symb($r);
     if (!$symb) {return '';}
+
+    my %Saveable_Parameters=&clicker_grading_parameters();
+    &Apache::loncommon::store_course_settings('grades_clicker',
+                                              \%Saveable_Parameters);
+
     my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
     if (($env{'form.gradingmechanism'} eq 'specific') && ($env{'form.specificid'}!~/\w/)) {
-       $result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
-       return $result.&show_grading_menu_form($symb);
+	$result.='<span class="LC_error">'.&mt('You need to specify a clicker ID for the correct answer').'</span>';
+	return $result.&show_grading_menu_form($symb);
     }
     my %clicker_ids=&gather_clicker_ids();
-    my %correct_ids=();
+    my %correct_ids;
     if ($env{'form.gradingmechanism'} eq 'personnel') {
-       %correct_ids=&gather_adv_clicker_ids();
+	%correct_ids=&gather_adv_clicker_ids();
     }
     if ($env{'form.gradingmechanism'} eq 'specific') {
-       my $correct_id=$env{'form.specificid'};
-       $correct_id=~tr/a-z/A-Z/;
-       $correct_id=~s/\s//gs;
-       $correct_id=~s/^0+//;
-       $correct_ids{$correct_id}='specified';
+	foreach my $correct_id (split(/[\s\,]/,$env{'form.specificid'})) {;
+	   $correct_id=~tr/a-z/A-Z/;
+	   $correct_id=~s/\s//gs;
+	   $correct_id=~s/^[\#0]+//;
+           $correct_id=~s/[\-\:]//g;
+           if ($correct_id) {
+	      $correct_ids{$correct_id}='specified';
+           }
+        }
     }
     if ($env{'form.gradingmechanism'} eq 'attendance') {
-       $result.=&mt('Score based on attendance only');
+	$result.=&mt('Score based on attendance only');
     } else {
-       my $number=0;
-       $result.='<h3>'.&mt('Correctness determined by the following IDs').'</h3>';
-       foreach my $id (sort(keys(%correct_ids))) {
-          $result.='<tt>'.$id.'</tt> - ';
-          if ($correct_ids{$id} eq 'specified') {
-             $result.=&mt('specified');
-          } else {
-             my ($uname,$udom)=split(/\:/,$correct_ids{$id});
-             $result.=&Apache::loncommon::plainname($uname,$udom);
-          }
-          $result.='<br />';
-          $number++;
-       }
-       if ($number==0) {
-          $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
-          return $result.&show_grading_menu_form($symb);
-       }
+	my $number=0;
+	$result.='<p><b>'.&mt('Correctness determined by the following IDs').'</b>';
+	foreach my $id (sort(keys(%correct_ids))) {
+	    $result.='<br /><tt>'.$id.'</tt> - ';
+	    if ($correct_ids{$id} eq 'specified') {
+		$result.=&mt('specified');
+	    } else {
+		my ($uname,$udom)=split(/\:/,$correct_ids{$id});
+		$result.=&Apache::loncommon::plainname($uname,$udom);
+	    }
+	    $number++;
+	}
+        $result.="</p>\n";
+	if ($number==0) {
+	    $result.='<span class="LC_error">'.&mt('No IDs found to determine correct answer').'</span>';
+	    return $result.&show_grading_menu_form($symb);
+	}
     }
     if (length($env{'form.upfile'}) < 2) {
         $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
@@ -6313,17 +6472,84 @@ sub process_clicker_file {
 		     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
         return $result.&show_grading_menu_form($symb);
     }
-    my %responses=();
-    my @questiontitles=();
+
+# Were able to get all the info needed, now analyze the file
+
+    $result.=&Apache::loncommon::studentbrowser_javascript();
+    $symb = &Apache::lonenc::check_encrypt($symb);
+    my $heading=&mt('Scanning clicker file');
+    $result.=(<<ENDHEADER);
+<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
+<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
+<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
+<form method="post" action="/adm/grades" name="clickeranalysis">
+<input type="hidden" name="symb" value="$symb" />
+<input type="hidden" name="command" value="assignclickergrades" />
+<input type="hidden" name="probTitle" value="$env{'form.probTitle'}" />
+<input type="hidden" name="saveState"  value="$env{'form.saveState'}" />
+<input type="hidden" name="gradingmechanism" value="$env{'form.gradingmechanism'}" />
+<input type="hidden" name="pcorrect" value="$env{'form.pcorrect'}" />
+<input type="hidden" name="pincorrect" value="$env{'form.pincorrect'}" />
+ENDHEADER
+    my %responses;
+    my @questiontitles;
     my $errormsg='';
     my $number=0;
     if ($env{'form.upfiletype'} eq 'iclicker') {
-       ($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
+	($errormsg,$number)=&iclicker_eval(\@questiontitles,\%responses);
     }
-    $result.='<br />'.&mt('Found [_1] questions',$number).'<br />';
-    foreach my $id (keys %responses) {
-       $result.='<br />'.$id.' - '.$responses{$id};
+    if ($env{'form.upfiletype'} eq 'interwrite') {
+        ($errormsg,$number)=&interwrite_eval(\@questiontitles,\%responses);
+    }
+    $result.='<br />'.&mt('Found [_1] question(s)',$number).'<br />'.
+             '<input type="hidden" name="number" value="'.$number.'" />'.
+             &mt('Awarding [_1] percent for correct and [_2] percent for incorrect responses',
+                 $env{'form.pcorrect'},$env{'form.pincorrect'}).
+             '<br />';
+# Remember Question Titles
+# FIXME: Possibly need delimiter other than ":"
+    for (my $i=0;$i<$number;$i++) {
+        $result.='<input type="hidden" name="question:'.$i.'" value="'.
+                 &HTML::Entities::encode($questiontitles[$i],'"&<>').'" />';
+    }
+    my $correct_count=0;
+    my $student_count=0;
+    my $unknown_count=0;
+# Match answers with usernames
+# FIXME: Possibly need delimiter other than ":"
+    foreach my $id (keys(%responses)) {
+       if ($correct_ids{$id}) {
+          $result.="\n".'<input type="hidden" name="correct:'.$correct_count.':'.$correct_ids{$id}.'" value="'.$responses{$id}.'" />';
+          $correct_count++;
+       } elsif ($clicker_ids{$id}) {
+          $result.="\n".'<input type="hidden" name="student:'.$clicker_ids{$id}.'" value="'.$responses{$id}.'" />';
+          $student_count++;
+       } else {
+          $result.="\n<hr />".&mt('Unregistered Clicker')." <tt>".$id."</tt><br />";
+          $result.="\n".'<input type="hidden" name="unknown:'.$id.'" value="'.$responses{$id}.'" />'.
+                   "\n".&mt("Username").": <input type='text' name='uname".$id."' />&nbsp;".
+                   "\n".&mt("Domain").": ".
+                   &Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).'&nbsp;'.
+                   &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id);
+          $unknown_count++;
+       }
+    }
+    $result.='<hr />'.
+             &mt('Found [_1] registered and [_2] unregistered clickers.',$student_count,$unknown_count);
+    if ($env{'form.gradingmechanism'} ne 'attendance') {
+       if ($correct_count==0) {
+          $errormsg.="Found no correct answers answers for grading!";
+       } elsif ($correct_count>1) {
+          $result.='<br /><span class="LC_warning">'.&mt("Found [_1] entries for grading!",$correct_count).'</span>';
+       }
     }
+    if ($errormsg) {
+       $result.='<br /><span class="LC_error">'.&mt($errormsg).'</span>';
+    } else {
+       $result.='<br /><input type="submit" name="finalize" value="'.&mt('Finalize Grading').'" />';
+    }
+    $result.='</form></td></tr></table>'."\n".
+             '</td></tr></table><br /><br />'."\n";
     return $result.&show_grading_menu_form($symb);
 }
 
@@ -6332,32 +6558,159 @@ sub iclicker_eval {
     my $number=0;
     my $errormsg='';
     foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
-       chomp($line);
-       foreach my $quoted ($line=~/\,\s*\"([^\"]*)\"\s*\,/g) {
-          my $replace=$quoted;
-          $replace=~s/\,//g;
-          &Apache::lonnet::logthis($quoted.' - '.$replace.'<br />');
-          $line=~s/\,\s*\"\Q$quoted\E\"\s*\,/,$replace,/gs;
+        my %components=&Apache::loncommon::record_sep($line);
+        my @entries=map {$components{$_}} (sort(keys(%components)));
+	if ($entries[0] eq 'Question') {
+	    for (my $i=3;$i<$#entries;$i+=6) {
+		$$questiontitles[$number]=$entries[$i];
+		$number++;
+	    }
+	}
+	if ($entries[0]=~/^\#/) {
+	    my $id=$entries[0];
+	    my @idresponses;
+	    $id=~s/^[\#0]+//;
+	    for (my $i=0;$i<$number;$i++) {
+		my $idx=3+$i*6;
+		push(@idresponses,$entries[$idx]);
+	    }
+	    $$responses{$id}=join(',',@idresponses);
+	}
+    }
+    return ($errormsg,$number);
+}
+
+sub interwrite_eval {
+    my ($questiontitles,$responses)=@_;
+    my $number=0;
+    my $errormsg='';
+    my $skipline=1;
+    my $questionnumber=0;
+    my %idresponses=();
+    foreach my $line (split(/[\n\r]/,$env{'form.upfile'})) {
+        my %components=&Apache::loncommon::record_sep($line);
+        my @entries=map {$components{$_}} (sort(keys(%components)));
+        if ($entries[1] eq 'Time') { $skipline=0; next; }
+        if ($entries[1] eq 'Response') { $skipline=1; }
+        next if $skipline;
+        if ($entries[0]!=$questionnumber) {
+           $questionnumber=$entries[0];
+           $$questiontitles[$number]=&mt('Question [_1]',$questionnumber);
+           $number++;
+        }
+        my $id=$entries[4];
+        $id=~s/^[\#0]+//;
+        $id=~s/^v\d*\://i;
+        $id=~s/[\-\:]//g;
+        $idresponses{$id}[$number]=$entries[6];
+    }
+    foreach my $id (keys %idresponses) {
+       $$responses{$id}=join(',',@{$idresponses{$id}});
+       $$responses{$id}=~s/^\s*\,//;
+    }
+    return ($errormsg,$number);
+}
+
+sub assign_clicker_grades {
+    my ($r)=@_;
+    my ($symb)=&get_symb($r);
+    if (!$symb) {return '';}
+# See which part we are saving to
+    my ($partlist,$handgrade,$responseType) = &response_type($symb);
+# FIXME: This should probably look for the first handgradeable part
+    my $part=$$partlist[0];
+# Start screen output
+    my ($result) = &showResourceInfo($symb,$env{'form.probTitle'});
+
+    my $heading=&mt('Assigning grades based on clicker file');
+    $result.=(<<ENDHEADER);
+<br /><table width="100%" border="0"><tr><td bgcolor="#777777">
+<table width="100%" border="0"><tr bgcolor="#e6ffff"><td>
+<b>$heading</b></td></tr><tr bgcolor=#ffffe6><td>
+ENDHEADER
+# Get correct result
+# FIXME: Possibly need delimiter other than ":"
+    my @correct=();
+    my $gradingmechanism=$env{'form.gradingmechanism'};
+    my $number=$env{'form.number'};
+    if ($gradingmechanism ne 'attendance') {
+       foreach my $key (keys(%env)) {
+          if ($key=~/^form\.correct\:/) {
+             my @input=split(/\,/,$env{$key});
+             for (my $i=0;$i<=$#input;$i++) {
+                 if (($correct[$i]) && ($input[$i]) &&
+                     ($correct[$i] ne $input[$i])) {
+                    $result.='<br /><span class="LC_warning">'.
+                             &mt('More than one correct result given for question "[_1]": [_2] versus [_3].',
+                                 $env{'form.question:'.$i},$correct[$i],$input[$i]).'</span>';
+                 } elsif ($input[$i]) {
+                    $correct[$i]=$input[$i];
+                 }
+             }
+          }
+       }
+       for (my $i=0;$i<$number;$i++) {
+          if (!$correct[$i]) {
+             $result.='<br /><span class="LC_error">'.
+                      &mt('No correct result given for question "[_1]"!',
+                          $env{'form.question:'.$i}).'</span>';
+          }
+       }
+       $result.='<br />'.&mt("Correct answer: [_1]",join(', ',map { ($_?$_:'-') } @correct));
+    }
+# Start grading
+    my $pcorrect=$env{'form.pcorrect'};
+    my $pincorrect=$env{'form.pincorrect'};
+    my $storecount=0;
+    foreach my $key (keys(%env)) {
+       my $user='';
+       if ($key=~/^form\.student\:(.*)$/) {
+          $user=$1;
        }
-       my @entries=split(/\,/,$line);
-       if ($entries[0] eq 'Question') {
-          for (my $i=3;$i<$#entries;$i+=6) {
-              $$questiontitles[$number]=$entries[$i];
-              $number++;
+       if ($key=~/^form\.unknown\:(.*)$/) {
+          my $id=$1;
+          if (($env{'form.uname'.$id}) && ($env{'form.udom'.$id})) {
+             $user=$env{'form.uname'.$id}.':'.$env{'form.udom'.$id};
           }
        }
-       if ($entries[0]=~/^\#/) {
-          my $id=$entries[0];
-          my @idresponses=();
-          $id=~s/^[\#0]+//;
+       if ($user) { 
+          my @answer=split(/\,/,$env{$key});
+          my $sum=0;
           for (my $i=0;$i<$number;$i++) {
-             my $idx=3+$i*6;
-             push(@idresponses,$entries[$idx]);
+             if ($answer[$i]) {
+                if ($gradingmechanism eq 'attendance') {
+                   $sum+=$pcorrect;
+                } else {
+                   if ($answer[$i] eq $correct[$i]) {
+                      $sum+=$pcorrect;
+                   } else {
+                      $sum+=$pincorrect;
+                   }
+                }
+             }
+          }
+          my $ave=$sum/(100*$number);
+# Store
+          my ($username,$domain)=split(/\:/,$user);
+          my %grades=();
+          $grades{"resource.$part.solved"}='correct_by_override';
+          $grades{"resource.$part.awarded"}=$ave;
+          $grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
+          my $returncode=&Apache::lonnet::cstore(\%grades,$symb,
+                                                 $env{'request.course.id'},
+                                                 $domain,$username);
+          if ($returncode ne 'ok') {
+             $result.="<br /><span class=\"LC_error\">Failed to save student $username:$domain. Message when trying to save was ($returncode)</span>";
+          } else {
+             $storecount++;
           }
-          $$responses{$id}=join(',',@idresponses);
        }
     }
-    return ($errormsg,$number);
+# We are done
+    $result.='<br />'.&mt('Successfully stored grades for [_1] student(s).',$storecount).
+             '</td></tr></table>'."\n".
+             '</td></tr></table><br /><br />'."\n";
+    return $result.&show_grading_menu_form($symb);
 }
 
 sub handler {
@@ -6431,6 +6784,8 @@ sub handler {
             $request->print(&process_clicker($request));
         } elsif ($command eq 'processclickerfile' && $perm{'mgr'}) {
             $request->print(&process_clicker_file($request));
+        } elsif ($command eq 'assignclickergrades' && $perm{'mgr'}) {
+            $request->print(&assign_clicker_grades($request));
 	} elsif ($command eq 'csvform' && $perm{'mgr'}) {
 	    $request->print(&upcsvScores_form($request));
 	} elsif ($command eq 'csvupload' && $perm{'mgr'}) {