--- loncom/homework/grades.pm 2001/07/27 18:20:16 1.10
+++ loncom/homework/grades.pm 2007/07/04 20:28:44 1.420
@@ -1,7 +1,30 @@
+# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
-# 2/9,2/13 Guy Albertelli
-# 6/8 Gerd Kortemeyer
-# 7/26 H.K. Ng
+#
+# $Id: grades.pm,v 1.420 2007/07/04 20:28:44 www Exp $
+#
+# Copyright Michigan State University Board of Trustees
+#
+# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
+#
+# LON-CAPA is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LON-CAPA is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LON-CAPA; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# /home/httpd/html/adm/gpl.txt
+#
+# http://www.lon-capa.org/
+#
package Apache::grades;
use strict;
@@ -9,345 +32,6664 @@ use Apache::style;
use Apache::lonxml;
use Apache::lonnet;
use Apache::loncommon;
+use Apache::lonhtmlcommon;
+use Apache::lonnavmaps;
use Apache::lonhomework;
+use Apache::loncoursedata;
+use Apache::lonmsg();
use Apache::Constants qw(:common);
+use Apache::lonlocal;
+use Apache::lonenc;
+use String::Similarity;
+use LONCAPA;
-sub moreinfo {
- my ($request,$reason) = @_;
- $request->print("Unable to process request: $reason");
- if ( $Apache::grades::viewgrades eq 'F' ) {
- $request->print('
');
- }
- return '';
+use POSIX qw(floor);
+
+my %oldessays=();
+my %perm=();
+
+# ----- These first few routines are general use routines.----
+#
+# --- Retrieve the parts from the metadata file.---
+sub getpartlist {
+ my ($symb) = @_;
+ my (undef,undef,$url) = &Apache::lonnet::decode_symb($symb);
+ my $partorder = &Apache::lonnet::metadata($url, 'partorder');
+ my @parts;
+ if ($partorder) {
+ for my $part (split (/,/,$partorder)) {
+ if (!&Apache::loncommon::check_if_partid_hidden($part,$symb)) {
+ push(@parts, $part);
+ }
+ }
+ } else {
+ my $metadata = &Apache::lonnet::metadata($url, 'packages');
+ foreach (split(/\,/,$metadata)) {
+ if ($_ =~ /^part_(.*)$/) {
+ if (!&Apache::loncommon::check_if_partid_hidden($1,$symb)) {
+ push(@parts, $1);
+ }
+ }
+ }
+ }
+ my @stores;
+ foreach my $part (@parts) {
+ my (@metakeys) = split(/,/,&Apache::lonnet::metadata($url,'keys'));
+ foreach my $key (@metakeys) {
+ if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); }
+ }
+ }
+ return @stores;
+}
+
+# --- Get the symbolic name of a problem and the url
+sub get_symb {
+ my ($request,$silent) = @_;
+ (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--;
+ my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
+ if ($symb eq '') {
+ if (!$silent) {
+ $request->print("Unable to handle ambiguous references:$url:.");
+ return ();
+ }
+ }
+ &Apache::lonenc::check_decrypt(\$symb);
+ return ($symb);
+}
+
+#--- Format fullname, username:domain if different for display
+#--- Use anywhere where the student names are listed
+sub nameUserString {
+ my ($type,$fullname,$uname,$udom) = @_;
+ if ($type eq 'header') {
+ return ' Fullname (Username)';
+ } else {
+ return ' '.$fullname.' ('.$uname.
+ ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')';
+ }
+}
+
+#--- Get the partlist and the response type for a given problem. ---
+#--- Indicate if a response type is coded handgraded or not. ---
+sub response_type {
+ my ($symb) = shift;
+
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my $res = $navmap->getBySymb($symb);
+ my $partlist = $res->parts();
+ my %vPart =
+ map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
+ my (%response_types,%handgrade);
+ foreach my $part (@{ $partlist }) {
+ next if (%vPart && !exists($vPart{$part}));
+
+ my @types = $res->responseType($part);
+ my @ids = $res->responseIds($part);
+ for (my $i=0; $i < scalar(@ids); $i++) {
+ $response_types{$part}{$ids[$i]} = $types[$i];
+ $handgrade{$part.'_'.$ids[$i]} =
+ &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
+ '.handgrade',$symb);
+ }
+ }
+ return ($partlist,\%handgrade,\%response_types);
+}
+
+sub flatten_responseType {
+ my ($responseType) = @_;
+ my @part_response_id =
+ map {
+ my $part = $_;
+ map {
+ [$part,$_]
+ } sort(keys(%{ $responseType->{$part} }));
+ } sort(keys(%$responseType));
+ return @part_response_id;
+}
+
+sub get_display_part {
+ my ($partID,$symb)=@_;
+ my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb);
+ if (defined($display) and $display ne '') {
+ $display.= " (id $partID)";
+ } else {
+ $display=$partID;
+ }
+ return $display;
+}
+
+#--- Show resource title
+#--- and parts and response type
+sub showResourceInfo {
+ my ($symb,$probTitle,$checkboxes) = @_;
+ my $col=3;
+ if ($checkboxes) { $col=4; }
+ my $result = '
'.&mt('Current Resource').': '.$probTitle.'
'."\n";
+ $result .='
';
+ my ($partlist,$handgrade,$responseType) = &response_type($symb);
+ my %resptype = ();
+ my $hdgrade='no';
+ my %partsseen;
+ foreach my $partID (sort keys(%$responseType)) {
+ foreach my $resID (sort keys(%{ $responseType->{$partID} })) {
+ my $handgrade=$$handgrade{$partID.'_'.$resID};
+ my $responsetype = $responseType->{$partID}->{$resID};
+ $hdgrade = $handgrade if ($handgrade eq 'yes');
+ $result.='
';
+ if ($checkboxes) {
+ if (exists($partsseen{$partID})) {
+ $result.="
';
+ my @grade = grep(/^\Q${version}resource.$respid.$partid.\E[^.]*[.]status$/,
+ keys(%{$record}));
+ foreach my $grade (sort(@grade)) {
+ my ($dim) = ($grade =~/[.]([^.]+)[.]status$/);
+ $result.= '
'.&mt("Dimension: [_1], status [_2] ",
+ $dim, $record->{$grade}).
+ '
';
+ }
+ $result.='
';
+ return $result;
+ }
+
+ }
+ return $answer;
+}
+
+#-- A couple of common js functions
+sub commonJSfunctions {
+ my $request = shift;
+ $request->print(<
+ function radioSelection(radioButton) {
+ var selection=null;
+ if (radioButton.length > 1) {
+ for (var i=0; i 1) {
+ for (var i=0; i
+COMMONJSFUNCTIONS
+}
+
+#--- Dumps the class list with usernames,list of sections,
+#--- section, ids and fullnames for each user.
+sub getclasslist {
+ my ($getsec,$filterlist) = @_;
+ my @getsec;
+ if (!ref($getsec)) {
+ if ($getsec ne '' && $getsec ne 'all') {
+ @getsec=($getsec);
+ }
+ } else {
+ @getsec=@{$getsec};
+ }
+ if (grep(/^all$/,@getsec)) { undef(@getsec); }
+
+ my $classlist=&Apache::loncoursedata::get_classlist();
+ # Bail out if we were unable to get the classlist
+ return if (! defined($classlist));
+ #
+ my %sections;
+ my %fullnames;
+ foreach my $student (keys(%$classlist)) {
+ my $end =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_END()];
+ my $start =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_START()];
+ my $id =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_ID()];
+ my $section =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()];
+ my $fullname =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()];
+ my $status =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()];
+ # filter students according to status selected
+ if ($filterlist && $env{'form.Status'} ne 'Any') {
+ if ($env{'form.Status'} ne $status) {
+ delete ($classlist->{$student});
+ next;
+ }
+ }
+ $section = ($section ne '' ? $section : 'none');
+ if (&canview($section)) {
+ if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
+ $sections{$section}++;
+ $fullnames{$student}=$fullname;
+ } else {
+ delete($classlist->{$student});
+ }
+ } else {
+ delete($classlist->{$student});
+ }
+ }
+ my %seen = ();
+ my @sections = sort(keys(%sections));
+ return ($classlist,\@sections,\%fullnames);
}
+sub canmodify {
+ my ($sec)=@_;
+ if ($perm{'mgr'}) {
+ if (!defined($perm{'mgr_section'})) {
+ # can modify whole class
+ return 1;
+ } else {
+ if ($sec eq $perm{'mgr_section'}) {
+ #can modify the requested section
+ return 1;
+ } else {
+ # can't modify the request section
+ return 0;
+ }
+ }
+ }
+ #can't modify
+ return 0;
+}
+sub canview {
+ my ($sec)=@_;
+ if ($perm{'vgr'}) {
+ if (!defined($perm{'vgr_section'})) {
+ # can modify whole class
+ return 1;
+ } else {
+ if ($sec eq $perm{'vgr_section'}) {
+ #can modify the requested section
+ return 1;
+ } else {
+ # can't modify the request section
+ return 0;
+ }
+ }
+ }
+ #can't modify
+ return 0;
+}
+
+#--- Retrieve the grade status of a student for all the parts
+sub student_gradeStatus {
+ my ($symb,$udom,$uname,$partlist) = @_;
+ my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname);
+ my %partstatus = ();
+ foreach (@$partlist) {
+ my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2);
+ $status = 'nothing' if ($status eq '');
+ $partstatus{$_} = $status;
+ my $subkey = "resource.$_.submitted_by";
+ $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne '');
+ }
+ return %partstatus;
+}
+
+# hidden form and javascript that calls the form
+# Use by verifyscript and viewgrades
+# Shows a student's view of problem and submission
+sub jscriptNform {
+ my ($symb) = @_;
+ my $jscript=''."\n";
+ $jscript.= ''."\n";
+ 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 --------------------
+
+#
+# Find most similar essay
+#
+
+sub most_similar {
+ my ($uname,$udom,$uessay)=@_;
+
+# ignore spaces and punctuation
+
+ $uessay=~s/\W+/ /gs;
+
+# ignore empty submissions (occuring when only files are sent)
+
+ unless ($uessay=~/\w+/) { return ''; }
+
+# 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
+#
+#--- Check whether a receipt number is valid.---
+sub verifyreceipt {
+ my $request = shift;
+
+ my $courseid = $env{'request.course.id'};
+ my $receipt = &Apache::lonnet::recprefix($courseid).'-'.
+ $env{'form.receipt'};
+ $receipt =~ s/[^\-\d]//g;
+ my ($symb) = &get_symb($request);
+
+ my $title.='