--- loncom/homework/grades.pm	2006/05/29 18:51:35	1.302.2.6
+++ loncom/homework/grades.pm	2006/02/27 20:32:38	1.321
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # The LON-CAPA Grading handler
 #
-# $Id: grades.pm,v 1.302.2.6 2006/05/29 18:51:35 albertel Exp $
+# $Id: grades.pm,v 1.321 2006/02/27 20:32:38 banghart Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -40,6 +40,7 @@ use Apache::lonmsg qw(:user_normal_msg);
 use Apache::Constants qw(:common);
 use Apache::lonlocal;
 use String::Similarity;
+use POSIX qw(floor);
 
 my %oldessays=();
 my %perm=();
@@ -467,6 +468,33 @@ sub jscriptNform {
     return $jscript;
 }
 
+# Given the score (as a number [0-1] and the weight) what is the final
+# point value? This function will round to the nearest tenth, third,
+# or quarter if one of those is within the tolerance of .00001.
+sub compute_points {
+    my ($score, $weight) = @_;
+    
+    my $tolerance = .00001;
+    my $points = $score * $weight;
+
+    # Check for nearness to 1/x.
+    my $check_for_nearness = sub {
+        my ($factor) = @_;
+        my $num = ($points * $factor) + $tolerance;
+        my $floored_num = floor($num);
+        if ($num - $floored_num < 2 * $tolerance * $factor) {
+            return $floored_num / $factor;
+        }
+        return $points;
+    };
+
+    $points = $check_for_nearness->(10);
+    $points = $check_for_nearness->(3);
+    $points = $check_for_nearness->(4);
+    
+    return $points;
+}
+
 #------------------ End of general use routines --------------------
 
 #
@@ -1385,31 +1413,26 @@ SUBJAVASCRIPT
 
 #--- displays the grading box, used in essay type problem and grading by page/sequence
 sub gradeBox {
-    my ($request,$symb,$uname,$udom,$counter,$partid,$record) = @_;
-
+    my ($request,$symb,$uname,$udom,$counter,$partid,$record,$respid) = @_;
     my $checkIcon = '<img src="'.$request->dir_config('lonIconsURL').
 	'/check.gif" height="16" border="0" />';
-
     my $wgt    = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname);
     my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 
 		  '<font color="red">problem weight assigned by computer</font>');
     $wgt       = ($wgt > 0 ? $wgt : '1');
     my $score  = ($$record{'resource.'.$partid.'.awarded'} eq '' ?
-		  '' : $$record{'resource.'.$partid.'.awarded'}*$wgt);
+		  '' : &compute_points($$record{'resource.'.$partid.'.awarded'},$wgt));
     my $result='<input type="hidden" name="WGT'.$counter.'_'.$partid.'" value="'.$wgt.'" />'."\n";
-
+    $result.='<br />'.$partid.' - '.$respid.'<br />';
     my $display_part=&get_display_part($partid,undef,$symb);
-
     my %last_resets = &get_last_resets($symb,$env{'request.course.id'},
 				       [$partid]);
     my $aggtries = $$record{'resource.'.$partid.'.tries'};
     if ($last_resets{$partid}) {
         $aggtries = &get_num_tries($record,$last_resets{$partid},$partid);
     }
-
     $result.='<table border="0"><tr><td>'.
 	'<b>Part: </b>'.$display_part.' <b>Points: </b></td><td>'."\n";
