--- loncom/homework/grades.pm	2007/06/15 21:21:16	1.405
+++ loncom/homework/grades.pm	2007/06/16 23:00:09	1.415
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.405 2007/06/15 21:21:16 www Exp $
+# $Id: grades.pm,v 1.415 2007/06/16 23:00:09 www Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -41,7 +41,6 @@ use Apache::Constants qw(:common);
 use Apache::lonlocal;
 use Apache::lonenc;
 use String::Similarity;
-use lib '/home/httpd/lib/perl';
 use LONCAPA;
 
 use POSIX qw(floor);
@@ -6130,53 +6129,61 @@ sub init_perm {
 }
 
 sub gather_clicker_ids {
-    my %clickerids=();
+    my %clicker_ids;
 
     my $classlist = &Apache::loncoursedata::get_classlist();
 
     # Set up a couple variables.
-    my $usernameidx = &Apache::loncoursedata::CL_SNAME();
-    my $domainidx   = &Apache::loncoursedata::CL_SDOM();
+    my $username_idx = &Apache::loncoursedata::CL_SNAME();
+    my $domain_idx   = &Apache::loncoursedata::CL_SDOM();
 
-    foreach my $student (keys %$classlist) {
+    foreach my $student (keys(%$classlist)) {
 
-        my $username = $classlist->{$student}->[$usernameidx];
-        my $domain   = $classlist->{$student}->[$domainidx];
+        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+//;
-            if (exists($clickerids{$id})) {
-               $clickerids{$id}.=','.$username.':'.$domain;
+            $id=~s/^[\#0]+//;
+            if (exists($clicker_ids{$id})) {
+		$clicker_ids{$id}.=','.$username.':'.$domain;
             } else {
-               $clickerids{$id}=$username.':'.$domain;
+		$clicker_ids{$id}=$username.':'.$domain;
             }
         }
     }
-    return %clickerids;
+    return %clicker_ids;
 }
 
 sub gather_adv_clicker_ids {
-    my %clickerids=();
+    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($clickerids{$id})) {
-                  $clickerids{$id}.=','.$puname.':'.$pudom;
-               } else {
-                  $clickerids{$id}=$puname.':'.$pudom;
-               }
+		$id=~s/^[\#0]+//;
+		if (exists($clicker_ids{$id})) {
+		    $clicker_ids{$id}.=','.$puname.':'.$pudom;
+		} else {
+		    $clicker_ids{$id}=$puname.':'.$pudom;
+		}
             }
         }
     }
-    return %clickerids;
+    return %clicker_ids;
+}
+
+sub clicker_grading_parameters {
+    return ('gradingmechanism' => 'scalar',
+            'upfiletype' => 'scalar',
+            'specificid' => 'scalar',
+            'pcorrect' => 'scalar',
+            'pincorrect' => 'scalar');
 }
 
 sub process_clicker {
@@ -6192,15 +6199,31 @@ 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'));
 
     $result.=<<ENDUPFORM;
 <script type="text/javascript">
@@ -6249,13 +6272,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
@@ -6269,78 +6292,233 @@ 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 %clickerids=&gather_clicker_ids();
-    my %correctids=();
+    my %clicker_ids=&gather_clicker_ids();
+    my %correct_ids;
     if ($env{'form.gradingmechanism'} eq 'personnel') {
-       %correctids=&gather_adv_clicker_ids();
+	%correct_ids=&gather_adv_clicker_ids();
     }
     if ($env{'form.gradingmechanism'} eq 'specific') {
-       my $correctid=$env{'form.specificid'};
-       $correctid=~tr/a-z/A-Z/;
-       $correctid=~s/\s//gs;
-       $correctid=~s/^0+//;
-       $correctids{$correctid}='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]+//;
+           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(%correctids))) {
-          $result.='<tt>'.$id.'</tt> - ';
-          if ($correctids{$id} eq 'specified') {
-             $result.=&mt('specified');
-          } else {
-             my ($uname,$udom)=split(/\:/,$correctids{$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.="<span class=\"LC_error\">Error:</span> The file you attempted to upload, <tt>".&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"')."</tt>, contained no information. Please check that you entered the correct filename.";
+        $result.=&mt('[_1] Error: [_2] The file you attempted to upload, [_3] contained no information. Please check that you entered the correct filename.',
+		     '<span class="LC_error">',
+		     '</span>',
+		     '<span class="LC_filename">'.&HTML::Entities::encode($env{'form.upfile.filename'},'<>&"').'</span>');
         return $result.&show_grading_menu_form($symb);
     }
-    my %contents=();
+
+# Were able to get all the info needed, now analyze the file
+
+    $result.=&Apache::loncommon::studentbrowser_javascript();
+    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,%contents)=&iclicker_eval();
-       $result.=$errormsg;
+	($errormsg,$number)=&iclicker_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);
 }
 
 sub iclicker_eval {
+    my ($questiontitles,$responses)=@_;
     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 assign_clicker_grades {
+    my ($r)=@_;
+    my ($symb)=&get_symb($r);
+    if (!$symb) {return '';}
+    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];
+                 }
+             }
+          }
        }
-       my @entries=split(/\,/,$line);
-       if ($entries[0] eq 'Question') {
-          for (my $i=3;$i<$#entries;$i+=6) {
-              $number++;
-              $errormsg.='<br />'.$entries[$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));
     }
-    return ($errormsg,$number);
+# Start grading
+    my $pcorrect=$env{'form.pcorrect'};
+    my $pincorrect=$env{'form.pincorrect'};
+    foreach my $key (keys(%env)) {
+       if ($key=~/^form\.student\:(.*)$/) {
+          my $user=$1;
+          my @answer=split(/\,/,$env{$key});
+          my $sum=0;
+          for (my $i=0;$i<$number;$i++) {
+             if ($answer[$i]) {
+                if ($gradingmechanism eq 'attendance') {
+                   $sum+=$pcorrect;
+                } else {
+                   if ($answer[$i] eq $correct[$i]) {
+                      $sum+=$pcorrect;
+                   } else {
+                      $sum+=$pincorrect;
+                   }
+                }
+             }
+          }
+          my $ave=$sum/$number;
+          $result.='<br />'.$user.' - '.$env{$key}.': '.$ave;
+       }
+    }
+# We are done
+    $result.='</td></tr></table>'."\n".
+             '</td></tr></table><br /><br />'."\n";
+    return $result.&show_grading_menu_form($symb);
 }
 
 sub handler {
@@ -6414,6 +6592,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'}) {