--- loncom/homework/grades.pm 2003/04/19 09:02:57 1.85 +++ loncom/homework/grades.pm 2003/05/30 21:20:20 1.91 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.85 2003/04/19 09:02:57 albertel Exp $ +# $Id: grades.pm,v 1.91 2003/05/30 21:20:20 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -46,6 +46,9 @@ use Apache::lonhomework; use Apache::loncoursedata; use Apache::lonmsg qw(:user_normal_msg); use Apache::Constants qw(:common); +use String::Similarity; + +my %oldessays=(); # ----- These first few routines are general use routines.---- # @@ -221,6 +224,50 @@ sub jscriptNform { } #------------------ End of general use routines -------------------- + +# +# Find most similar essay +# + +sub most_similar { + my ($uname,$udom,$uessay)=@_; + +# ignore spaces and punctuation + + $uessay=~s/\W+/ /gs; + +# these will be returned. Do not care if not at least 50 percent similar + my $limit=0.6; + my $sname=''; + my $sdom=''; + my $scrsid=''; + my $sessay=''; +# go through all essays ... + foreach my $tkey (keys %oldessays) { + my ($tname,$tdom,$tcrsid)=split(/\./,$tkey); +# ... except the same student + if (($tname ne $uname) || ($tdom ne $udom)) { + my $tessay=$oldessays{$tkey}; + $tessay=~s/\W+/ /gs; +# String similarity gives up if not even limit + my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); +# Found one + if ($tsimilar>$limit) { + $limit=$tsimilar; + $sname=$tname; + $sdom=$tdom; + $scrsid=$tcrsid; + $sessay=$oldessays{$tkey}; + } + } + } + if ($limit>0.6) { + return ($sname,$sdom,$scrsid,$sessay,$limit); + } else { + return ('','','','',0); + } +} + #------------------------------------------------------------------- #------------------------------------ Receipt Verification Routines @@ -1075,6 +1122,9 @@ sub submission { $request->print($prnmsg); if ($ENV{'form.handgrade'} eq 'yes' && $ENV{'form.showgrading'} eq 'yes') { +# +# Print out the keyword options line +# $request->print(<<KEYWORDS); <b>Keyword Options:</b> <a href="javascript:keywords(document.SCORE.keywords)"; TARGET=_self>List</a> @@ -1082,6 +1132,14 @@ sub submission { CLASS="page">Paste Selection to List</a> <a href="javascript:kwhighlight()"; TARGET=_self>Highlight Attribute</a><br /><br /> KEYWORDS +# +# Load the other essays for similarity check +# + my $essayurl=&Apache::lonnet::declutter($url); + my ($adom,$aname,$apath)=($essayurl=~/^(\w+)\/(\w+)\/(.*)$/); + $apath=&Apache::lonnet::escape($apath); + $apath=~s/\W/\_/gs; + %oldessays=&Apache::lonnet::dump('nohist_essay_'.$apath,$adom,$aname); } } @@ -1108,7 +1166,6 @@ KEYWORDS my @col_fullnames; my ($classlist,$fullname); if ($ENV{'form.handgrade'} eq 'yes') { - my @col_list; ($classlist,undef,$fullname) = &getclasslist('all','0'); for (keys (%$handgrade)) { my $ncol = &Apache::lonnet::EXT('resource.'.$_. @@ -1117,56 +1174,46 @@ KEYWORDS next if ($ncol <= 0); s/\_/\./g; next if ($record{'resource.'.$_.'.collaborators'} eq ''); - my (@colList) = split(/,?\s+/, - $record{'resource.'.$_.'.collaborators'}); - my @collaborators = (); - foreach (@colList) { #pre-filter list - throw out submitter + my @goodcollaborators = (); + my @badcollaborators = (); + foreach (split(/,?\s+/,$record{'resource.'.$_.'.collaborators'})) { + $_ =~ s/[\$\^\(\)]//g; + next if ($_ eq ''); my ($co_name,$co_dom) = split /\@|:/,$_; - $co_dom = $udom if (! defined($co_dom)); + $co_dom = $udom if (! defined($co_dom) || $co_dom =~ /^domain$/i); next if ($co_name eq $uname && $co_dom eq $udom); - push @collaborators, $_; + # Doing this grep allows 'fuzzy' specification + my @Matches = grep /^$co_name:$co_dom$/i,keys %$classlist; + if (! scalar(@Matches)) { + push @badcollaborators,$_; + } else { + push @goodcollaborators, @Matches; + } } - my (@badcollaborators); - if (scalar(@collaborators) != 0) { + if (scalar(@goodcollaborators) != 0) { $result.='<b>Collaborators: </b>'; - foreach my $collaborator (@collaborators) { - my ($co_name,$co_dom) = split /\@|:/,$collaborator; - $co_dom = $udom if (! defined($co_dom)); - # Doing this grep allows 'fuzzy' specification - my @Matches = grep /^$co_name:$co_dom$/i, - keys %$classlist; - if (! scalar(@Matches)) { - push @badcollaborators,':'.$collaborator.':'; - next; - } - push @col_list, @Matches; - foreach (@Matches) { - my ($lastname,$givenn) = split(/,/,$$fullname{$_}); - push @col_fullnames, $givenn.' '.$lastname; - $result.=$$fullname{$_}.' '; - } - } + foreach (@goodcollaborators) { + my ($lastname,$givenn) = split(/,/,$$fullname{$_}); + push @col_fullnames, $givenn.' '.$lastname; + $result.=$$fullname{$_}.' '; + } $result.='<br />'."\n"; - if (scalar(@badcollaborators) > 0) { - $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'; - $result.='This student has submitted '; - if (scalar(@badcollaborators) == 1) { - $result .= 'an invalid collaborator'; - } else { - $result .= 'invalid collaborators'; - } - $result .= ': '.join(', ',@badcollaborators); - $result .= '</td></tr></table>'; - } - if (scalar(@collaborators > $ncol)) { - $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>'; - $result .= 'This student has submitted too many '. - 'collaborators. Maximum is '.$ncol; - $result .= '</td></tr></table>'; - } - $result.='<input type="hidden" name="collaborator'.$counter. - '" value="'.(join ':',@col_list).'" />'."\n"; - } + $result.='<input type="hidden" name="collaborator'.$counter. + '" value="'.(join ':',@goodcollaborators).'" />'."\n"; + } + if (scalar(@badcollaborators) > 0) { + $result.='<table border="0"><tr bgcolor="#ffbbbb"><td>'; + $result.='This student has submitted '; + $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators'; + $result .= ': '.join(', ',@badcollaborators); + $result .= '</td></tr></table>'; + } + if (scalar(@badcollaborators > $ncol)) { + $result .= '<table border="0"><tr bgcolor="#ffbbbb"><td>'; + $result .= 'This student has submitted too many '. + 'collaborators. Maximum is '.$ncol.'.'; + $result .= '</td></tr></table>'; + } } } $request->print($result."\n"); @@ -1200,6 +1247,15 @@ KEYWORDS my ($partid,$respid) = /^resource\.(\d+)\.(\d+)\.submission/; if ($part eq ($partid.'_'.$respid)) { my ($ressub,$subval) = split(/:/,$_,2); +# Similarity check + my $similar=''; + my ($oname,$odom,$ocrsid,$oessay,$osim)=&most_similar($uname,$udom,$subval); + if ($osim) { + $osim=int($osim*100.0); + $similar='<hr /><h3><font color="#FF0000">Essay is '.$osim.'% similar to an essay by '.&Apache::loncommon::plainname($oname,$odom). + '</font></h3><blockquote><i>'. + &keywords_highlight($oessay).'</i></blockquote><hr />'; + } $lastsubonly.='<tr><td bgcolor="#ffffe6"><b>Part '. $partid.'</b> <font color="#999999">( ID '.$respid. ' )</font> '. @@ -1207,8 +1263,8 @@ KEYWORDS '<a href="'. &Apache::lonnet::tokenwrapper($record{"resource.$partid.$respid.uploadedurl"}). '"><img src="/adm/lonIcons/unknown.gif" border=0"> File uploaded by student</a> <font color="red" size="1">Like all files provided by users, this file may contain virusses</font><br />':''). - '<b>Answer: </b>'. - &keywords_highlight($subval).'</td></tr>'."\n" + '<b>Answer: </b><blockquote>'. + &keywords_highlight($subval).'</blockquote><br /> '.$similar.'</td></tr>'."\n" if ($ENV{'form.lastSub'} eq 'lastonly' || ($ENV{'form.lastSub'} eq 'hdgrade' && $$handgrade{$part} =~ /:yes$/)); @@ -1246,7 +1302,7 @@ KEYWORDS my $lastone = pop @col_fullnames; $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.'; } - $msgfor =~ s/\'/\\'/g; + $msgfor =~ s/\'/\\'/g; #' stupid emacs $result.='<tr><td bgcolor="#ffffff">'."\n". ' <a href="javascript:msgCenter(document.SCORE,'.$counter. ',\''.$msgfor.'\')"; TARGET=_self>'. @@ -1444,12 +1500,20 @@ sub processHandGrade { $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); # Called by Save & Refresh from Highlight Attribute Window + my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'0'); if ($ENV{'form.refresh'} eq 'on') { - my $ctr = 0; - $ENV{'form.NTSTU'}=$ngrade; + my ($ctr,$total) = (0,0); while ($ctr < $ngrade) { - ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$ENV{'form.unamedom'.$ctr}); - &submission($request,$ctr,$ngrade-1); + $total++ if $ENV{'form.unamedom'.$ctr} ne ''; + $ctr++; + } + $ENV{'form.NTSTU'}=$ngrade; + $ctr = 0; + while ($ctr < $total) { + my $processUser = $ENV{'form.unamedom'.$ctr}; + ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$processUser); + $ENV{'form.fullname'} = $$fullname{$processUser}; + &submission($request,$ctr,$total-1); $ctr++; } return ''; @@ -1465,7 +1529,6 @@ sub processHandGrade { $laststu = $firststu if ($ctr > $ngrade); } - my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'0'); my (@parsedlist,@nextlist); my ($nextflg) = 0; foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { @@ -1850,7 +1913,8 @@ sub viewgrades { my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1'); my $ctr = 0; foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { - my ($uname,$udom) = split(/:/); + my $uname = $_; + $uname=~s/:/_/; $result.='<input type="hidden" name="ctr'.$ctr.'" value="'.$uname.'" />'."\n"; $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'}, $_,$$fullname{$_},\@parts,\%weight); @@ -1868,6 +1932,7 @@ sub viewgrades { sub viewstudentgrade { my ($url,$symb,$courseid,$student,$fullname,$parts,$weight) = @_; my ($uname,$udom) = split(/:/,$student); + $student=~s/:/_/; my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname); my $result='<tr bgcolor="#ffffdd"><td>'. '<a href="javascript:viewOneStudent(\''.$uname.'\',\''.$udom. @@ -1879,19 +1944,19 @@ sub viewstudentgrade { if ($type eq 'awarded') { my $pts = $score eq '' ? '' : $score*$$weight{$part}; $result.='<input type="hidden" name="'. - 'GD_'.$uname.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n"; + 'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n"; $result.='<td align="middle"><input type="text" name="'. - 'GD_'.$uname.'_'.$part.'_awarded" '. - 'onChange="javascript:changeSelect(\''.$part.'\',\''.$uname. + 'GD_'.$student.'_'.$part.'_awarded" '. + 'onChange="javascript:changeSelect(\''.$part.'\',\''.$student. '\')" value="'.$pts.'" size="4" /></td>'."\n"; } elsif ($type eq 'solved') { my ($status,$foo)=split(/_/,$score,2); $status = 'nothing' if ($status eq ''); - $result.='<input type="hidden" name="'.'GD_'.$uname.'_'. + $result.='<input type="hidden" name="'.'GD_'.$student.'_'. $part.'_solved_s" value="'.$status.'" />'."\n"; $result.='<td align="middle"><select name="'. - 'GD_'.$uname.'_'.$part.'_solved" '. - 'onChange="javascript:changeOneScore(\''.$part.'\',\''.$uname.'\')" >'."\n"; + 'GD_'.$student.'_'.$part.'_solved" '. + 'onChange="javascript:changeOneScore(\''.$part.'\',\''.$student.'\')" >'."\n"; my $optsel = '<option selected="on"> </option><option>excused</option>'."\n"; $optsel = '<option> </option><option selected="on">excused</option>'."\n" if ($status eq 'excused'); @@ -1899,10 +1964,10 @@ sub viewstudentgrade { $result.="</select></td>\n"; } else { $result.='<input type="hidden" name="'. - 'GD_'.$uname.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'. + 'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'. "\n"; $result.='<td align="middle"><input type="text" name="'. - 'GD_'.$uname.'_'.$part.'_'.$type.'" '. + 'GD_'.$student.'_'.$part.'_'.$type.'" '. 'value="'.$score.'" size="4" /></td>'."\n"; } } @@ -1922,7 +1987,7 @@ sub editgrades { $title.='<font size=+1><b>Section: </b>'.$ENV{'form.section'}.'</font>'."\n"; my $result= '<table border="0"><tr><td bgcolor="#777777">'."\n"; $result.= '<table border="0"><tr bgcolor="#deffff">'. - '<td rowspan=2><b>Username</b></td><td rowspan=2><b>Fullname</b></td>'."\n"; + '<td rowspan=2><b>Username</b></td><td rowspan=2><b>Domain</b></td><td rowspan=2><b>Fullname</b></td>'."\n"; my %scoreptr = ( 'correct' =>'correct_by_override', @@ -1973,13 +2038,13 @@ sub editgrades { for ($i=0; $i<$ENV{'form.total'}; $i++) { my $user = $ENV{'form.ctr'.$i}; + my ($uname,$udom)=split(/:/,$user); my %newrecord; my $updateflag = 0; - my @userdom = grep /^$user:/,keys %$classlist; - my (undef,$udom) = split(/:/,$userdom[0]); - $result .= '<tr bgcolor="#ffffde"><td>'.$user.' </td><td>'. - $$fullname{$userdom[0]}.' </td>'; + $result .= '<tr bgcolor="#ffffde"><td>'.$uname.' </td><td>'. + $udom.' </td><td>'. + $$fullname{$user}.' </td>'; foreach (@partid) { my $old_aw = $ENV{'form.GD_'.$user.'_'.$_.'_awarded_s'}; my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1); @@ -2030,7 +2095,7 @@ sub editgrades { if ($updateflag) { $count++; &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'}, - $udom,$user); + $udom,$uname); } } $result .= '</table></td></tr></table>'."\n". @@ -2211,6 +2276,48 @@ sub csvuploadmap_footer { ENDPICK } +sub upcsvScores_form { + my ($request) = shift; + my ($symb,$url)=&get_symb_and_url($request); + if (!$symb) {return '';} + my $result =<<CSVFORMJS; +<script type="text/javascript" language="javascript"> + function checkUpload(formname) { + if (formname.upfile.value == "") { + alert("Please use the browse button to select a file from your local directory."); + return false; + } + formname.submit(); + } + </script> +CSVFORMJS + $ENV{'form.probTitle'} = &Apache::lonnet::gettitle($symb); + $result.='<br /><table width=100% border=0><tr><td bgcolor="#777777">'."\n"; + $result.='<table width=100% border=0><tr bgcolor="#e6ffff"><td>'."\n"; + $result.=' <b>Specify a file containing the class scores for problem - '.$ENV{'form.probTitle'}. + '.</b></td></tr>'."\n"; + $result.='<tr bgcolor=#ffffe6><td>'."\n"; + my $upfile_select=&Apache::loncommon::upfile_select_html(); + $result.=<<ENDUPFORM; +<form method="post" enctype="multipart/form-data" action="/adm/grades" name="gradesupload" target="LONcatInfo"> +<input type="hidden" name="symb" value="$symb" /> +<input type="hidden" name="url" value="$url" /> +<input type="hidden" name="command" value="csvuploadmap" /> +<input type="hidden" name="probTitle" value="$ENV{'form.probTitle'}" /> +<input type="hidden" name="saveState" value="$ENV{'form.saveState'}" /> +$upfile_select +<br /><input type="button" onClick="javascript:checkUpload(this.form);" value="Upload Scores" /> + +</form> +ENDUPFORM + $result.='</td></tr></table>'."\n"; + $result.='</td></tr></table><br /><br />'."\n"; + $result.=&show_grading_menu_form($symb,$url); + + return $result; +} + + sub csvuploadmap { my ($request)= @_; my ($symb,$url)=&get_symb_and_url($request); @@ -2553,7 +2660,6 @@ sub displayPage { if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { my $parts = $curRes->parts(); - $parts = &temp_parts_fix($parts); # remove line when lonnavmap is fixed my $title = $curRes->compTitle(); my $symbx = $curRes->symb(); $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$question. @@ -2646,18 +2752,6 @@ sub displayPage { return ''; } -sub temp_parts_fix { #remove sub once lonnavmap is fixed - my $parts = shift; - my %seen = (); - my @correctParts = (); - foreach (@{$parts}) { - next if ($seen{$_} > 0); - $seen{$_}++; - push @correctParts,$_; - } - return \@correctParts; -} - sub updateGradeByPage { my ($request) = shift; @@ -2698,7 +2792,6 @@ sub updateGradeByPage { if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { my $parts = $curRes->parts(); - $parts = &temp_parts_fix($parts); # remove line when lonnavmap is fixed my $title = $curRes->compTitle(); my $symbx = $curRes->symb(); $studentTable.='<tr bgcolor="#ffffe6"><td align="center" valign="top" >'.$question. @@ -3124,10 +3217,7 @@ sub gradingmenu { var cmd = formname.command; formname.saveState.value = "saveCmd="+radioSelection(cmd)+":saveSec="+pullDownSelection(formname.section)+ ":saveSub="+radioSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.status); - if (cmd[0].checked || cmd[1].checked || cmd[2].checked || cmd[4].checked) formname.submit(); - - if (cmd[3].checked) browseAndUpload(); - + if (cmd[0].checked || cmd[1].checked || cmd[2].checked || cmd[3].checked || cmd[4].checked) formname.submit(); if (cmd[5].checked) { if (!checkReceiptNo(formname,'notOK')) { return false;} formname.submit(); @@ -3176,57 +3266,6 @@ sub gradingmenu { } } - function browseAndUpload() { - bNLoad = window.open('', 'BrowseAndUpload', 'toolbar=no,location=no,scrollbars=no,width=550,height=200,screenx=100,screeny=75'); - bNLoad.focus(); - var lDoc = bNLoad.document; - lDoc.write("<html><head>"); - lDoc.write("<title>Browse And Upload</title>"); - - lDoc.write("<script language=javascript>"); - lDoc.write("function checkUpload(formname) {"); - - lDoc.write(" if (formname.upfile.value == \\"\\") {"); - lDoc.write(" alert(\\"Please use the browse button to select a file from your local directory.\\");"); - lDoc.write(" return false;"); - lDoc.write(" }"); - lDoc.write(" var openformname = opener.document.gradingMenu;"); - lDoc.write(" formname.saveState.value = \\"saveCmd=\\"+opener.radioSelection(openformname.command)+\\":saveSec=\\"+opener.pullDownSelection(openformname.section)+\\":saveSub=\\"+opener.radioSelection(openformname.submitonly)+\\":saveStatus=\\"+opener.pullDownSelection(openformname.status);"); - lDoc.write(" document.gradesupload.submit();"); - lDoc.write(" if (navigator.appName !=\\"Netscape\\") {self.close()};"); - lDoc.write(" setTimeout('self.close()',750)"); - lDoc.write("}"); - - lDoc.write("<"); - lDoc.write("/script>"); - - lDoc.write("</head><body bgcolor=white>"); - lDoc.write("<form method=\\"post\\" enctype=\\"multipart/form-data\\" action=\\"/adm/grades\\" name=\\"gradesupload\\" target=\\"LONcatInfo\\">"); - lDoc.write("<input type=\\"hidden\\" name=\\"symb\\" value=\\"$symb\\">"); - lDoc.write("<input type=\\"hidden\\" name=\\"url\\" value=\\"$url\\">"); - lDoc.write("<input type=\\"hidden\\" name=\\"probTitle\\" value=\\"$probTitle\\">"); - lDoc.write("<input type=\\"hidden\\" name=\\"saveState\\" value=\\"\\">"); - lDoc.write("<input type=\\"hidden\\" name=\\"command\\" value=\\"csvuploadmap\\">"); - - lDoc.write("<font color=\\"green\\" size=+1> <b>Specify a file containing the class scores for problem - $probTitle</b></font><br><br>"); - - lDoc.write("<table border=0 width=100%><tr><td bgcolor=\\"#777777\\">"); - lDoc.write("<table border=0 width=100%><tr bgcolor=\\"#ddffff\\">"); - lDoc.write("<td>"); - lDoc.write("<input type=\\"file\\" name=\\"upfile\\" size=\\"50\\" />"); - lDoc.write("<br />Type: <select name=\\"upfiletype\\">"); - lDoc.write("<option value=\\"csv\\">CSV (comma separated values, spreadsheet)</option>"); - lDoc.write("<option value=\\"space\\">Space separated</option>"); - lDoc.write("<option value=\\"tab\\">Tabulator separated</option>"); - lDoc.write("<option value=\\"xml\\">HTML/XML</option>"); - lDoc.write("</select>"); - lDoc.write("</td></tr></table>"); - lDoc.write("</td></tr></table> "); - lDoc.write("<input type=\\"button\\" value=\\"Upload Scores\\" onClick=\\"javascript:checkUpload(this.form)\\"> "); - lDoc.write("<input type=\\"button\\" value=\\"Cancel\\" onClick=\\"self.close()\\"><br><br>"); - lDoc.write("</form>"); - lDoc.write("</body></html>"); - } </script> GRADINGMENUJS @@ -3288,8 +3327,8 @@ GRADINGMENUJS ($saveSub eq 'all' ? 'checked' : '').' /> everybody</td></tr>'."\n". '<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'. - '<input type="radio" name="command" value="csvupload" '. - ($saveCmd eq 'csvupload' ? 'checked' : '').'> '. + '<input type="radio" name="command" value="csvform" '. + ($saveCmd eq 'csvform' ? 'checked' : '').'> '. 'Upload scores from file</td></tr>'."\n"; $result.='<tr bgcolor="#ffffe6"valign="top"><td colspan="2">'.