-
     my $ctr = 0;
     $result.='<table border="0"><tr>'."\n";  # display radio buttons in a nice table 10 across
     while ($ctr<=$wgt) {
@@ -1421,7 +1444,6 @@ sub gradeBox {
 	$ctr++;
     }
     $result.='</tr></table>';
-
     $result.='</td><td>&nbsp;<b>or</b>&nbsp;</td>'."\n";
     $result.='<td><input type="text" name="GD_BOX'.$counter.'_'.$partid.'"'.
 	($score ne ''? ' value = "'.$score.'"':'').' size="4" '.
@@ -1430,7 +1452,6 @@ sub gradeBox {
     $result.='<td>/'.$wgt.' '.$wgtmsg.
 	($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? '&nbsp;'.$checkIcon : '').
 	' </td><td>'."\n";
-
     $result.='<select name="GD_SEL'.$counter.'_'.$partid.'" '.
 	'onChange="javascript:clearRadBox(this.form,\''.$counter.'_'.$partid.'\')" >'."\n";
     if ($$record{'resource.'.$partid.'.solved'} eq 'excused') {
@@ -1451,8 +1472,26 @@ sub gradeBox {
         '<input type="hidden" name="aggtries'.$counter.'_'.$partid.'" value="'.
         $aggtries.'" />'."\n";
     $result.='</td></tr></table>'."\n";
+    $result.=&handbackBox($uname,$udom,$counter,$partid,$record,$respid);
     return $result;
 }
+sub handbackBox {
+    my ($uname,$udom,$counter,$partid,$record,$respid) = @_;
+    my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
+    my $result;
+    if (@$files) {
+        my $file_counter = 1;
+	foreach my $file (@$files) {
+	    $file =~ /.+\/(.+$)/;
+	    my $file_disp = $1;
+            $result.=' Return commented version of <span class="filename">'.$file_disp.' </span>to student. <input type="file" name="'.$partid.'_'.$respid.'_returndoc';
+            $result.=$file_counter.'" />'."\n";
+            $result.='<input type="hidden" name="'.$partid.'_'.$respid.'_origdoc'.$file_counter.'" value="'.$file.'" /><br />';
+            $file_counter++;
+        }
+    }
+    return $result;    
+}
 
 sub show_problem {
     my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_;
@@ -1577,7 +1616,7 @@ sub submission {
 	}
 	my $overRideScore = $env{'form.overRideScore'} eq '' ? 'no' : $env{'form.overRideScore'};
 
-	$request->print('<form action="/adm/grades" method="post" name="SCORE">'."\n".
+	$request->print('<form action="/adm/grades" method="post" name="SCORE" enctype="multipart/form-data">'."\n".
 			'<input type="hidden" name="command"    value="handgrade" />'."\n".
 			'<input type="hidden" name="saveState"  value="'.$env{'form.saveState'}.'" />'."\n".
 			'<input type="hidden" name="Status"     value="'.$env{'form.Status'}.'" />'."\n".
@@ -1792,24 +1831,14 @@ KEYWORDS
 			$lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part:</b> '.
 			    $display_part.' <font color="#999999">( ID '.$respid.
 			    ' )</font>&nbsp; &nbsp;';
-			my @files;
-			if ($record{"resource.$partid.$respid.portfiles"}) {
-			    my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
-			    foreach my $file (split(',',$record{"resource.$partid.$respid.portfiles"})) {
-				push(@files,$file_url.$file);
-			    
-				&Apache::lonnet::logthis("found a portfolio file".$record{"resource.$partid.$respid.portfiles"});
-				&Apache::lonnet::logthis("uploaded URL file".$record{"resource.$partid.$respid.uploadedurl"});
-			    }
-			}
-			if ($record{"resource.$partid.$respid.uploadedurl"}) {
-			    push(@files,$record{"resource.$partid.$respid.uploadedurl"});
-			}
-			if (@files) {
+			my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
+			if (@$files) {
 			    $lastsubonly.='<br /><font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />';
-			    foreach my $file (@files) {
+			    my $file_counter = 0;
+			    foreach my $file (@$files) {
+			        $file_counter ++;
 				&Apache::lonnet::allowuploaded('/adm/grades',$file);
-				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border=0"> '.$file.'</a>';
+				$lastsubonly.='<br /><a href="'.$file.'" target="lonGRDs"><img src="'.&Apache::loncommon::icon($file).'" border="0"> '.$file.'</a>';
 			    }
 			    $lastsubonly.='<br />';
 			}
@@ -1887,15 +1916,18 @@ KEYWORDS
     my %seen = ();
     my @partlist;
     my @gradePartRespid;
-    for (sort keys(%$handgrade)) {
-	my ($partid,$respid) = split(/_/);
-	next if ($seen{$partid} > 0);
+    for my $part_resp(sort keys(%$handgrade)) {
+	my ($partid,$respid) = split(/_/, $part_resp);
+	#next if ($seen{$partid} > 0);
+	if ($seen{$partid} > 0) {
+	    $request->print(&handbackBox($uname,$udom,$counter,$partid,\%record,$respid));
+	    next;
+	}
 	$seen{$partid}++;
-	next if ($$handgrade{$_} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
+	next if ($$handgrade{$part_resp} =~ /:no$/ && $env{'form.lastSub'} =~ /^(hdgrade)$/);
 	push @partlist,$partid;
 	push @gradePartRespid,$partid.'.'.$respid;
-
-	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
+	$request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record,$respid));
     }
     $result='<input type="hidden" name="partlist'.$counter.
 	'" value="'.(join ":",@partlist).'" />'."\n";
@@ -2024,18 +2056,17 @@ sub processHandGrade {
 		my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr");
 		foreach my $collabstr (@collabstrs) {
 		    my ($part,@collaborators) = split(/:/,$collabstr);
-		    foreach (@collaborators) {
+		    foreach my $collaborator (@collaborators) {
 			my ($errorflag,$pts,$wgt) = 
-			    &saveHandGrade($request,$url,$symb,$_,$udom,$ctr,
+			    &saveHandGrade($request,$url,$symb,$collaborator,$udom,$ctr,
 					   $env{'form.unamedom'.$ctr},$part);
 			if ($errorflag eq 'not_allowed') {
-			    $request->print("<font color=\"red\">Not allowed to modify grades for $_:$udom</font>");
+			    $request->print("<font color=\"red\">Not allowed to modify grades for $collaborator:$udom</font>");
 			    next;
 			} else {
 			    if ($message ne '') {
-				$msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$env{'form.msgsub'},$message);
+				$msgstatus = &Apache::lonmsg::user_normal_msg($collaborator,$udom,$env{'form.msgsub'},$message);
 			    }
-			    
 			}
 		    }
 		}
@@ -2248,7 +2279,7 @@ sub saveHandGrade {
 
             my $solvedstatus = $record{'resource.'.$new_part.'.solved'};
             if ($aggtries > 0) {
-                &decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
+                &decrement($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
                 $aggregateflag = 1;
             }
 	} elsif ($dropMenu eq '') {
@@ -2287,8 +2318,30 @@ sub saveHandGrade {
 	    $newrecord{'resource.'.$new_part.'.regrader'}=
 		"$env{'user.name'}:$env{'user.domain'}";
 	}
+	my ($partlist,$handgrade,$responseType) = &response_type($url,$symb);
+	my $portfolio_root = &Apache::loncommon::propath($domain,
+        					 $stuname).
+        					'/userfiles/portfolio';
+	foreach my $part_resp(sort(keys(%$handgrade))) {
+	    my ($part_id, $resp_id) = split(/_/,$part_resp);
+            if ($env{'form.'.$part_resp.'_returndoc1'} && ($new_part eq $part_id)) {
+                # if multiple files are uploaded names will be 'returndoc2','returndoc3'
+                my $file_counter = 1;
+                while ($env{'form.'.$part_resp.'_returndoc'.$file_counter}) {
+                    my $fname=$env{'form.returndoc'.$file_counter.'.filename'};
+                    $newrecord{"resource.$new_part.$resp_id.handback"} = $env{'form.returndocorig'.$file_counter};
+                    $request->print("<br />".$fname." will be the uploaded file name");
+                    $request->print("<font color=\"red\">Will upload document</font>".$env{'form.returndocorig'.$file_counter});
+                    $file_counter++;
+                }
+            }
+        }
+	
 	# unless problem has been graded, set flag to version the submitted files
-	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override') {
+	unless ($record{'resource.'.$new_part.'.solved'} =~ /^correct_/  || 
+	        $record{'resource.'.$new_part.'.solved'} eq 'incorrect_by_override' ||
+	        $dropMenu eq 'reset status')
+	   {
 	    push (@v_flag,$new_part);
 	}
     }
@@ -2320,7 +2373,20 @@ sub saveHandGrade {
     }
     return ('',$pts,$wgt);
 }
-
+sub get_submitted_files {
+    my ($udom,$uname,$partid,$respid,$record) = @_;
+    my @files;
+    if ($$record{"resource.$partid.$respid.portfiles"}) {
+        my $file_url = '/uploaded/'.$udom.'/'.$uname.'/portfolio';
+        foreach my $file (split(',',$$record{"resource.$partid.$respid.portfiles"})) {
+    	    push(@files,$file_url.$file);
+        }
+    }
+    if ($$record{"resource.$partid.$respid.uploadedurl"}) {
+        push(@files,$$record{"resource.$partid.$respid.uploadedurl"});
+    }
+    return (\@files);
+}
 # ----------- Provides number of tries since last reset.
 sub get_num_tries {
     my ($record,$last_reset,$part) = @_;
@@ -2382,61 +2448,78 @@ sub get_last_resets {
 
 # ----------- Handles creating versions for portfolio files as answers
 sub version_portfiles {
-    my ($record, $parts_graded, $courseid, $symb, $domain, $stuname, $v_flag) = @_;
+    my ($record, $parts_graded, $courseid, $symb, $domain, $stu_name, $v_flag) = @_;
     my $version_parts = join('|',@$v_flag);
     my $parts = join('|', @$parts_graded);
     my $portfolio_root = &Apache::loncommon::propath($domain,
-						 $stuname).
+						 $stu_name).
 						'/userfiles/portfolio';
     foreach my $key (keys(%$record)) {
         my $new_portfiles;
-	
         if ($key =~ /^resource\.($version_parts)\./ && $key =~ /\.portfiles$/ ) {
             my @v_portfiles;
             my @portfiles = split(/,/,$$record{$key});
-            &Apache::lonnet::logthis("should be unmarking and remarking $key",@portfiles);
             foreach my $file (@portfiles) {
-                &Apache::lonnet::unmark_as_readonly($domain,$stuname,[$symb,$env{'request.course.id'}],$file);
+                &Apache::lonnet::unmark_as_readonly($domain,$stu_name,[$symb,$env{'request.course.id'}],$file);
                 my ($directory,$answer_file) =($file =~ /^(.*?)([^\/]*)$/);
                 my $version = 0;
 		my ($answer_name,$answer_ver,$answer_ext) =
 		    &file_name_version_ext($answer_file);
-                my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stuname,$portfolio_root);
-                foreach my $row (@dir_list) {
-                    my ($file) = split(/\&/,$row,2);
-		    my ($file_name,$file_version,$file_ext) =
-			&file_name_version_ext($file);
-                    if (($file_name eq $answer_name) && 
-			($file_ext eq $answer_ext)) {
-                        # gets here if filename and extension match, regardless of version
-                        if ($file_version ne '') {
-                            # a versioned file is found  so save it for later
-                            if ($file_version > $version) {
-				$version = $file_version;
-			    }
-                        }
-                    }
-                }
-                $version++;
-                $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stuname/portfolio$directory$answer_file");
-                if($env{'form.copy'} eq '-1') {
-                    &Apache::lonnet::logthis('problem getting file '.$directory.$answer_file);
-                } else {
-		    my $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
-		    my $copy_result = &Apache::lonnet::finishuserfileupload(
-                                        $stuname,$domain,'copy',
-				        '/portfolio'.$directory.$new_answer);
+                my @dir_list = &Apache::lonnet::dirlist($directory,$domain,$stu_name,$portfolio_root);
+                $version = &get_next_version($answer_name, $answer_ext, \@dir_list);
+                my $new_answer = &version_selected_portfile($domain, $stu_name, $directory, $answer_file, $version);
+                if ($new_answer ne 'problem getting file') {
                     push(@v_portfiles, $directory.$new_answer);
-                    &Apache::lonnet::mark_as_readonly($domain,$stuname,
-                                ['/portfolio'.$directory.$new_answer],
-                                [$symb,$env{'request.course.id'},'graded']);
+                    &Apache::lonnet::mark_as_readonly($domain,$stu_name,
+                        ['/portfolio'.$directory.$new_answer],
+                        [$symb,$env{'request.course.id'},'graded']);
                 }
+                
             }
             $$record{$key} = join(',',@v_portfiles);
         }
     } 
     return 'ok';   
-    
+}
+
+sub get_next_version {
+    my ($answer_name, $answer_ext, $dir_list);
+    my $version;
+    foreach my $row (@$dir_list) {
+        my ($file) = split(/\&/,$row,2);
+        my ($file_name,$file_version,$file_ext) =
+	    &file_name_version_ext($file);
+        if (($file_name eq $answer_name) && 
+	    ($file_ext eq $answer_ext)) {
+                # gets here if filename and extension match, regardless of version
+                if ($file_version ne '') {
+                # a versioned file is found  so save it for later
+                if ($file_version > $version) {
+		    $version = $file_version;
+	        }
+            }
+        }
+    } 
+    $version ++;
+    return($version);
+}
+
+sub version_selected_portfile {
+    my ($domain,$stu_name,$directory,$file_name,$version) = @_;
+    my ($answer_name,$answer_ver,$answer_ext) =
+        &file_name_version_ext($file_name);
+    my $new_answer;
+    $env{'form.copy'} = &Apache::lonnet::getfile("/uploaded/$domain/$stu_name/portfolio$directory$file_name");
+    if($env{'form.copy'} eq '-1') {
+        &Apache::lonnet::logthis('problem getting file '.$file_name);
+        $new_answer = 'problem getting file';
+    } else {
+        $new_answer = $answer_name.'.'.$version.'.'.$answer_ext;
+        my $copy_result = &Apache::lonnet::finishuserfileupload(
+                            $stu_name,$domain,'copy',
+		        '/portfolio'.$directory.$new_answer);
+    }    
+    return ($new_answer);
 }
 
 sub file_name_version_ext {
@@ -2809,7 +2892,7 @@ sub viewstudentgrade {
             $aggregates{$part} = 1;
         }
 	if ($type eq 'awarded') {
-	    my $pts = $score eq '' ? '' : $score*$$weight{$part};
+	    my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
 	    $result.='<input type="hidden" name="'.
 		'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n";
 	    $result.='<input type="text" name="'.
@@ -3291,13 +3374,13 @@ sub csvuploadmap {
 	    unshift(@fields,['none','']);
 	    $i=&Apache::loncommon::csv_samples_select_table($request,\@records,
 							    \@fields);
-	    foreach my $rec (@records) {
-		my %temp = &Apache::loncommon::record_sep($rec);
-		if (%temp) {
-		    $keyfields=join(',',sort(keys(%temp)));
-		    last;
-		}
-	    }
+            foreach my $rec (@records) {
+                my %temp = &Apache::loncommon::record_sep($rec);
+                if (%temp) {
+                    $keyfields=join(',',sort(keys(%temp)));
+                    last;
+                }
+            }
 	}
     }
     &csvuploadmap_footer($request,$i,$keyfields);
@@ -3459,7 +3542,7 @@ sub csvuploadassign {
     }
     $request->print("<br />Stored $countdone students\n");
     if (@skipped) {
-	$request->print('<p><font size="+1"><b>Skipped Students</b></font></p>');
+	$request->print('<p<font size="+1"><b>Skipped Students</b></font></p>');
 	foreach my $student (@skipped) { $request->print("$student<br />\n"); }
     }
     if (@notallowed) {
@@ -3679,7 +3762,7 @@ sub displayPage {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }
 
-        if (ref($curRes) && $curRes->is_problem()) {
+        if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
 	    my $parts = $curRes->parts();
             my $title = $curRes->compTitle();
 	    my $symbx = $curRes->symb();
@@ -3876,7 +3959,7 @@ sub updateGradeByPage {
         if($curRes == $iterator->BEGIN_MAP) { $depth++; }
         if($curRes == $iterator->END_MAP) { $depth--; }
 
-        if (ref($curRes) && $curRes->is_problem()) {
+        if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) {
 	    my $parts = $curRes->parts();
             my $title = $curRes->compTitle();
 	    my $symbx = $curRes->symb();