--- loncom/homework/grades.pm 2002/07/30 19:59:58 1.43 +++ loncom/homework/grades.pm 2004/09/27 20:59:21 1.216 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.43 2002/07/30 19:59:58 ng Exp $ +# $Id: grades.pm,v 1.216 2004/09/27 20:59:21 www Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,13 +25,7 @@ # # http://www.lon-capa.org/ # -# 2/9,2/13 Guy Albertelli -# 6/8 Gerd Kortemeyer -# 7/26 H.K. Ng -# 8/20 Gerd Kortemeyer -# Year 2002 -# June, July 2002 H.K. Ng -# +### package Apache::grades; use strict; @@ -39,338 +33,1431 @@ 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 qw(:user_normal_msg); use Apache::Constants qw(:common); -#use Time::HiRes qw( gettimeofday tv_interval ); +use Apache::lonlocal; +use String::Similarity; -sub moreinfo { - my ($request,$reason) = @_; - $request->print("Unable to process request: $reason"); - if ( $Apache::grades::viewgrades eq 'F' ) { - $request->print('
'."\n"); - if ($ENV{'form.url'}) { - $request->print(''."\n"); - } - if ($ENV{'form.symb'}) { - $request->print(''."\n"); - } -# $request->print(''."\n"); - $request->print(''."\n"); - $request->print("Student:".''."
\n"); - $request->print("Domain:".''."
\n"); - $request->print(''."
\n"); - $request->print('
'); - } - return ''; -} +my %oldessays=(); +my %perm=(); -sub verifyreceipt { - my $request=shift; - my $courseid=$ENV{'request.course.id'}; -# my $cdom=$ENV{"course.$courseid.domain"}; -# my $cnum=$ENV{"course.$courseid.num"}; - my $receipt=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}).'-'. - $ENV{'form.receipt'}; - $receipt=~s/[^\-\d]//g; - my $symb=$ENV{'form.symb'}; - unless ($symb) { - $symb=&Apache::lonnet::symbread($ENV{'form.url'}); +# ----- These first few routines are general use routines.---- +# +# --- Retrieve the parts from the metadata file.--- +sub getpartlist { + my ($url,$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); + } + } + } } - if ((&Apache::lonnet::allowed('mgr',$courseid)) && ($symb)) { - $request->print('

Verifying Submission Receipt '.$receipt.'

'); - my $matches=0; - my ($classlist) = &getclasslist('all','0'); - foreach my $student ( sort(@{ $$classlist{'all'} }) ) { - my ($uname,$udom)=split(/\:/,$student); - if ($receipt eq - &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb)) { - $request->print('Matching '.$student.'
'); - $matches++; - } - } - $request->printf('

'.$matches." match%s

",$matches <= 1 ? '' : 'es'); -# needs to print who is matched + 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 ''; + return @stores; } -sub student_gradeStatus { - my ($url,$udom,$uname,$partlist) = @_; +# --- Get the symbolic name of a problem and the url +sub get_symb_and_url { + 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))); - my %record= &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); - my %partstatus = (); - foreach (@$partlist) { - my ($status,$foo)=split(/_/,$record{"resource.$_.solved"},2); - $status = 'nothing' if ($status eq ''); - $partstatus{$_} = $status; - $partstatus{"resource.$_.submitted_by"} = $record{"resource.$_.submitted_by"} - if ($record{"resource.$_.submitted_by"} ne ''); + if ($symb eq '') { + if (!$silent) { + $request->print("Unable to handle ambiguous references:$url:."); + return (); + } } - return %partstatus; + return ($symb,$url); } +# --- Retrieve the fullname for a user. Return lastname, first middle --- +# --- Generation is attached next to the lastname if it exists. --- sub get_fullname { my ($uname,$udom) = @_; my %name=&Apache::lonnet::get('environment', ['lastname','generation', - 'firstname','middlename'],$udom,$uname); + 'firstname','middlename'], + $udom,$uname); my $fullname; my ($tmp) = keys(%name); if ($tmp !~ /^(con_lost|error|no_such_host)/i) { - $fullname=$name{'lastname'}.$name{'generation'}; - if ($fullname =~ /[^\s]+/) { $fullname.=', '; } - $fullname.=$name{'firstname'}.' '.$name{'middlename'}; + $fullname = &Apache::loncoursedata::ProcessFullName + (@name{qw/lastname generation firstname middlename/}); + } else { + &Apache::lonnet::logthis('grades.pm: no name data for '.$uname. + '@'.$udom.':'.$tmp); } return $fullname; } +#--- 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 ($url) = shift; + my ($url,$symb) = shift; + $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))) if ($symb eq ''); my $allkeys = &Apache::lonnet::metadata($url,'keys'); -# print "allkeys=>$allkeys
"; + my %vPart; + foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { + $vPart{$partid}=1; + } my %seen = (); - my (@partlist,%handgrade); + my (@partlist,%handgrade,%responseType); foreach (split(/,/,&Apache::lonnet::metadata($url,'packages'))) { - if (/^\w+response_\d+.*/) { + if (/^\w+response_.*/) { my ($responsetype,$part) = split(/_/,$_,2); my ($partid,$respid) = split(/_/,$part); - $handgrade{$part} = $responsetype.':'.($allkeys =~ /parameter_$part\_handgrade/ ? 'yes' : 'no'); + if (&Apache::loncommon::check_if_partid_hidden($partid,$symb)) { + next; + } + if (%vPart && !exists($vPart{$partid})) { + next; + } + $responsetype =~ s/response$//; # make it compatible w/ navmaps - should move to that!! + my ($value) = &Apache::lonnet::EXT('resource.'.$part.'.handgrade',$symb); + $handgrade{$part} = ($value eq 'yes' ? 'yes' : 'no'); + if (!exists($responseType{$partid})) { $responseType{$partid}={}; } + $responseType{$partid}->{$respid}=$responsetype; next if ($seen{$partid} > 0); $seen{$partid}++; push @partlist,$partid; } } - return \@partlist,\%handgrade; + return \@partlist,\%handgrade,\%responseType; } +sub get_display_part { + my ($partID,$url,$symb)=@_; + if (!defined($symb) || $symb eq '') { + $symb=$ENV{'form.symb'}; + if ($symb eq '') { $symb=&Apache::lonnet::symbread($url) } + } + 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 ($url,$probTitle,$checkboxes) = @_; + my $col=3; + if ($checkboxes) { $col=4; } + my $result =''. + ''."\n"; + my ($partlist,$handgrade,$responseType) = &response_type($url); + my %resptype = (); + my $hdgrade='no'; + my %partsseen; + for my $part_resID (sort keys(%$handgrade)) { + my $handgrade=$$handgrade{$part_resID}; + my ($partID,$resID) = split(/_/,$part_resID); + my $responsetype = $responseType->{$partID}->{$resID}; + $hdgrade = $handgrade if ($handgrade eq 'yes'); + $result.=''; + if ($checkboxes) { + if (exists($partsseen{$partID})) { + $result.=""; + } else { + $result.=""; + } + $partsseen{$partID}=1; + } + my $display_part=&get_display_part($partID,$url); + $result.=''. + ''; +# ''; + } + $result.='
'.&mt('Current Resource').': '. + $probTitle.'
 Part: '.$display_part.' '. + $resID.'Type: '.$responsetype.'
Handgrade: '.$handgrade.'
'."\n"; + return $result,$responseType,$hdgrade,$partlist,$handgrade; +} + + +sub get_order { + my ($partid,$respid,$symb,$uname,$udom)=@_; + my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); + $url=&Apache::lonnet::clutter($url); + my $subresult=&Apache::lonnet::ssi($url, + ('grade_target' => 'analyze'), + ('grade_domain' => $udom), + ('grade_symb' => $symb), + ('grade_courseid' => + $ENV{'request.course.id'}), + ('grade_username' => $uname)); + (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); + my %analyze=&Apache::lonnet::str2hash($subresult); + return ($analyze{"$partid.$respid.shown"}); +} +#--- Clean response type for display +#--- Currently filters option/rank/radiobutton/match/essay response types only. +sub cleanRecord { + my ($answer,$response,$symb,$partid,$respid,$record,$order,$version) = @_; + my $grayFont = ''; + if ($response =~ /^(option|rank)$/) { + my %answer=&Apache::lonnet::str2hash($answer); + my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); + my ($toprow,$bottomrow); + foreach my $foil (@$order) { + if ($grading{$foil} == 1) { + $toprow.=''.$answer{$foil}.' '; + } else { + $toprow.=''.$answer{$foil}.' '; + } + $bottomrow.=''.$grayFont.$foil.' '; + } + return '
'. + ''.$toprow.''. + ''. + $grayFont.$bottomrow.''.'
Answer
'.$grayFont.'Option ID
'; + } elsif ($response eq 'match') { + my %answer=&Apache::lonnet::str2hash($answer); + my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"}); + my @items=&Apache::lonnet::str2array($record->{$version."resource.$partid.$respid.submissionitems"}); + my ($toprow,$middlerow,$bottomrow); + foreach my $foil (@$order) { + my $item=shift(@items); + if ($grading{$foil} == 1) { + $toprow.=''.$item.' '; + $middlerow.=''.$grayFont.$answer{$foil}.' '; + } else { + $toprow.=''.$item.' '; + $middlerow.=''.$grayFont.$answer{$foil}.' '; + } + $bottomrow.=''.$grayFont.$foil.' '; + } + return '
'. + ''.$toprow.''. + ''. + $middlerow.''. + ''. + $bottomrow.''.'
Answer
'.$grayFont.'Item ID
'.$grayFont.'Option ID
'; + } elsif ($response eq 'radiobutton') { + my %answer=&Apache::lonnet::str2hash($answer); + my ($toprow,$bottomrow); + my $correct=($order->[0])+1; + for (my $i=1;$i<=$#$order;$i++) { + my $foil=$order->[$i]; + if (exists($answer{$foil})) { + if ($i == $correct) { + $toprow.='true'; + } else { + $toprow.='true'; + } + } else { + $toprow.='false'; + } + $bottomrow.=''.$grayFont.$foil.' '; + } + return '
'. + ''.$toprow.''. + ''. + $grayFont.$bottomrow.''.'
Answer
'.$grayFont.'Option ID
'; + } elsif ($response eq 'essay') { + if (! exists ($ENV{'form.'.$symb})) { + my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade', + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); + + my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; + $ENV{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; + $ENV{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; + $ENV{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; + $ENV{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; + $ENV{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob. + } + $answer =~ s-\n-
-g; + return '

'.&keywords_highlight($answer).'
'; + } + 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) = @_; + $getsec = $getsec eq '' ? 'all' : $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 eq 'all' || $getsec eq $section) { + $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 ($url,$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 ($url,$symb) = @_; + my $jscript=''."\n"; + $jscript.= '
'."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '
'."\n"; + return $jscript; +} + +#------------------ 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 +# +#--- 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 $url = $ENV{'form.url'}; + my $symb = $ENV{'form.symb'}; + unless ($symb) { + $symb = &Apache::lonnet::symbread($url); + } + + my $title.='

Verifying Submission Receipt '. + $receipt.'

'."\n". + 'Resource: '.$ENV{'form.probTitle'}.'

'."\n"; + + my ($string,$contents,$matches) = ('','',0); + my (undef,undef,$fullname) = &getclasslist('all','0'); + + my $receiptparts=0; + if ($ENV{"course.$courseid.receiptalg"} eq 'receipt2') { $receiptparts=1; } + my $parts=['0']; + if ($receiptparts) { ($parts)=&response_type($url,$symb); } + foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { + my ($uname,$udom)=split(/\:/); + foreach my $part (@$parts) { + if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) { + $contents.=' '."\n". + ''.$$fullname{$_}.' '."\n". + ' '.$uname.' '. + ' '.$udom.' '; + if ($receiptparts) { + $contents.=' '.$part.' '; + } + $contents.=''."\n"; + + $matches++; + } + } + } + if ($matches == 0) { + $string = $title.'No match found for the above receipt.'; + } else { + $string = &jscriptNform($url,$symb).$title. + 'The above receipt matches the following student'. + ($matches <= 1 ? '.' : 's.')."\n". + '
'."\n". + ''."\n". + ''."\n". + ''."\n". + ''; + if ($receiptparts) { + $string.=''; + } + $string.=''."\n".$contents. + '
 Fullname  Username  Domain  Problem Part 
'."\n"; + } + return $string.&show_grading_menu_form($symb,$url); +} + +#--- This is called by a number of programs. +#--- Called from the Grading Menu - View/Grade an individual student +#--- Also called directly when one clicks on the subm button +# on the problem page. sub listStudents { my ($request) = shift; - my $cdom =$ENV{"course.$ENV{'request.course.id'}.domain"}; - my $cnum =$ENV{"course.$ENV{'request.course.id'}.num"}; - my $getsec =$ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; - my $submitonly=$ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'}; - my $result='

 View Submissions for a Student or a Group of Students

'; - $result.=''; - $result.=''; - my ($partlist,$handgrade) = &response_type($ENV{'form.url'}); - for (sort keys(%$handgrade)) { - my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_}); - $ENV{'form.handgrade'} = 'yes' if ($handgrade eq 'yes'); - $result.=''. - ''. - ''; + my ($symb,$url) = &get_symb_and_url($request); + my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; + my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; + my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; + my $submitonly= $ENV{'form.submitonly'} eq '' ? 'all' : $ENV{'form.submitonly'}; + + my $viewgrade = $ENV{'form.showgrading'} eq 'yes' ? 'View/Grade/Regrade' : 'View'; + $ENV{'form.probTitle'} = $ENV{'form.probTitle'} eq '' ? + &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'}; + + my $result='

 '.$viewgrade. + ' Submissions for a Student or a Group of Students

'; + + my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($url,$ENV{'form.probTitle'},($ENV{'form.showgrading'} eq 'yes')); + + $request->print(< + function checkSelect(checkBox) { + var ctr=0; + var sense=""; + if (checkBox.length > 1) { + for (var i=0; iprint($result); - $request->print(<View Problem: no - yes
Submissions: - handgrade only - last sub only - last sub & parts info - all details - - - -
-
- -ENDTABLEST - if ($ENV{'form.url'}) { - $request->print(''."\n"); + function reLoadList(formname) { + if (formname.saveStatusOld.value == pullDownSelection(formname.Status)) {return;} + formname.command.value = 'submission'; + formname.submit(); } - if ($ENV{'form.symb'}) { - $request->print(''."\n"); + +LISTJAVASCRIPT + + &commonJSfunctions($request); + $request->print($result); + + my $checkhdgrade = ($ENV{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked' : ''; + my $checklastsub = $checkhdgrade eq '' ? 'checked' : ''; + my $gradeTable='
'. + "\n".$table. + ' View Problem Text: no '."\n". + ' one student '."\n". + ' all students
'."\n". + ' View Answer: no '."\n". + ' one student '."\n". + ' all students
'."\n". + ' Submissions: '."\n"; + if ($ENV{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { + $gradeTable.=' essay part only'."\n"; + } + + my $saveStatus = $ENV{'form.Status'} eq '' ? 'Active' : $ENV{'form.Status'}; + $ENV{'form.Status'} = $saveStatus; + + $gradeTable.=' last submission only'."\n". + ' last submission & parts info'."\n". + ' by dates and submissions'."\n". + ' all details'."\n". + ''."\n". + ''."\n". + '
'."\n". + '
'."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n"; + + if (exists($ENV{'form.gradingMenu'}) && exists($ENV{'form.Status'})) { + $gradeTable.=''."\n"; + } else { + $gradeTable.='Student Status: '. + &Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,'javascript:reLoadList(this.form);').'
'; } - $request->print(''."\n"); - - my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($getsec,'0'); - - $result='
Resource: '.$ENV{'form.url'}.'
Part id: '.(split(/_/))[0].'Type: '.$responsetype.'Handgrade: '.$handgrade.'
'. - ''. - ''. - ''; - foreach (sort(@$partlist)) { - $result.=''; + + $gradeTable.='To '.lc($viewgrade).' a submission or a group of submissions, click on the check box(es) '. + 'next to the student\'s name(s). Then click on the Next button.
'."\n". + ''."\n"; + $gradeTable.=''."\n"; + $gradeTable.='Check For Plagiarism'; + my (undef, undef, $fullname) = &getclasslist($getsec,'1'); + $gradeTable.='
 Select  Username  Fullname  Domain  Part ID '.(split(/_/))[0].' Status 
'. + ''; + my $loop = 0; + while ($loop < 2) { + $gradeTable.=''. + ''; + if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { + foreach (sort(@$partlist)) { + my $display_part=&get_display_part((split(/_/))[0],$url,$symb); + $gradeTable.=''; + } + } + $loop++; +# $gradeTable.='' if ($loop%2 ==1); } - $request->print($result.''."\n"); + $gradeTable.=''."\n"; - foreach my $student (sort(@{ $$classlist{$getsec} }) ) { + my $ctr = 0; + foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { my ($uname,$udom) = split(/:/,$student); - my (%status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname,$partlist); - my $statusflg = ''; - foreach (keys(%status)) { - $statusflg = 1 if ($status{$_} ne 'nothing'); - my ($foo,$partid,$foo1) = split(/\./,$_); - if ($status{'resource.'.$partid.'.submitted_by'} ne '') { - $statusflg = ''; - $request->print(''); + my %status = (); + if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { + (%status) =&student_gradeStatus($url,$symb,$udom,$uname,$partlist); + my $submitted = 0; + my $graded = 0; + foreach (keys(%status)) { + $submitted = 1 if ($status{$_} ne 'nothing'); + $graded = 1 if ($status{$_} !~ /^correct/); + + my ($foo,$partid,$foo1) = split(/\./,$_); + if ($status{'resource.'.$partid.'.submitted_by'} ne '') { + $submitted = 0; + my ($part)=split(/\./,$partid); + $gradeTable.=''; + } } + next if (!$submitted && ($submitonly eq 'yes' || + $submitonly eq 'incorrect' || + $submitonly eq 'graded')); + next if (!$graded && ($submitonly eq 'graded' || + $submitonly eq 'incorrect')); } - next if ($statusflg eq '' && $submitonly eq 'yes'); - if ( $Apache::grades::viewgrades eq 'F' ) { - $result=''. + $ctr++; + if ( $perm{'vgr'} eq 'F' ) { + $gradeTable.='' if ($ctr%2 ==1); + $gradeTable.=''. ''."\n". - ''."\n". - ''."\n". - ''."\n"; - - foreach (sort keys(%status)) { - next if (/^resource.*?submitted_by$/); - $result.=''."\n"; + $student.':'.$$fullname{$student}.' ">'."\n". + ''."\n"; + + if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { + foreach (sort keys(%status)) { + next if (/^resource.*?submitted_by$/); + $gradeTable.=''."\n"; + } + } +# $gradeTable.='' if ($ctr%2 ==1); + $gradeTable.=''."\n" if ($ctr%2 ==0); + } + } + if ($ctr%2 ==1) { + $gradeTable.=''; + if ($ENV{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { + foreach (@$partlist) { + $gradeTable.=''; + } } - $request->print($result.''."\n"); + $gradeTable.=''; + } + + $gradeTable.='
 No.  Select '.&nameUserString('header').' Part: '.$display_part. + ' Status 
'.$ctr.'  '.$uname.'  '.$$fullname{$student}.'  '.$udom.'  '.$status{$_}.' '.&nameUserString(undef,$$fullname{$student},$uname,$udom).' '.$status{$_}.' 
    
'. + ''."\n"; + if ($ctr == 0) { + my $num_students=(scalar(keys(%$fullname))); + if ($num_students eq 0) { + $gradeTable='
 There are no students currently enrolled.'; + } else { + my $submissions='submissions'; + if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; } + if ($submitonly eq 'graded' ) { $submissions = 'ungraded submissions'; } + $gradeTable='
 '. + 'No '.$submissions.' found for this resource for any students. ('.$num_students. + ' students checked for '.$submissions.')
'; } + } elsif ($ctr == 1) { + $gradeTable =~ s/type=checkbox/type=checkbox checked/; } - $request->print('
'); - $request->print('
'); + $gradeTable.=&show_grading_menu_form($symb,$url); + $request->print($gradeTable); + return ''; } +#---- Called from the listStudents routine +# Displays the submissions for one student or a group of students sub processGroup { my ($request) = shift; my $ctr = 0; - my @stuchecked = (ref($ENV{'form.stuinfo'}) ? @{$ENV{'form.stuinfo'}} - : ($ENV{'form.stuinfo'})); + my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo'); my $total = scalar(@stuchecked)-1; - if ($stuchecked[0] eq '') { - &userError($request,'No student was selected for viewing/grading.'); - return; - } + foreach (@stuchecked) { my ($uname,$udom,$fullname) = split(/:/); - $ENV{'form.student'} = $uname; - $ENV{'form.fullname'} = $fullname; + $ENV{'form.student'} = $uname; + $ENV{'form.userdom'} = $udom; + $ENV{'form.fullname'} = $fullname; &submission($request,$ctr,$total); $ctr++; } return ''; } -sub userError { - my ($request, $reason, $step) = @_; - $request->print('

LON-CAPA User Error


'."\n"); - $request->print('Reason: '.$reason.'

'."\n"); - $request->print('Step: '.($step ne '' ? $step : 'Use your browser back button to correct') - .'

'."\n"); - return ''; -} +#------------------------------------------------------------------------------------ +# +#-------------------------- Next few routines handles grading by student, essentially +# handles essay response type problem/part +# +#--- Javascript to handle the submission page functionality --- +sub sub_page_js { + my $request = shift; + $request->print(< + function updateRadio(formname,id,weight) { + var gradeBox = formname["GD_BOX"+id]; + var radioButton = formname["RADVAL"+id]; + var oldpts = formname["oldpts"+id].value; + var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts; + gradeBox.value = pts; + var resetbox = false; + if (isNaN(pts) || pts < 0) { + alert("A number equal or greater than 0 is expected. Entered value = "+pts); + for (var i=0; i weight) { + var resp = confirm("You entered a value ("+pts+ + ") greater than the weight for the part. Accept?"); + if (resp == false) { + gradeBox.value = oldpts; + return; + } } - return ($name,$domain); - } else { - return ($ENV{'user.name'},$ENV{'user.domain'}); + + for (var i=0; i +SUBJAVASCRIPT } -sub print_hash { - my ($request, $hash) = @_; - $request->print(''); - for (sort keys (%$hash)) { - $request->print(''); +#--- javascript for essay type problem -- +sub sub_page_kw_js { + my $request = shift; + my $iconpath = $request->dir_config('lonIconsURL'); + &commonJSfunctions($request); + my $nothing= &Apache::lonhtmlcommon::javascript_nothing(); + $request->print(< + +//===================== Show list of keywords ==================== + function keywords(formname) { + var nret = prompt("Keywords list, separated by a space. Add/delete to list if desired.",formname.keywords.value); + if (nret==null) return; + formname.keywords.value = nret; + + if (formname.keywords.value != "") { + formname.refresh.value = "on"; + formname.submit(); } - $request->print('
KeyValue
'.$_.''.$$hash{$_}.' 
'); - return ''; + return; + } + +//===================== Script to view submitted by ================== + function viewSubmitter(submitter) { + document.SCORE.refresh.value = "on"; + document.SCORE.NCT.value = "1"; + document.SCORE.unamedom0.value = submitter; + document.SCORE.submit(); + return; + } + +//===================== Script to add keyword(s) ================== + function getSel() { + if (document.getSelection) txt = document.getSelection(); + else if (document.selection) txt = document.selection.createRange().text; + else return; + var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," "); + if (cleantxt=="") { + alert("Please select a word or group of words from document and then click this link."); + return; + } + var nret = prompt("Add selection to keyword list? Edit if desired.",cleantxt); + if (nret==null) return; + document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret; + if (document.SCORE.keywords.value != "") { + document.SCORE.refresh.value = "on"; + document.SCORE.submit(); + } + return; + } + +//====================== Script for composing message ============== + // preload images + img1 = new Image(); + img1.src = "$iconpath/mailbkgrd.gif"; + img2 = new Image(); + img2.src = "$iconpath/mailto.gif"; + + function msgCenter(msgform,usrctr,fullname) { + var Nmsg = msgform.savemsgN.value; + savedMsgHeader(Nmsg,usrctr,fullname); + var subject = msgform.msgsub.value; + var msgchk = document.SCORE["includemsg"+usrctr].value; + re = /msgsub/; + var shwsel = ""; + if (re.test(msgchk)) { shwsel = "checked" } + subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject); + displaySubject(checkEntities(subject),shwsel); + for (var i=1; i<=Nmsg; i++) { + var testmsg = "savemsg"+i+","; + re = new RegExp(testmsg,"g"); + shwsel = ""; + if (re.test(msgchk)) { shwsel = "checked" } + var message = document.SCORE["savemsg"+i].value; + message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message); + displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages, + //any < is already converted to <, etc. However, only once!! + } + newmsg = document.SCORE["newmsg"+usrctr].value; + shwsel = ""; + re = /newmsg/; + if (re.test(msgchk)) { shwsel = "checked" } + newMsg(newmsg,shwsel); + msgTail(); + return; + } + + function checkEntities(strx) { + if (strx.length == 0) return strx; + var orgStr = ["&", "<", ">", '"']; + var newStr = ["&", "<", ">", """]; + var counter = 0; + while (counter < 4) { + strx = strReplace(strx,orgStr[counter],newStr[counter]); + counter++; + } + return strx; + } + + function strReplace(strx, orgStr, newStr) { + return strx.split(orgStr).join(newStr); + } + + function savedMsgHeader(Nmsg,usrctr,fullname) { + var height = 70*Nmsg+250; + var scrollbar = "no"; + if (height > 600) { + height = 600; + scrollbar = "yes"; + } + var xpos = (screen.width-600)/2; + xpos = (xpos < 0) ? '0' : xpos; + var ypos = (screen.height-height)/2-30; + ypos = (ypos < 0) ? '0' : ypos; + + pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars='+scrollbar+',screenx='+xpos+',screeny='+ypos+',width=600,height='+height); + pWin.focus(); + pDoc = pWin.document; + pDoc.open($nothing,'replace'); + pDoc.write(""); + pDoc.write("Message Central"); + + pDoc.write(" +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 $checkIcon = ''; + + my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname); + my $wgtmsg = ($wgt > 0 ? '(problem weight)' : + 'problem weight assigned by computer'); + $wgt = ($wgt > 0 ? $wgt : '1'); + my $score = ($$record{'resource.'.$partid.'.awarded'} eq '' ? + '' : $$record{'resource.'.$partid.'.awarded'}*$wgt); + my $result=''."\n"; + + my $display_part=&get_display_part($partid,undef,$symb); + $result.=''."\n"; + $result.=''."\n"; + $result.='
'. + 'Part: '.$display_part.' Points: '."\n"; + + my $ctr = 0; + $result.=''."\n"; # display radio buttons in a nice table 10 across + while ($ctr<=$wgt) { + $result.= '\n"; + $result.=(($ctr+1)%10 == 0 ? '' : ''); + $ctr++; + } + $result.='
'.$ctr."
'; + + $result.='
 or /'.$wgt.' '.$wgtmsg. + ($$record{'resource.'.$partid.'.solved'} eq 'correct_by_student' ? ' '.$checkIcon : ''). + ' '."\n"; + + $result.=''."\n"; + $result.="  \n"; + $result.=''."\n". + ''."\n". + ''."\n"; + $result.='
'."\n"; + return $result; +} + +sub show_problem { + my ($request,$symb,$uname,$udom,$removeform,$viewon,$mode) = @_; + my $rendered; + if ($mode eq 'both' or $mode eq 'text') { + $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom, + $ENV{'request.course.id'}); + } + if ($removeform) { + $rendered=~s|||g; + $rendered=~s|||g; + $rendered=~s|name="submit"|name="would_have_been_submit"|g; + } + my $companswer; + if ($mode eq 'both' or $mode eq 'answer') { + $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom, + $ENV{'request.course.id'}); + } + if ($removeform) { + $companswer=~s|||g; + $companswer=~s|||g; + $companswer=~s|name="submit"|name="would_have_been_submit"|g; + } + my $result.='
'; + $result.=''; + if ($viewon) { + $result.=''; + } + if ($mode eq 'both') { + $result.='
'; + if ($mode eq 'both' or $mode eq 'text') { + $result.='View of the problem - '; + } else { + $result.='Correct answer: '; + } + $result.=$ENV{'form.fullname'}.'
'.$rendered.'
'; + $result.='Correct answer:
'.$companswer; + } elsif ($mode eq 'text') { + $result.='
'.$rendered; + } elsif ($mode eq 'answer') { + $result.='
'.$companswer; + } + $result.='
'; + $result.='

'; + return $result; +} + +# --------------------------- show submissions of a student, option to grade sub submission { my ($request,$counter,$total) = @_; (my $url=$ENV{'form.url'})=~s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; - if ($ENV{'form.student'} eq '') { &moreinfo($request,'Need student login id'); return ''; } - my ($uname,$udom) = &finduser($ENV{'form.student'}); - if ($uname eq '') { &moreinfo($request,'Unable to find student'); return ''; } + my ($uname,$udom) = ($ENV{'form.student'},$ENV{'form.userdom'}); + $udom = ($udom eq '' ? $ENV{'user.domain'} : $udom); #has form.userdom changed for a student? + my $usec = &Apache::lonnet::getsection($udom,$uname,$ENV{'request.course.id'}); + $ENV{'form.fullname'} = &get_fullname ($uname,$udom) if $ENV{'form.fullname'} eq ''; my $symb=($ENV{'form.symb'} ne '' ? $ENV{'form.symb'} : (&Apache::lonnet::symbread($url))); if ($symb eq '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; } + + if (!&canview($usec)) { + $request->print('Unable to view requested student.('. + $uname.$udom.$usec.$ENV{'request.course.id'}.')'); + $request->print(&show_grading_menu_form($symb,$url)); + return; + } + + if (!$ENV{'form.lastSub'}) { $ENV{'form.lastSub'} = 'datesub'; } + if (!$ENV{'form.vProb'}) { $ENV{'form.vProb'} = 'yes'; } + if (!$ENV{'form.vAns'}) { $ENV{'form.vAns'} = 'yes'; } my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : ''); - $ENV{'form.vProb'} = $ENV{'form.vProb'} ne '' ? $ENV{'form.vProb'} : 'yes'; - my ($classlist,$seclist,$ids,$stusec,$fullname); + my $checkIcon = ''; # header info if ($counter == 0) { &sub_page_js($request); - $request->print('

 Submission Record

'. - ' Resource: '.$url.''); - - # option to display problem, only once else it cause problems with the form later - # since the problem has a form. - if ($ENV{'form.vProb'} eq 'yes') { - my $rendered=&Apache::loncommon::get_student_view($symb,$uname,$udom, - $ENV{'request.course.id'}); - my $companswer=&Apache::loncommon::get_student_answers($symb,$uname,$udom, - $ENV{'request.course.id'}); - my $result.='
'; - $result.='
'; - $result.='Student\'s view of the problem
'.$rendered.'
'; - $result.='Correct answer:
'.$companswer; - $result.='
'; - $result.='

'; - $request->print($result); + &sub_page_kw_js($request) if ($ENV{'form.handgrade'} eq 'yes'); + $ENV{'form.probTitle'} = $ENV{'form.probTitle'} eq '' ? + &Apache::lonnet::gettitle($symb) : $ENV{'form.probTitle'}; + + $request->print('

 Submission Record

'."\n". + ' Resource: '.$ENV{'form.probTitle'}.''."\n"); + + if ($ENV{'form.handgrade'} eq 'no') { + my $checkMark='

 Note: Part(s) graded correct by the computer is marked with a '. + $checkIcon.' symbol.'."\n"; + $request->print($checkMark); + } + + # option to display problem, only once else it cause problems + # with the form later since the problem has a form. + if ($ENV{'form.vProb'} eq 'yes' or $ENV{'form.vAns'} eq 'yes') { + my $mode; + if ($ENV{'form.vProb'} eq 'yes' && $ENV{'form.vAns'} eq 'yes') { + $mode='both'; + } elsif ($ENV{'form.vProb'} eq 'yes') { + $mode='text'; + } elsif ($ENV{'form.vAns'} eq 'yes') { + $mode='answer'; + } + $request->print(&show_problem($request,$symb,$uname,$udom,0,1,$mode)); } - # kwclr is the only variable that is guaranteed to be non blank if this subroutine has been called once. + # kwclr is the only variable that is guaranteed to be non blank + # if this subroutine has been called once. my %keyhash = (); - if ($ENV{'form.kwclr'} eq '') { + if ($ENV{'form.kwclr'} eq '' && $ENV{'form.handgrade'} eq 'yes') { %keyhash = &Apache::lonnet::dump('nohist_handgrade', $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); @@ -381,267 +1468,382 @@ sub submission { $ENV{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; $ENV{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; $ENV{'form.msgsub'} = $keyhash{$symb.'_subject'} ne '' ? - $keyhash{$symb.'_subject'} : &Apache::lonnet::metadata($url,'title'); + $keyhash{$symb.'_subject'} : $ENV{'form.probTitle'}; $ENV{'form.savemsgN'} = $keyhash{$symb.'_savemsgN'} ne '' ? $keyhash{$symb.'_savemsgN'} : '0'; - } + my $overRideScore = $ENV{'form.overRideScore'} eq '' ? 'no' : $ENV{'form.overRideScore'}; + $request->print('
'."\n". ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". ''."\n". + ''."\n". + ''."\n". ''."\n". ''."\n". ''."\n". ''."\n". + ''."\n". ''."\n". ''."\n". ''."\n". - ''."\n". ''."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". ''."\n"); + if ($ENV{'form.handgrade'} eq 'yes') { + $request->print(''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n"); + foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) { + $request->print(''."\n"); + } + } my ($cts,$prnmsg) = (1,''); while ($cts <= $ENV{'form.savemsgN'}) { $prnmsg.=''."\n"; + (!exists($keyhash{$symb.'_savemsg'.$cts}) ? + &Apache::lonfeedback::clear_out_html($ENV{'form.savemsg'.$cts}) : + &Apache::lonfeedback::clear_out_html($keyhash{$symb.'_savemsg'.$cts})). + '" />'."\n". + ''."\n"; $cts++; } $request->print($prnmsg); if ($ENV{'form.handgrade'} eq 'yes' && $ENV{'form.showgrading'} eq 'yes') { +# +# Print out the keyword options line +# $request->print(<Keyword Options:  -List    +List    Paste Selection to List    Highlight Attribute

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); } } + + if ($ENV{'form.vProb'} eq 'all' or $ENV{'form.vAns'} eq 'all') { + $request->print('


') if ($counter > 0); + my $mode; + if ($ENV{'form.vProb'} eq 'all' && $ENV{'form.vAns'} eq 'all') { + $mode='both'; + } elsif ($ENV{'form.vProb'} eq 'all' ) { + $mode='text'; + } elsif ($ENV{'form.vAns'} eq 'all') { + $mode='answer'; + } + $request->print(&show_problem($request,$symb,$uname,$udom,1,1,$mode)); + } + my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); - my ($partlist,$handgrade) = &response_type($url); -# &print_hash($request,\%record); - # Student info + my ($partlist,$handgrade,$responseType) = &response_type($url,$symb); + + # Display student info $request->print(($counter == 0 ? '' : '
')); -# my $fullname = ($ENV{'form.fullname'} ne '' ? $ENV{'form.fullname'} : &get_fullname($uname,$udom)); - my $result='
'. - '
'; - - $result.=''; - if ($$timestamp eq '') { - $lastsubonly.=''; - } else { - for my $part (sort keys(%$handgrade)) { - foreach (@$string) { - my ($partid,$respid) = /^resource\.(\d+)\.(\d+)\.submission/; - if ($part eq ($partid.'_'.$respid)) { - my ($ressub,$subval) = split(/:/,$_,2); - $lastsubonly.='' - if ($ENV{'form.lastSub'} eq 'lastonly' || - ($ENV{'form.lastSub'} eq 'hdgrade' && $$handgrade{$part} =~ /:yes$/)); + my ($string,$timestamp)= &get_last_submission(\%record); + my $lastsubonly=''. + ($$timestamp eq '' ? '' : 'Date Submitted: '. + $$timestamp)."\n"; + if ($$timestamp eq '') { + $lastsubonly.='
Fullname: '.$ENV{'form.fullname'}. -# $result.=''; + my $result='
Fullname: '.$fullname. - '   Username: '.$uname. - '   Domain: '.$udom.'
'."\n". + ''."\n"; - $result.='' - if (scalar(@badcollaborators) > 0); - - $result.='' if (scalar(@collaborators) > $ncol); - $result.=''."\n"; - } + my $ncol = &Apache::lonnet::EXT('resource.'.$_. + '.maxcollaborators', + $symb,$udom,$uname); + next if ($ncol <= 0); + s/\_/\./g; + next if ($record{'resource.'.$_.'.collaborators'} eq ''); + 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 =~ /^domain$/i); + next if ($co_name eq $uname && $co_dom eq $udom); + # 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; + } + } + if (scalar(@goodcollaborators) != 0) { + $result.='Collaborators: '; + foreach (@goodcollaborators) { + my ($lastname,$givenn) = split(/,/,$$fullname{$_}); + push @col_fullnames, $givenn.' '.$lastname; + $result.=$$fullname{$_}.'     '; } + $result.='
'."\n"; + my ($part)=split(/\./,$_); + $result.=''. + "\n"; + } + if (scalar(@badcollaborators) > 0) { + $result.='
'."\n"; + + $result.='Fullname: '.&nameUserString(undef,$ENV{'form.fullname'},$uname,$udom).'
'."\n"; + $result.=''."\n"; + + # If any part of the problem is an essay-response (handgraded), then check for collaborators + my @col_fullnames; + my ($classlist,$fullname); if ($ENV{'form.handgrade'} eq 'yes') { - my @col_list; - ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist('all','0'); + ($classlist,undef,$fullname) = &getclasslist('all','0'); for (keys (%$handgrade)) { - my $ncol = &Apache::lonnet::EXT('resource.'.$_.'.maxcollaborators',$symb,$udom,$uname); - if ($ncol > 0) { - s/\_/\./g; - if ($record{'resource.'.$_.'.collaborators'} ne '') { - my (@collaborators) = split(/,?\s+/,$record{'resource.'.$_.'.collaborators'}); - my (@badcollaborators); - if (scalar(@collaborators) != 0) { - $result.='
Collaborators: '; - foreach my $collaborator (@collaborators) { - $collaborator = $collaborator =~ /\@|:/ ? - (split(/@|:/,$collaborator))[0] : $collaborator; - next if ($collaborator eq $uname); - if (!grep /^$collaborator:/i,keys %$classlist) { - push @badcollaborators,$collaborator; - next; - } - push @col_list, $collaborator; - $result.=$$fullname{$collaborator.':'.$udom}.' ('.$collaborator.')    '; - } - $result.='
'. - 'This student has submitted '.(scalar (@badcollaborators) > 1 ? '' : 'an'). - ' invalid collaborator'.(scalar (@badcollaborators) > 1 ? 's. ' : '. '). - (join ', ',@badcollaborators).'
'. - 'This student has submitted too many collaborators. Maximum is '. - $ncol.'.
'; + $result.='This student has submitted '; + $result.=(scalar(@badcollaborators) == 1) ? 'an invalid collaborator' : 'invalid collaborators'; + $result .= ': '.join(', ',@badcollaborators); + $result .= '
'; + } + if (scalar(@badcollaborators > $ncol)) { + $result .= '
'; + $result .= 'This student has submitted too many '. + 'collaborators. Maximum is '.$ncol.'.'; + $result .= '
'; } } } - $request->print($result.'
'."\n"); + $request->print($result."\n"); - # print student answer + # print student answer/submission + # Options are (1) Handgaded submission only + # (2) Last submission, includes submission that is not handgraded + # (for multi-response type part) + # (3) Last submission plus the parts info + # (4) The whole record for this student if ($ENV{'form.lastSub'} =~ /^(lastonly|hdgrade)$/) { - if ($ENV{'form.'.$uname.':'.$udom.':submitted_by'}) { - my $submitby='
'. - 'Collaborative submission by: '. - ''. - $$fullname{$ENV{'form.'.$uname.':'.$udom.':submitted_by'}}.''; - $submitby.='
'."\n"; - $request->print($submitby); - } else { - my ($string,$timestamp)=&get_last_submission ($symb,$uname,$udom,$ENV{'request.course.id'}); - my $lastsubonly.='
Last Submission Only'. - ($$timestamp eq '' ? '' : '    Date Submitted: '.$$timestamp).'
'.$$string[0].'
Part ID '. - $partid.' Response ID '.$respid. - ' Submission '.&keywords_highlight($subval).'
'.$$string[0]; + } else { + my %seenparts; + for my $part (sort keys(%$handgrade)) { + my ($partid,$respid) = split(/_/,$part); + my $display_part=&get_display_part($partid,$url,$symb); + if ($ENV{"form.$uname:$udom:$partid:submitted_by"}) { + if (exists($seenparts{$partid})) { next; } + $seenparts{$partid}=1; + my $submitby='Part: '.$display_part. + ' Collaborative submission by: '. + ''. + $$fullname{$ENV{"form.$uname:$udom:$partid:submitted_by"}}.'
'; + $request->print($submitby); + next; + } + my $responsetype = $responseType->{$partid}->{$respid}; + if (!exists($record{"resource.$partid.$respid.submission"})) { + $lastsubonly.='
Part: '. + $display_part.' ( ID '.$respid. + ' )   '. + 'Nothing submitted - no attempts

'; + next; + } + foreach (@$string) { + my ($partid,$respid) = /^resource\.([^\.]*)\.([^\.]*)\.submission/; + if ($part ne ($partid.'_'.$respid)) { next; } + my ($ressub,$subval) = split(/:/,$_,2); + # Similarity check + my $similar=''; + if($ENV{'form.checkPlag'}){ + my ($oname,$odom,$ocrsid,$oessay,$osim)= + &most_similar($uname,$udom,$subval); + if ($osim) { + $osim=int($osim*100.0); + $similar="

Essay". + " is $osim% similar to an essay by ". + &Apache::loncommon::plainname($oname,$odom). + '

'. + &keywords_highlight($oessay). + '

'; } } + my $order=&get_order($partid,$respid,$symb,$uname,$udom); + if ($ENV{'form.lastSub'} eq 'lastonly' || + ($ENV{'form.lastSub'} eq 'hdgrade' && + $$handgrade{$part} eq 'yes')) { + my $display_part=&get_display_part($partid,$url,$symb); + $lastsubonly.='
Part: '. + $display_part.' ( ID '.$respid. + ' )   '; + if ($record{"resource.$partid.$respid.uploadedurl"}) { + &Apache::lonnet::allowuploaded('/adm/grades', + $record{"resource.$partid.$respid.uploadedurl"}); + $lastsubonly.=' File uploaded by student Like all files provided by users, this file may contain virusses
'; + } + $lastsubonly.='Submitted Answer: '. + &cleanRecord($subval,$responsetype,$symb,$partid, + $respid,\%record,$order); + if ($similar) {$lastsubonly.="

$similar\n";} + } } } - $lastsubonly.='
'."\n"; - $request->print($lastsubonly); } - } else { + $lastsubonly.='
'."\n"; + $request->print($lastsubonly); + } elsif ($ENV{'form.lastSub'} eq 'datesub') { + my (undef,$responseType,undef,$parts) = &showResourceInfo($url); + $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom)); + } elsif ($ENV{'form.lastSub'} =~ /^(last|all)$/) { $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom, - $ENV{'request.course.id'},$last, - '.submission','Apache::grades::keywords_highlight')); + $ENV{'request.course.id'}, + $last,'.submission', + 'Apache::grades::keywords_highlight')); } + + $request->print(''."\n"); - # view submission with no grading option - if ($ENV{'form.showgrading'} eq '') { - $request->print('
'); + # return if view submission with no grading option + if ($ENV{'form.showgrading'} eq '' || (!&canmodify($usec))) { + my $toGrade.='  '."\n" if (&canmodify($usec)); + $toGrade.='
'."\n"; + if (($ENV{'form.command'} eq 'submission') || + ($ENV{'form.command'} eq 'processGroup' && $counter == $total)) { + $toGrade.=''.&show_grading_menu_form($symb,$url) + } + $request->print($toGrade); return; + } else { + $request->print(''."\n"); } - $result=''."\n". - ''."\n". - ''."\n"; - $result.=' Compose Message
'."\n" if ($ENV{'form.handgrade'} eq 'yes'); - $request->print($result); + # essay grading message center + if ($ENV{'form.handgrade'} eq 'yes') { + my ($lastname,$givenn) = split(/,/,$ENV{'form.fullname'}); + my $msgfor = $givenn.' '.$lastname; + if (scalar(@col_fullnames) > 0) { + my $lastone = pop @col_fullnames; + $msgfor .= ', '.(join ', ',@col_fullnames).' and '.$lastone.'.'; + } + $msgfor =~ s/\'/\\'/g; #' stupid emacs - no! javascript + $result=''."\n". + ''."\n"; + $result.=' '. + 'Compose Message to student'.(scalar(@col_fullnames) >= 1 ? 's' : '').'  '. + ''."\n". + '
 (Message will be sent when you click on Save & Next below.)'."\n" + if ($ENV{'form.handgrade'} eq 'yes'); + $request->print($result); + } my %seen = (); my @partlist; + my @gradePartRespid; for (sort keys(%$handgrade)) { my ($partid,$respid) = split(/_/); next if ($seen{$partid} > 0); $seen{$partid}++; - next if ($$handgrade{$_} =~ /:no$/); + next if ($$handgrade{$_} =~ /:no$/ && $ENV{'form.lastSub'} =~ /^(hdgrade)$/); push @partlist,$partid; - my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb,$udom,$uname); - my $wgtmsg = ($wgt > 0 ? '(problem weight)' : 'problem weight assigned by computer'); - $wgt = ($wgt > 0 ? $wgt : '1'); - my $score = ($record{'resource.'.$partid.'.awarded'} eq '' ? - '' : $record{'resource.'.$partid.'.awarded'}*$wgt); - - # display grading options - $result=''; - $result.=''; - $result.=''."\n"; - $result.='
Part '.$partid.' Points'; + push @gradePartRespid,$partid.'.'.$respid; - my $ctr = 0; - $result.=''; # display radio buttons in a nice table 10 across - while ($ctr<=$wgt) { - $result.= '\n"; - $result.=(($ctr+1)%10 == 0 ? '' : ''); - $ctr++; - } - $result.='
'.$ctr."
'; - - $result.='
 or /'.$wgt.' '.$wgtmsg.' '; - - $result.=''."  \n"; - $result.=''; - $result.='
'; - $request->print($result); + $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record)); } - $request->print(''."\n"); - $request->print(''."\n"); + $result=''."\n"; + $result.=''."\n" if ($counter == 0); + my $ctr = 0; + while ($ctr < scalar(@partlist)) { + $result.=''."\n"; + $ctr++; + } + $request->print($result.''."\n"); # print end of form if ($counter == $total) { - my $endform.='
'; + my $endform='
'."\n"; + $endform.='  '."\n"; my $ntstu =''."\n"; my $nsel = ($ENV{'form.NTSTU'} ne '' ? $ENV{'form.NTSTU'} : '1'); $ntstu =~ s/
'; + $endform.=$ntstu.'student(s)   '; + $endform.='  '."\n". + '  '; + $endform.='(Next and Previous (student) do not save the scores.)'."\n" ; + $endform.='
'; + $endform.=&show_grading_menu_form($symb,$url); $request->print($endform); } return ''; } +#--- Retrieve the last submission for all the parts sub get_last_submission { - my ($symb,$username,$domain,$course)=@_; - if ($symb) { - my (@string,$timestamp); - my (%returnhash)=&Apache::lonnet::restore($symb,$course,$domain,$username); - if ($returnhash{'version'}) { - my %lasthash=(); - my ($version); - for ($version=1;$version<=$returnhash{'version'};$version++) { - foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) { - $lasthash{$_}=$returnhash{$version.':'.$_}; - } - } - foreach ((keys %lasthash)) { - if ($_ =~ /\.submission$/) {push @string, (join(':',$_,$lasthash{$_}))} - if ($_ =~ /timestamp/) {$timestamp = scalar(localtime($lasthash{$_}))}; + my ($returnhash)=@_; + my (@string,$timestamp); + if ($$returnhash{'version'}) { + my %lasthash=(); + my ($version); + for ($version=1;$version<=$$returnhash{'version'};$version++) { + foreach (sort(split(/\:/,$$returnhash{$version.':keys'}))) { + $lasthash{$_}=$$returnhash{$version.':'.$_}; + $timestamp = scalar(localtime($$returnhash{$version.':timestamp'})); + } + } + foreach ((keys %lasthash)) { + if ($_ =~ /\.submission$/) { + my ($partid,$foo) = split(/submission$/,$_); + my $draft = $lasthash{$partid.'awarddetail'} eq 'DRAFT' ? + 'Draft Copy ' : ''; + push @string, (join(':',$_,$draft.$lasthash{$_})); } } - @string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string; - return \@string,\$timestamp; } + @string = $string[0] eq '' ? 'Nothing submitted - no attempts.' : @string; + return \@string,\$timestamp; } +#--- High light keywords, with style choosen by user. sub keywords_highlight { - my $string = shift; - my $size = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'}; - my $styleon = $ENV{'form.kwstyle'} eq '' ? '' : $ENV{'form.kwstyle'}; + my $string = shift; + my $size = $ENV{'form.kwsize'} eq '0' ? '' : 'size='.$ENV{'form.kwsize'}; + my $styleon = $ENV{'form.kwstyle'} eq '' ? '' : $ENV{'form.kwstyle'}; (my $styleoff = $styleon) =~ s/\$styleon$_$styleoff\<\/font\>/gi; + $string =~ s/\b\Q$_\E(\b|\.)/$styleon$_$styleoff<\/font>/gi; } return $string; } +#--- Called from submission routine sub processHandGrade { my ($request) = shift; my $url = $ENV{'form.url'}; @@ -649,99 +1851,144 @@ sub processHandGrade { my $button = $ENV{'form.gradeOpt'}; my $ngrade = $ENV{'form.NCT'}; my $ntstu = $ENV{'form.NTSTU'}; - - my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; - my %keyhash = (); - $ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g; - $ENV{'form.keywords'} =~ s/^\s+|\s+$//; - $keyhash{$symb.'_keywords'} = $ENV{'form.keywords'}; - $keyhash{$symb.'_subject'} = $ENV{'form.msgsub'}; - $keyhash{$loginuser.'_kwclr'} = $ENV{'form.kwclr'}; - $keyhash{$loginuser.'_kwsize'} = $ENV{'form.kwsize'}; - $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'}; - - my ($ctr,$idx) = (1,1); - while ($ctr <= $ENV{'form.savemsgN'}) { - if ($ENV{'form.savemsg'.$ctr} ne '') { - $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr}; - $idx++; - } - $ctr++; - } - $ctr = 0; - while ($ctr < $ngrade) { - if ($ENV{'form.newmsg'.$ctr} ne '') { - $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; - $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; - $idx++; - } - $ctr++; - } - $ENV{'form.savemsgN'} = --$idx; - $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'}; - my $putresult = &Apache::lonnet::put - ('nohist_handgrade',\%keyhash, - $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, - $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); - - if ($ENV{'form.refresh'} eq 'on') { - my $ctr = 0; - $ENV{'form.NTSTU'}=$ngrade; - while ($ctr < $ngrade) { - ($ENV{'form.student'},my $udom) = split(/:/,$ENV{'form.unamedom'.$ctr}); - &submission($request,$ctr,$ngrade-1); - $ctr++; - } - return ''; - } - if ($button eq 'Save & Next') { my $ctr = 0; while ($ctr < $ngrade) { my ($uname,$udom) = split(/:/,$ENV{'form.unamedom'.$ctr}); - my ($errorflg) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr); - return '' if ($errorflg eq 'error'); - + my ($errorflag,$pts,$wgt) = &saveHandGrade($request,$url,$symb,$uname,$udom,$ctr); + if ($errorflag eq 'no_score') { + $ctr++; + next; + } + if ($errorflag eq 'not_allowed') { + $request->print("Not allowed to modify grades for $uname:$udom"); + $ctr++; + next; + } my $includemsg = $ENV{'form.includemsg'.$ctr}; my ($subject,$message,$msgstatus) = ('','',''); - if ($includemsg =~ /savemsg|new$ctr/) { + if ($includemsg =~ /savemsg|newmsg\Q$ctr\E/) { $subject = $ENV{'form.msgsub'} if ($includemsg =~ /^msgsub/); my (@msgnum) = split(/,/,$includemsg); foreach (@msgnum) { $message.=$ENV{'form.'.$_} if ($_ =~ /savemsg|newmsg/ && $_ ne ''); } - $message =~ s/\s+/ /g; + $message =&Apache::lonfeedback::clear_out_html($message); + $message.="\n\nPoint".($pts > 1 ? 's':'').' awarded = '.$pts.' out of '.$wgt; + $message.=" for $ENV{'form.probTitle'}"; $msgstatus = &Apache::lonmsg::user_normal_msg ($uname,$udom, $ENV{'form.msgsub'},$message); } if ($ENV{'form.collaborator'.$ctr}) { - my (@collaborators) = split(/:/,$ENV{'form.collaborator'.$ctr}); - foreach (@collaborators) { - &saveHandGrade($request,$url,$symb,$_,$udom,$ctr, - $ENV{'form.unamedom'.$ctr}); - if ($message ne '') { - $msgstatus = &Apache::lonmsg::user_normal_msg ($_,$udom, - $ENV{'form.msgsub'}, - $message); + my @collabstrs=&Apache::loncommon::get_env_multiple("form.collaborator$ctr"); + foreach my $collabstr (@collabstrs) { + my ($part,@collaborators) = split(/:/,$collabstr); + foreach (@collaborators) { + my ($errorflag,$pts,$wgt) = + &saveHandGrade($request,$url,$symb,$_,$udom,$ctr, + $ENV{'form.unamedom'.$ctr},$part); + if ($errorflag eq 'not_allowed') { + $request->print("Not allowed to modify grades for $_:$udom"); + next; + } else { + if ($message ne '') { + $msgstatus = &Apache::lonmsg::user_normal_msg($_,$udom,$ENV{'form.msgsub'},$message); + } + + } } } } $ctr++; } } + + if ($ENV{'form.handgrade'} eq 'yes') { + # Keywords sorted in alphabatical order + my $loginuser = $ENV{'user.name'}.':'.$ENV{'user.domain'}; + my %keyhash = (); + $ENV{'form.keywords'} =~ s/,\s{0,}|\s+/ /g; + $ENV{'form.keywords'} =~ s/^\s+|\s+$//; + my (@keywords) = sort(split(/\s+/,$ENV{'form.keywords'})); + $ENV{'form.keywords'} = join(' ',@keywords); + $keyhash{$symb.'_keywords'} = $ENV{'form.keywords'}; + $keyhash{$symb.'_subject'} = $ENV{'form.msgsub'}; + $keyhash{$loginuser.'_kwclr'} = $ENV{'form.kwclr'}; + $keyhash{$loginuser.'_kwsize'} = $ENV{'form.kwsize'}; + $keyhash{$loginuser.'_kwstyle'} = $ENV{'form.kwstyle'}; + + # message center - Order of message gets changed. Blank line is eliminated. + # New messages are saved in ENV for the next student. + # All messages are saved in nohist_handgrade.db + my ($ctr,$idx) = (1,1); + while ($ctr <= $ENV{'form.savemsgN'}) { + if ($ENV{'form.savemsg'.$ctr} ne '') { + $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.savemsg'.$ctr}; + $idx++; + } + $ctr++; + } + $ctr = 0; + while ($ctr < $ngrade) { + if ($ENV{'form.newmsg'.$ctr} ne '') { + $keyhash{$symb.'_savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; + $ENV{'form.savemsg'.$idx} = $ENV{'form.newmsg'.$ctr}; + $idx++; + } + $ctr++; + } + $ENV{'form.savemsgN'} = --$idx; + $keyhash{$symb.'_savemsgN'} = $ENV{'form.savemsgN'}; + my $putresult = &Apache::lonnet::put + ('nohist_handgrade',\%keyhash, + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); + } + # Called by Save & Refresh from Highlight Attribute Window + my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1'); + if ($ENV{'form.refresh'} eq 'on') { + my ($ctr,$total) = (0,0); + while ($ctr < $ngrade) { + $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 ''; + } + +# Go directly to grade student - from submission or link from chart page + if ($button eq 'Grade Student') { + (undef,undef,$ENV{'form.handgrade'},undef,undef) = &showResourceInfo($url); + my $processUser = $ENV{'form.unamedom'.$ENV{'form.studentNo'}}; + ($ENV{'form.student'},$ENV{'form.userdom'}) = split(/:/,$processUser); + $ENV{'form.fullname'} = $$fullname{$processUser}; + &submission($request,0,0); + return ''; + } + + # Get the next/previous one or group of students my $firststu = $ENV{'form.unamedom0'}; my $laststu = $ENV{'form.unamedom'.($ngrade-1)}; - $ctr = 2; + my $ctr = 2; while ($laststu eq '') { $laststu = $ENV{'form.unamedom'.($ngrade-$ctr)}; $ctr++; $laststu = $firststu if ($ctr > $ngrade); } - my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0'); my (@parsedlist,@nextlist); my ($nextflg) = 0; - foreach ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) { + foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { if ($nextflg == 1 && $button =~ /Next$/) { push @parsedlist,$_; } @@ -752,21 +1999,32 @@ sub processHandGrade { } } $ctr = 0; - my ($partlist,$handgrade) = &response_type($ENV{'form.url'}); @parsedlist = reverse @parsedlist if ($button eq 'Previous'); + my ($partlist) = &response_type($url); foreach my $student (@parsedlist) { + my $submitonly=$ENV{'form.submitonly'}; my ($uname,$udom) = split(/:/,$student); - if ($ENV{'form.submitonly'} eq 'yes') { - my (%status) = &student_gradeStatus($ENV{'form.url'},$udom,$uname,$partlist) ; - my $statusflg = ''; + if ($submitonly =~ /^(yes|graded|incorrect)$/) { +# my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$udom,$uname); + my %status=&student_gradeStatus($url,$symb,$udom,$uname,$partlist); + my $submitted = 0; + my $graded = 1; foreach (keys(%status)) { - $statusflg = 1 if ($status{$_} ne 'nothing'); + $submitted = 1 if ($status{$_} ne 'nothing'); + $graded = 0 if ($status{$_} =~ /^correct/); my ($foo,$partid,$foo1) = split(/\./,$_); - $statusflg = '' if ($status{'resource.'.$partid.'.submitted_by'} ne ''); + if ($status{'resource.'.$partid.'.submitted_by'} ne '') { + $submitted = 0; + } } - next if ($statusflg eq ''); + next if (!$submitted && ($submitonly eq 'yes' || + $submitonly eq 'incorrect' || + $submitonly eq 'graded')); + next if (!$graded && ($submitonly eq 'graded' || + $submitonly eq 'incorrect')); } push @nextlist,$student if ($ctr < $ntstu); + last if ($ctr == $ntstu); $ctr++; } @@ -775,10 +2033,9 @@ sub processHandGrade { foreach (sort @nextlist) { my ($uname,$udom,$submitter) = split(/:/); - $ENV{'form.student'} = $uname; + $ENV{'form.student'} = $uname; + $ENV{'form.userdom'} = $udom; $ENV{'form.fullname'} = $$fullname{$_}; -# $ENV{'form.'.$_.':submitted_by'} = $submitter; -# print "submitter=$ENV{'form.'.$_.':submitted_by'}= $submitter:
"; &submission($request,$ctr,$total); $ctr++; } @@ -792,247 +2049,93 @@ sub processHandGrade { return ''; } +#---- Save the score and award for each student, if changed sub saveHandGrade { - my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter) = @_; -# my %record=&Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname); - my %newrecord; + my ($request,$url,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_; + my $usec = &Apache::lonnet::getsection($domain,$stuname, + $ENV{'request.course.id'}); + if (!&canmodify($usec)) { return('not_allowed'); } + my %record = &Apache::lonnet::restore($symb,$ENV{'request.course.id'},$domain,$stuname); + my %newrecord = (); + my ($pts,$wgt) = ('',''); foreach (split(/:/,$ENV{'form.partlist'.$newflg})) { - if ($ENV{'form.GD_SEL'.$newflg.'_'.$_} eq 'excused') { - $newrecord{'resource.'.$_.'.solved'} = 'excused'; - } else { - my $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ? - $ENV{'form.GD_BOX'.$newflg.'_'.$_} : $ENV{'form.RADVAL'.$newflg.'_'.$_}); - if ($pts eq '') { - &userError($request,'No point was assigned for part id '.$_.' and for username '.$stuname.'.'); - return 'error'; + #collaborator may vary for different parts + if ($submitter && $_ ne $part) { next; } + my $dropMenu = $ENV{'form.GD_SEL'.$newflg.'_'.$_}; + if ($dropMenu eq 'excused') { + if ($record{'resource.'.$_.'.solved'} ne 'excused') { + $newrecord{'resource.'.$_.'.solved'} = 'excused'; + if (exists($record{'resource.'.$_.'.awarded'})) { + $newrecord{'resource.'.$_.'.awarded'} = ''; + } + $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; + } + } elsif ($dropMenu eq 'reset status' + && exists($record{'resource.'.$_.'.solved'})) { #don't bother if no old records -> no attempts + foreach my $key (keys (%record)) { + if ($key=~/^resource\.\Q$_\E\./) { $newrecord{$key} = ''; } + } + $newrecord{'resource.'.$_.'.regrader'}= + "$ENV{'user.name'}:$ENV{'user.domain'}"; + } elsif ($dropMenu eq '') { + $pts = ($ENV{'form.GD_BOX'.$newflg.'_'.$_} ne '' ? + $ENV{'form.GD_BOX'.$newflg.'_'.$_} : + $ENV{'form.RADVAL'.$newflg.'_'.$_}); + if ($pts eq '' && $ENV{'form.GD_SEL'.$newflg.'_'.$_} eq '') { + next; } - my $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 : $ENV{'form.WGT'.$newflg.'_'.$_}; + $wgt = $ENV{'form.WGT'.$newflg.'_'.$_} eq '' ? 1 : + $ENV{'form.WGT'.$newflg.'_'.$_}; my $partial= $pts/$wgt; - $newrecord{'resource.'.$_.'.awarded'} = $partial; + if ($partial eq $record{'resource.'.$_.'.awarded'}) { + #do not update score for part if not changed. + next; + } + if ($record{'resource.'.$_.'.awarded'} ne $partial) { + $newrecord{'resource.'.$_.'.awarded'} = $partial; + } + my $reckey = 'resource.'.$_.'.solved'; if ($partial == 0) { - $newrecord{'resource.'.$_.'.solved'} = 'incorrect_by_override'; + if ($record{$reckey} ne 'incorrect_by_override') { + $newrecord{$reckey} = 'incorrect_by_override'; + } } else { - $newrecord{'resource.'.$_.'.solved'} = 'correct_by_override'; + if ($record{$reckey} ne 'correct_by_override') { + $newrecord{$reckey} = 'correct_by_override'; + } + } + if ($submitter && + ($record{'resource.'.$_.'.submitted_by'} ne $submitter)) { + $newrecord{'resource.'.$_.'.submitted_by'} = $submitter; } - $newrecord{'resource.'.$_.'.submitted_by'} = $submitter if ($submitter); + $newrecord{'resource.'.$_.'.regrader'}= + "$ENV{'user.name'}:$ENV{'user.domain'}"; } } - - if ( scalar(keys(%newrecord)) > 0 ) { - $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; -# &print_hash($request,\%newrecord); - &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'},$domain,$stuname); - } - return ''; -} - -sub get_symb_and_url { - my ($request) = @_; - (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 '') { $request->print("Unable to handle ambiguous references:$url:."); return ''; } - return ($symb,$url); -} - -sub show_grading_menu_form { - my ($symb,$url)=@_; - my $result.='
'."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - '
'."\n"; - return $result; -} - -sub gradingmenu { - my ($request) = @_; - my ($symb,$url)=&get_symb_and_url($request); - if (!$symb) {return '';} - my $result='

 Select a Grading Method

'; - $result.=''; - $result.=''; - my ($partlist,$handgrade) = &response_type($url); - my ($resptype,$hdgrade)=('','no'); - for (sort keys(%$handgrade)) { - my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_}); - $resptype = $responsetype; - $hdgrade = $handgrade if ($handgrade eq 'yes'); - $result.=''. - ''. - ''; - } - $result.='
Resource: '.$url.'
Part ID: '.(split(/_/))[0].'Type: '.$responsetype.'Handgrade: '.$handgrade.'
'; - $result.=&view_edit_entire_class_form($symb,$url).'
'; - $result.=&upcsvScores_form($symb,$url).'
'; - $result.=&viewGradeaStu_form($symb,$url,$resptype,$hdgrade).'
'; - $result.=&verifyReceipt_form($symb,$url).'
'; - $result.=&view_classlist_form($symb,$url); - - return $result; -} - -sub view_classlist_form { - my ($symb,$url)=@_; - my $result.='
'."\n"; - $result.=''."\n"; - $result.='
'."\n"; - $result.=' View Class List
'."\n"; - $result.='
'."\n". - ''."\n". - ''."\n". - ''."\n"; - $result.=' 
'."\n"; - $result.='
'."\n"; - $result.='
'."\n"; - return $result; -} - -sub viewclasslist { - my ($request) = shift; - my ($coursedomain,$coursenum) = split(/_/,$ENV{'request.course.id'}); - my %classlist=&Apache::lonnet::dump('classlist',$coursedomain,$coursenum); - $request->print(''); - foreach (sort keys(%classlist)) { -# my ($unam,$udom) = split(/:/,$_,2); -# my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'}); -# my $fullname = &get_fullname ($unam,$udom); -# my @uname; -# $uname[0]=$unam; -# my %userid=&Apache::lonnet::idrget($udom,@uname); -# my $value=$classlist{$_}.':'.$userid{$unam}.':'.$section.':'.$fullname; -# $classlist{$_}=$value; - $request->print(''); - } - $request->print('
'.$_.'
 '.$classlist{$_}.'
'); -# my $putresult = &Apache::lonnet::put -# ('classlist',\%classlist, -# $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, -# $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); - - return ''; -} - -sub view_edit_entire_class_form { - my ($symb,$url)=@_; - my ($classlist,$sections) = &getclasslist('all','0'); - my $result.='
'."\n"; - $result.=''."\n"; - $result.='
'."\n"; - $result.=' View/Grade Entire Section/Class
'."\n"; - $result.='
'."\n". - ''."\n". - ''."\n". - ''."\n"; - $result.=' Select section: '."
\n"; - $result.=' 
'."\n"; - $result.='
'."\n"; - $result.='
'."\n"; - return $result; -} - -sub upcsvScores_form { - my ($symb,$url) = @_; - if (!$symb) {return '';} - my $result.='
'."\n"; - $result.=''."\n"; - $result.='
'."\n"; - $result.=' Specify a file containing the class scores for above resource
'."\n"; - my $upfile_select=&Apache::loncommon::upfile_select_html(); - $result.=< - - - -$upfile_select -
  - -ENDUPFORM - $result.='
'."\n"; - $result.='
'."\n"; - return $result; -} - -sub viewGradeaStu_form { - my ($symb,$url,$response,$handgrade) = @_; - my ($classlist,$sections) = &getclasslist('all','0'); - my $result.='
'."\n"; - $result.=''."\n"; - $result.='
'."\n"; - $result.=' View/Grade an Individual Student\'s Submission
'."\n"; - $result.='
'."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n"; - - $result.=' Select section: '."\n"; - $result.='  Display students who has: '. - ' submitted'. - ' everybody
'; - $result.=' (Section "no" implies the students were not assigned a section.)
' - if (grep /no/,@$sections); - - $result.='
 '."\n". - '
'."\n"; - $result.='
'."\n"; - $result.='
'."\n"; - return $result; -} - -sub verifyReceipt_form { - my ($symb,$url) = @_; - my $cdom=$ENV{"course.$ENV{'request.course.id'}.domain"}; - my $cnum=$ENV{"course.$ENV{'request.course.id'}.num"}; - my $hostver=unpack("%32C*",$Apache::lonnet::perlvar{'lonHostID'}); - - my $result.='
'."\n"; - $result.=''."\n"; - $result.='
'."\n"; - $result.=' Verify a Submission Receipt Issued by this Server
'."\n"; - $result.='
'."\n"; - $result.=' '.$hostver.'-
'."\n"; - $result.=' '."\n"; - $result.=''."\n"; - if ($ENV{'form.url'}) { - $result.=''; - } - if ($ENV{'form.symb'}) { - $result.=''; + if (scalar(keys(%newrecord)) > 0) { + &Apache::lonnet::cstore(\%newrecord,$symb, + $ENV{'request.course.id'},$domain,$stuname); } - $result.='
'; - $result.='
'."\n"; - $result.='
'."\n"; - return $result; + return '',$pts,$wgt; } +#-------------------------------------------------------------------------------------- +# +#-------------------------- Next few routines handles grading by section or whole class +# +#--- Javascript to handle grading by section or whole class sub viewgrades_js { my ($request) = shift; $request->print(< - function viewOneStudent(user) { - document.onestudent.student.value = user; - document.onestudent.submit(); - } - - function writePoint(partid,weight,point) { - var radioButton = eval("document.classgrade.RADVAL_"+partid); - var textbox = eval("document.classgrade.TEXTVAL_"+partid); + function writePoint(partid,weight,point) { + var radioButton = document.classgrade["RADVAL_"+partid]; + var textbox = document.classgrade["TEXTVAL_"+partid]; if (point == "textval") { - var point = eval("document.classgrade.TEXTVAL_"+partid+".value"); - if (isNaN(point) || point < 0) { - alert("A number equal or greater than 0 is expected. Entered value = "+point); + point = document.classgrade["TEXTVAL_"+partid].value; + if (isNaN(point) || parseFloat(point) < 0) { + alert("A number equal or greater than 0 is expected. Entered value = "+parseFloat(point)); var resetbox = false; for (var i=0; i parseFloat(weight)) { + var resp = confirm("You entered a value ("+parseFloat(point)+ + ") greater than the weight for the part. Accept?"); + if (resp == false) { + textbox.value = ""; + return; + } + } for (var i=0; i parseFloat(weight)) { + var resp = confirm("You entered a value ("+parseFloat(point)+ + ") greater than the weight of the part. Accept?"); + if (resp == false) { + textbox.value = ""; + return; + } + } selval[0].selected = true; } function changeOneScore(partid,user) { - var selval = eval("document.classgrade.GD_"+user+'_'+partid+"_sv"); - if (selval[1].selected) { - var boxval = eval("document.classgrade.GD_"+user+'_'+partid+"_aw"); - boxval.value = ""; + var selval = document.classgrade["GD_"+user+'_'+partid+"_solved"]; + if (selval[1].selected || selval[2].selected) { + document.classgrade["GD_"+user+'_'+partid+"_awarded"].value = ""; + if (selval[2].selected) { + document.classgrade["GD_"+user+'_'+partid+"_tries"].value = "0"; + } } } function resetEntry(numpart) { for (ctpart=0;ctpart VIEWJAVASCRIPT } +#--- show scores for a section or whole class w/ option to change/update a score sub viewgrades { my ($request) = shift; &viewgrades_js($request); my ($symb,$url) = ($ENV{'form.symb'},$ENV{'form.url'}); - my $result='

Manual Grading

'; + #need to make sure we have the correct data for later EXT calls, + #thus invalidate the cache + &Apache::lonnet::devalidatecourseresdata( + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}); + &Apache::lonnet::clear_EXT_cache_status(); - $result.='Resource: '.$ENV{'form.url'}.''."\n"; + my $result='

'.&mt('Manual Grading').'

'; + $result.='Current Resource: '.$ENV{'form.probTitle'}.''."\n"; #view individual student submission form - called using Javascript viewOneStudent - $result.= '
'."\n". - ''."\n". - ''."\n". - ''."\n". - ''."\n". - '
'."\n"; + $result.=&jscriptNform($url,$symb); - #start the form + #beginning of class grading form $result.= '
'."\n". ''."\n". ''."\n". ''."\n". - ''."\n"; - - $result.='To assign the same score for all the students use the radio buttons or '. - 'text box below. To assign scores individually fill in the score boxes for '. - 'each student in the table below. A score that has already '. - 'been graded does not get changed using the radio buttons or text box. '. - 'If needed, it has to be changed individually.'; - - my ($partlist,$handgrade) = &response_type($ENV{'form.url'}); + ''."\n". + ''."\n". + ''."\n". + ''."\n"; + + my $sectionClass; + if ($ENV{'form.section'} eq 'all') { + $sectionClass='Class '; + } elsif ($ENV{'form.section'} eq 'none') { + $sectionClass='Students in no Section '; + } else { + $sectionClass='Students in Section '.$ENV{'form.section'}.''; + } + $result.='

Assign Common Grade To '.$sectionClass; + $result.= '
'."\n". + '
'; + #radio buttons/text box for assigning points for a section or class. + #handles different parts of a problem + my ($partlist,$handgrade) = &response_type($url,$symb); my %weight = (); my $ctsparts = 0; $result.=''; + my %seen = (); for (sort keys(%$handgrade)) { - my ($responsetype,$handgrade)=split(/:/,$$handgrade{$_}); - my ($partid,$respid) = split (/_/); + my ($partid,$respid) = split (/_/,$_,2); + next if $seen{$partid}; + $seen{$partid}++; + my $handgrade=$$handgrade{$_}; my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb); $weight{$partid} = $wgt eq '' ? '1' : $wgt; - $result.=''."\n"; - $result.=''."\n"; $result.= ''."\n"; + ''. + ''."\n"; $ctsparts++; } - $result.='
Part ID: '.$partid.'   '; + $result.=''."\n"; + $result.=''."\n"; + my $display_part=&get_display_part($partid,$url,$symb); + $result.='
Part: '.$display_part.'   Point: '; $result.=''; my $ctr = 0; while ($ctr<=$weight{$partid}) { # display radio buttons in a nice table 10 across $result.= '\n"; $result.=(($ctr+1)%10 == 0 ? '' : ''); $ctr++; } $result.='
'.$ctr."
'; - $result.= '
or /'. + $result.= ' or /'. $weight{$partid}.' (problem weight)
'; + $result.='
'.'
'.''."\n". + ''; $result.='    '; -# $result.=''."\n"; - $result.= ''."\n"; + 'onClick="javascript:resetEntry('.$ctsparts.');" TARGET=_self>'; + #table listing all the students in a section/class + #header of table + $result.= '

Assign Grade to Specific Students in '.$sectionClass; $result.= '
'."\n". - ''. - ''."\n"; - #get list of parts for this problem - my (@parts) = sort(&getpartlist($url)); + '
UsernameFullnameDomain
'. + '\n"; + my (@parts) = sort(&getpartlist($url,$symb)); foreach my $part (@parts) { my $display=&Apache::lonnet::metadata($url,$part.'.display'); - next if ($display =~ /^Number of Attempts/); + $display =~ s|^Number of Attempts|Tries
|; # makes the column narrower if (!$display) { $display = &Apache::lonnet::metadata($url,$part.'.name'); } + my ($partid) = &split_part_type($part); + my $display_part=&get_display_part($partid,$url,$symb); if ($display =~ /^Partial Credit Factor/) { - $_ = $display; - my ($partid) = /.*?(\d+).*/; - $result.=''."\n"; + $result.=''."\n"; next; + } else { + $display =~s/\[Part: \Q$partid\E\]/Part:<\/b> $display_part/; } - $display =~ s/Problem Status/Grade Status
/; - $result.=''."\n"; + $display =~ s|Problem Status|Grade Status
|; + $result.=''."\n"; } $result.=''; + #get info for each student - my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0'); + #list all the students - with points and grade status + my (undef,undef,$fullname) = &getclasslist($ENV{'form.section'},'1'); my $ctr = 0; - foreach ( sort(@{ $$classlist{$ENV{'form.section'}} }) ) { - (my $username = $_) = split(/:/); - $result.=''."\n"; - $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'}, - $_,$$fullname{$_},\@parts,\%weight); + foreach (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { + my $uname = $_; + $uname=~s/:/_/; + $result.=''."\n"; $ctr++; + $result.=&viewstudentgrade($url,$symb,$ENV{'request.course.id'}, + $_,$$fullname{$_},\@parts,\%weight,$ctr); } $result.='
 No. '.&nameUserString('header')."Score Part '.$partid.'
(weight = '. - $weight{$partid}.')
Score Part: '.$display_part. + '
(weight = '.$weight{$partid}.')
'.$display.''.$display.'
'; $result.=''."\n"; - $result.=''; + $result.=''."\n"; + if (scalar(%$fullname) eq 0) { + my $colspan=3+scalar(@parts); + $result='There are no students in section "'.$ENV{'form.section'}. + '" with enrollment status "'.$ENV{'form.Status'}.'" to modify or grade.'; + } $result.=&show_grading_menu_form($symb,$url); return $result; } +#--- call by previous routine to display each student sub viewstudentgrade { - my ($url,$symb,$courseid,$student,$fullname,$parts,$weight) = @_; - my ($username,$domain) = split(/:/,$student); - my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$username); - my $result=''. - ''.$username.''. - ''.$fullname.''.$domain.''."\n"; - foreach my $part (@$parts) { - my ($temp,$part,$type)=split(/_/,$part); + my ($url,$symb,$courseid,$student,$fullname,$parts,$weight,$ctr) = @_; + my ($uname,$udom) = split(/:/,$student); + $student=~s/:/_/; + my %record=&Apache::lonnet::restore($symb,$courseid,$udom,$uname); + my $result=''.$ctr.'  '. + ''.$fullname.' '. + '('.$uname.($ENV{'user.domain'} eq $udom ? '' : ':'.$udom).')'."\n"; + foreach my $apart (@$parts) { + my ($part,$type) = &split_part_type($apart); my $score=$record{"resource.$part.$type"}; - next if $type eq 'tries'; if ($type eq 'awarded') { my $pts = $score eq '' ? '' : $score*$$weight{$part}; $result.=''."\n"; + 'GD_'.$student.'_'.$part.'_awarded_s" value="'.$pts.'" />'."\n"; $result.=''."\n"; + 'GD_'.$student.'_'.$part.'_awarded" '. + 'onChange="javascript:changeSelect(\''.$part.'\',\''.$student. + '\')" value="'.$pts.'" size="4" />'."\n"; } elsif ($type eq 'solved') { my ($status,$foo)=split(/_/,$score,2); $status = 'nothing' if ($status eq ''); + $result.=''."\n"; + $result.='  \n"; + } else { $result.=''."\n"; - $result.='\n"; + 'GD_'.$student.'_'.$part.'_'.$type.'_s" value="'.$score.'" />'. + "\n"; + $result.=''."\n"; } } $result.=''; return $result; } - +#--- change scores for all the students in a section/class +# record does not get update if unchanged sub editgrades { my ($request) = @_; my $symb=$ENV{'form.symb'}; my $url =$ENV{'form.url'}; - my $result='

Current Grade Status

'; - $result.='Resource: '.$ENV{'form.url'}.'
'."\n"; - $result.='Section: '.$ENV{'form.section'}.''."\n"; - $result.= &show_grading_menu_form ($symb,$url); - $result.= ''.$line; + $updateCtr++; } else { - if ($oldscore ne $newscore) { - $newrecord{"resource.$part.$type"}=$newscore; - $result.="Updating $student"."'s status for $part.$type to $newscore
\n"; - } else { - #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore:
\n"; - } + $noupdate.=''.$line; + $noupdateCtr++; } } - if ( scalar(keys(%newrecord)) > 0 ) { - $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; -# &Apache::lonnet::cstore(\%newrecord,$symb,$courseid,$domain,$stuname); - - $result.="Stored away ".scalar(keys(%newrecord))." elements.
\n"; + if ($noupdate) { +# my $numcols=(scalar(@partid)*(scalar(@parts)-1)*2)+3; + my $numcols=scalar(@partid)*4+2; + $result .= ''.$noupdate; } - return $result; + $result .= '
'."\n"; + my $title='

Current Grade Status

'; + $title.='Current Resource: '.$ENV{'form.probTitle'}.'
'."\n"; + $title.='Section: '.$ENV{'form.section'}.''."\n"; + + my $result= '
'."\n"; $result.= ''. - ''."\n"; + ''. + '\n"; my %scoreptr = ( 'correct' =>'correct_by_override', @@ -1333,439 +2495,163 @@ sub editgrades { 'ungraded' =>'ungraded_attempted', 'nothing' => '', ); - my ($classlist,$seclist,$ids,$stusec,$fullname) = &getclasslist($ENV{'form.section'},'0'); + my ($classlist,undef,$fullname) = &getclasslist($ENV{'form.section'},'0'); my (@partid); my %weight = (); - my ($i,$ctr) = (0,0); + my %columns = (); + my ($i,$ctr,$count,$rec_update) = (0,0,0,0); + + my (@parts) = sort(&getpartlist($url,$symb)); + my $header; while ($ctr < $ENV{'form.totalparts'}) { my $partid = $ENV{'form.partid_'.$ctr}; push @partid,$partid; - my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb); - $weight{$partid} = $wgt eq '' ? '1' : $wgt; + $weight{$partid} = $ENV{'form.weight_'.$partid}; $ctr++; - $result .= ''; } - $result .= ''; - foreach (@partid) { - $result .= ''. + foreach my $partid (@partid) { + $header .= ''. ''; + $columns{$partid}=2; + foreach my $stores (@parts) { + my ($part,$type) = &split_part_type($stores); + if ($part !~ m/^\Q$partid\E/) { next;} + if ($type eq 'awarded' || $type eq 'solved') { next; } + my $display=&Apache::lonnet::metadata($url,$stores.'.display'); + $display =~ s/\[Part: (\w)+\]//; + $display =~ s/Number of Attempts/Tries/; + $header .= ''. + ''; + $columns{$partid}+=2; + } + } + foreach my $partid (@partid) { + my $display_part=&get_display_part($partid,$url,$symb); + $result .= ''; + } + $result .= ''; + $result .= $header; $result .= ''."\n"; - + my $noupdate; + my ($updateCtr,$noupdateCtr) = (1,1); for ($i=0; $i<$ENV{'form.total'}; $i++) { + my $line; my $user = $ENV{'form.ctr'.$i}; + my $usercolon = $user; + $usercolon =~s/_/:/; + my ($uname,$udom)=split(/_/,$user); my %newrecord; my $updateflag = 0; - my @userdom = grep /^$user:/,keys %$classlist; - my ($foo,$udom) = split(/:/,$userdom[0]); - - $result .= ''; - + $line .= ''; + my $usec=$classlist->{"$uname:$udom"}[5]; + if (!&canmodify($usec)) { + my $numcols=scalar(@partid)*4+2; + $noupdate.=$line.""; + next; + } foreach (@partid) { - my $old_aw = $ENV{'form.GD_'.$user.'_'.$_.'_aw_s'}; - my $old_part = $old_aw eq '' ? '' : $old_aw/$weight{$_}; - my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}}; - - my $awarded = $ENV{'form.GD_'.$user.'_'.$_.'_aw'}; - my $partial = $awarded eq '' ? '' : $awarded/$weight{$_}; + my $old_aw = $ENV{'form.GD_'.$user.'_'.$_.'_awarded_s'}; + my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1); + my $old_part = $old_aw eq '' ? '' : $old_part_pcr; + my $old_score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}}; + + my $awarded = $ENV{'form.GD_'.$user.'_'.$_.'_awarded'}; + my $pcr = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1); + my $partial = $awarded eq '' ? '' : $pcr; my $score; if ($partial eq '') { - $score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_sv_s'}}; + $score = $scoreptr{$ENV{'form.GD_'.$user.'_'.$_.'_solved_s'}}; } elsif ($partial > 0) { $score = 'correct_by_override'; } elsif ($partial == 0) { $score = 'incorrect_by_override'; } - $score = 'excused' if (($ENV{'form.GD_'.$user.'_'.$_.'_sv'} eq 'excused') && - ($score ne 'excused')); - $result .= ''. + my $dropMenu = $ENV{'form.GD_'.$user.'_'.$_.'_solved'}; + $score = 'excused' if (($dropMenu eq 'excused') && ($score ne 'excused')); + + if ($dropMenu eq 'reset status' && + $old_score ne '') { # ignore if no previous attempts => nothing to reset + $newrecord{'resource.'.$_.'.tries'} = 0; + $newrecord{'resource.'.$_.'.solved'} = ''; + $newrecord{'resource.'.$_.'.award'} = ''; + $newrecord{'resource.'.$_.'.awarded'} = 0; + $newrecord{'resource.'.$_.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; + $updateflag = 1; + } elsif (!($old_part eq $partial && $old_score eq $score)) { + $updateflag = 1; + $newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne ''; + $newrecord{'resource.'.$_.'.solved'} = $score; + $rec_update++; + } + + $line .= ''. ''; - next if ($old_part eq $partial && $old_score eq $score); - - $updateflag = 1; - $newrecord{'resource.'.$_.'.awarded'} = $partial if $partial ne ''; - $newrecord{'resource.'.$_.'.solved'} = $score; + my $partid=$_; + foreach my $stores (@parts) { + my ($part,$type) = &split_part_type($stores); + if ($part !~ m/^\Q$partid\E/) { next;} + if ($type eq 'awarded' || $type eq 'solved') { next; } + my $old_aw = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type.'_s'}; + my $awarded = $ENV{'form.GD_'.$user.'_'.$part.'_'.$type}; + if ($awarded ne '' && $awarded ne $old_aw) { + $newrecord{'resource.'.$part.'.'.$type}= $awarded; + $newrecord{'resource.'.$part.'.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; + $updateflag=1; + } + $line .= ''. + ''; + } } - $result .= ''."\n"; + $line.=''."\n"; if ($updateflag) { - $newrecord{'resource.regrader'}="$ENV{'user.name'}:$ENV{'user.domain'}"; + $count++; &Apache::lonnet::cstore(\%newrecord,$symb,$ENV{'request.course.id'}, - $udom,$user); - } - } - $result .= '
UsernameFullname No. '.&nameUserString('header')."Part ID '.$partid. - ' (Weight = '.$weight{$partid}.')
 Old Score  Old Score  New Score  Old '.$display.'  New '.$display.' Part: '.$display_part. + ' (Weight = '.$weight{$partid}.')
'.$user.' '. - $$fullname{$userdom[0]}.' '.&nameUserString(undef,$$fullname{$usercolon},$uname,$udom).'Not allowed to modify student
'.$old_aw.' '.$old_aw.' '.$awarded. ($score eq 'excused' ? $score : '').' '.$old_aw.' '.$awarded.' 
'."\n"; - return $result; -} - - -#FIXME need to look at the metadata spec on what type of data to accept and provide an -#interface based on that, also do that to above function. -sub setstudentgrade { - my ($url,$symb,$courseid,$student,@parts) = @_; - my $result =''; - my ($stuname,$domain) = split(/:/,$student); - my %record=&Apache::lonnet::restore($symb,$courseid,$domain,$stuname); - my %newrecord; - - foreach my $part (@parts) { - my ($temp,$part,$type)=split(/_/,$part); - my $oldscore=$record{"resource.$part.$type"}; - my $newscore=$ENV{"form.GD.$student.$part.$type"}; - if ($type eq 'solved') { - my $update=0; - if ($newscore eq 'nothing' ) { - if ($oldscore ne '') { - $update=1; - $newscore = ''; - } - } elsif ($oldscore !~ m/^$newscore/) { - $update=1; - $result.="Updating $stuname to $newscore
\n"; - if ($newscore eq 'correct') { $newscore = 'correct_by_override'; } - if ($newscore eq 'incorrect') { $newscore = 'incorrect_by_override'; } - if ($newscore eq 'excused') { $newscore = 'excused'; } - if ($newscore eq 'ungraded') { $newscore = 'ungraded_attempted'; } - } else { - #$result.="$stuname:$part:$type:unchanged $oldscore to $newscore:
\n"; - } - if ($update) { $newrecord{"resource.$part.$type"}=$newscore; } + $udom,$uname); + $result.='
 '.$updateCtr.' 
 '.$noupdateCtr.' 
No Changes Occurred For the Students Below
'."\n". + &show_grading_menu_form ($symb,$url); + my $msg = '
Number of records updated = '.$rec_update. + ' for '.$count.' student'.($count <= 1 ? '' : 's').'.
'. + 'Total number of students = '.$ENV{'form.total'}.'
'; + return $title.$msg.$result; } - -sub sub_page_js { - my $request = shift; - $request->print(< - function updateRadio(radioButton,formtextbox,formsel,scores) { - var pts = formtextbox.value; - var resetbox =false; - if (isNaN(pts) || pts < 0) { - alert("A number equal or greater than 0 is expected. Entered value = "+pts); - for (var i=0; i"+msgchk); - re = /msgsub/; - var shwsel = ""; - if (re.test(msgchk)) { shwsel = "checked" } - displaySubject(subject,shwsel); - for (var i=1; i<=Nmsg; i++) { - var testpt = "savemsg"+i+","; - re = /testpt/; - shwsel = ""; - if (re.test(msgchk)) { shwsel = "checked" } - var message = eval("document.SCORE.savemsg"+i+".value"); - displaySavedMsg(i,message,shwsel); - } - newmsg = eval("document.SCORE.newmsg"+usrctr+".value"); - shwsel = ""; - re = /newmsg/; - if (re.test(msgchk)) { shwsel = "checked" } - newMsg(newmsg,shwsel); - msgTail(); - return; - } - - function savedMsgHeader(Nmsg,usrctr,fullname) { - var height = 30*Nmsg+250; - var scrollbar = "no"; - if (height > 600) { - height = 600; - scrollbar = "yes"; - } -/* if (window.pWin) - window.pWin.close(); */ - pWin = window.open('', 'MessageCenter', 'toolbar=no,location=no,scrollbars='+scrollbar+',screenx=70,screeny=75,width=600,height='+height); - pWin.document.write(""); - pWin.document.write("Message Central"); - - pWin.document.write(" -SUBJAVASCRIPT -} +#---------------------------------------------------------------------------- +# +#-------------------------- Next few routines handles grading by csv upload +# +#--- Javascript to handle csv upload sub csvupload_javascript_reverse_associate { return(<print(< -

Uploading Class Grades for resource $url

+

Uploading Class Grades

+$result

Identify fields

Total number of records found in file: $distotal
@@ -1866,19 +2755,21 @@ to this page if the data selected is ins value="$ENV{'form.upfile_associate'}" /> + +
ENDPICK -return ''; + return ''; } sub csvupload_fields { - my ($url) = @_; - my (@parts) = &getpartlist($url); + my ($url,$symb) = @_; + my (@parts) = &getpartlist($url,$symb); my @fields=(['username','Student Username'],['domain','Student Domain']); foreach my $part (sort(@parts)) { my @datum; @@ -1902,10 +2793,54 @@ sub csvuploadmap_footer { ENDPICK } +sub upcsvScores_form { + my ($request) = shift; + my ($symb,$url)=&get_symb_and_url($request); + if (!$symb) {return '';} + my $result =< + 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(); + } + +CSVFORMJS + $ENV{'form.probTitle'} = &Apache::lonnet::gettitle($symb); + my ($table) = &showResourceInfo($url,$ENV{'form.probTitle'}); + $result.=$table; + $result.='
'."\n"; + $result.=''."\n"; + $result.='
'."\n"; + $result.=' Specify a file containing the class scores for current resource'. + '.
'."\n"; + my $upfile_select=&Apache::loncommon::upfile_select_html(); + $result.=< + + + + + +$upfile_select +
+ + +ENDUPFORM + $result.='
'."\n"; + $result.='


'."\n"; + $result.=&show_grading_menu_form($symb,$url); + return $result; +} + + sub csvuploadmap { my ($request)= @_; my ($symb,$url)=&get_symb_and_url($request); if (!$symb) {return '';} + my $datatoken; if (!$ENV{'form.datatoken'}) { $datatoken=&Apache::loncommon::upfile_store($request); @@ -1917,7 +2852,8 @@ sub csvuploadmap { &csvuploadmap_header($request,$symb,$url,$datatoken,$#records+1); my ($i,$keyfields); if (@records) { - my @fields=&csvupload_fields($url); + my @fields=&csvupload_fields($url,$symb); + if ($ENV{'form.upfile_associate'} eq 'reverse') { &Apache::loncommon::csv_print_samples($request,\@records); $i=&Apache::loncommon::csv_print_select_table($request,\@records, @@ -1933,6 +2869,8 @@ sub csvuploadmap { } } &csvuploadmap_footer($request,$i,$keyfields); + $request->print(&show_grading_menu_form($symb,$url)); + return ''; } @@ -1941,7 +2879,7 @@ sub csvuploadassign { my ($symb,$url)=&get_symb_and_url($request); if (!$symb) {return '';} &Apache::loncommon::load_tmp_file($request); - my @gradedata=&Apache::loncommon::upfile_record_sep(); + my @gradedata = &Apache::loncommon::upfile_record_sep(); my @keyfields = split(/\,/,$ENV{'form.keyfields'}); my %fields=(); for (my $i=0; $i<=$ENV{'form.nfields'}; $i++) { @@ -1957,19 +2895,25 @@ sub csvuploadassign { } $request->print('

Assigning Grades

'); my $courseid=$ENV{'request.course.id'}; -# my $cdom=$ENV{"course.$courseid.domain"}; -# my $cnum=$ENV{"course.$courseid.num"}; - my ($classlist) = &getclasslist('all','1'); + my ($classlist) = &getclasslist('all',0); + my @notallowed; my @skipped; my $countdone=0; foreach my $grade (@gradedata) { my %entries=&Apache::loncommon::record_sep($grade); my $username=$entries{$fields{'username'}}; + $username=~s/\s//g; my $domain=$entries{$fields{'domain'}}; + $domain=~s/\s//g; if (!exists($$classlist{"$username:$domain"})) { push(@skipped,"$username:$domain"); next; } + my $usec=$classlist->{"$username:$domain"}[5]; + if (!&canmodify($usec)) { + push(@notallowed,"$username:$domain"); + next; + } my %grades; foreach my $dest (keys(%fields)) { if ($dest eq 'username' || $dest eq 'domain') { next; } @@ -1988,52 +2932,2065 @@ sub csvuploadassign { } $request->print("
Stored $countdone students\n"); if (@skipped) { - $request->print('
Skipped Students
'); - foreach my $student (@skipped) { $request->print("
$student"); } + $request->print('Skipped Students

'); + foreach my $student (@skipped) { $request->print("$student
\n"); } + } + if (@notallowed) { + $request->print('

Students Not Allowed to Modify

'); + foreach my $student (@notallowed) { $request->print("$student
\n"); } } - $request->print(&view_edit_entire_class_form($symb,$url)); + $request->print("
\n"); $request->print(&show_grading_menu_form($symb,$url)); return ''; } +#------------- end of section for handling csv file upload --------- +# +#------------------------------------------------------------------- +# +#-------------- Next few routines handle grading by page/sequence +# +#--- Select a page/sequence and a student to grade +sub pickStudentPage { + my ($request) = shift; -sub send_header { - my ($request)= @_; - $request->print(&Apache::lontexconvert::header()); -# $request->print(" -#"); - $request->print(''); + $request->print(< + +function checkPickOne(formname) { + if (radioSelection(formname.student) == null) { + alert("Please select the student you wish to grade."); + return; + } + ptr = pullDownSelection(formname.selectpage); + formname.page.value = formname["page"+ptr].value; + formname.title.value = formname["title"+ptr].value; + formname.submit(); } -sub send_footer { - my ($request)= @_; - $request->print(''); - $request->print(&Apache::lontexconvert::footer()); + +LISTJAVASCRIPT + &commonJSfunctions($request); + my ($symb,$url) = &get_symb_and_url($request); + my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; + my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; + my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; + + my $result='

 '. + 'Manual Grading by Page or Sequence

'; + + $result.='
'."\n"; + $result.=' Problems from: '."
\n"; + $ctr=0; + foreach (@$titles) { + my ($minder,$showtitle) = ($_ =~ /(\d+)\.(.*)/); + $result.=''."\n"; + $result.=''."\n"; + $ctr++; + } + $result.=''."\n". + ''."\n"; + + $result.=' View Problems Text: no '."\n". + ' yes '."
\n"; + + $result.=' Submission Details: '. + ' none'."\n". + ' by dates and submissions'."\n". + ' all details'."\n"; + + $result.=''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."
\n"; + + $result.=' 
'."\n"; + + $request->print($result); + + my $studentTable.=' Select a student you wish to grade and then click on the Next button.
'. + '
'. + ''. + ''. + ''. + ''. + ''; + + my (undef,undef,$fullname) = &getclasslist($getsec,'1'); + my $ptr = 1; + foreach my $student (sort {lc($$fullname{$a}) cmp lc($$fullname{$b}) } keys %$fullname) { + my ($uname,$udom) = split(/:/,$student); + $studentTable.=($ptr%2 == 1 ? '' : ''); + $studentTable.=''; + $studentTable.='' : ''); + $ptr++; + } + $studentTable.='
 No.'.&nameUserString('header').' No.'.&nameUserString('header').'
'.$ptr.'   ' + .&nameUserString(undef,$$fullname{$student},$uname,$udom)."\n"; + $studentTable.=($ptr%2 == 0 ? '
  ' if ($ptr%2 == 0); + $studentTable.='
'."\n"; + $studentTable.='
'."\n"; + + $studentTable.=&show_grading_menu_form($symb,$url); + $request->print($studentTable); + + return ''; +} + +sub getSymbMap { + my ($request) = @_; + my $navmap = Apache::lonnavmaps::navmap->new(); + + my %symbx = (); + my @titles = (); + my $minder = 0; + + # Gather every sequence that has problems. + my @sequences = $navmap->retrieveResources(undef, sub { shift->is_map(); }, 1); + for my $sequence ($navmap->getById('0.0'), @sequences) { + if ($navmap->hasResource($sequence, sub { shift->is_problem(); }, 0) ) { + my $title = $minder.'.'.$sequence->compTitle(); + push @titles, $title; # minder in case two titles are identical + $symbx{$title} = $sequence->symb(); + $minder++; + } + } + + $navmap->untieHashes(); + return \@titles,\%symbx; +} + +# +#--- Displays a page/sequence w/wo problems, w/wo submissions +sub displayPage { + my ($request) = shift; + + my ($symb,$url) = &get_symb_and_url($request); + my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; + my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; + my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; + my $pageTitle = $ENV{'form.page'}; + my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); + my ($uname,$udom) = split(/:/,$ENV{'form.student'}); + my $usec=$classlist->{$ENV{'form.student'}}[5]; + + #need to make sure we have the correct data for later EXT calls, + #thus invalidate the cache + &Apache::lonnet::devalidatecourseresdata( + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}); + &Apache::lonnet::clear_EXT_cache_status(); + + if (!&canview($usec)) { + $request->print('Unable to view requested student.('.$ENV{'form.student'}.')'); + $request->print(&show_grading_menu_form($symb,$url)); + return; + } + my $result='

 '.$ENV{'form.title'}.'

'; + $result.='

 Student: '.&nameUserString(undef,$$fullname{$ENV{'form.student'}},$uname,$udom). + '

'."\n"; + &sub_page_js($request); + $request->print($result); + + my $navmap = Apache::lonnavmaps::navmap->new(); + my ($mapUrl, $id, $resUrl)=&Apache::lonnet::decode_symb($ENV{'form.page'}); + my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps + + my $iterator = $navmap->getIterator($map->map_start(), + $map->map_finish()); + + my $studentTable='
'."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n"; + + my $checkIcon = ''; + + $studentTable.=' Note: Problems graded correct by the computer are marked with a '.$checkIcon. + ' symbol.'."\n". + ' + + +SCANTRONFORM + + $r->print($result); + + if (&Apache::lonnet::allowed('usc',$ENV{'request.role.domain'}) || + &Apache::lonnet::allowed('usc',$ENV{'request.course.id'})) { + + $r->print(< + + +SCANTRONFORM + } + $r->print(< + + +SCANTRONFORM + + $r->print(< + +$grading_menu_button +SCANTRONFORM + + return +} + +sub get_scantron_config { + my ($which) = @_; + my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); + my %config; + #FIXME probably should move to XML it has already gotten a bit much now + foreach my $line (<$fh>) { + my ($name,$descrip)=split(/:/,$line); + if ($name ne $which ) { next; } + chomp($line); + my @config=split(/:/,$line); + $config{'name'}=$config[0]; + $config{'description'}=$config[1]; + $config{'CODElocation'}=$config[2]; + $config{'CODEstart'}=$config[3]; + $config{'CODElength'}=$config[4]; + $config{'IDstart'}=$config[5]; + $config{'IDlength'}=$config[6]; + $config{'Qstart'}=$config[7]; + $config{'Qlength'}=$config[8]; + $config{'Qoff'}=$config[9]; + $config{'Qon'}=$config[10]; + $config{'PaperID'}=$config[11]; + $config{'PaperIDlength'}=$config[12]; + $config{'FirstName'}=$config[13]; + $config{'FirstNamelength'}=$config[14]; + $config{'LastName'}=$config[15]; + $config{'LastNamelength'}=$config[16]; + last; + } + return %config; +} + +sub username_to_idmap { + my ($classlist)= @_; + my %idmap; + foreach my $student (keys(%$classlist)) { + $idmap{$classlist->{$student}->[&Apache::loncoursedata::CL_ID]}= + $student; + } + return %idmap; +} + +sub scantron_fixup_scanline { + my ($scantron_config,$scan_data,$line,$whichline,$field,$args)=@_; + if ($field eq 'ID') { + if (length($args->{'newid'}) > $$scantron_config{'IDlength'}) { + return ($line,1,'New value too large'); + } + if (length($args->{'newid'}) < $$scantron_config{'IDlength'}) { + $args->{'newid'}=sprintf('%-'.$$scantron_config{'IDlength'}.'s', + $args->{'newid'}); + } + substr($line,$$scantron_config{'IDstart'}-1, + $$scantron_config{'IDlength'})=$args->{'newid'}; + if ($args->{'newid'}=~/^\s*$/) { + &scan_data($scan_data,"$whichline.user", + $args->{'username'}.':'.$args->{'domain'}); + } + } elsif ($field eq 'CODE') { + if ($args->{'CODE_ignore_dup'}) { + &scan_data($scan_data,"$whichline.CODE_ignore_dup",'1'); + } + &scan_data($scan_data,"$whichline.useCODE",'1'); + if ($args->{'CODE'} ne 'use_unfound') { + if (length($args->{'CODE'}) > $$scantron_config{'CODElength'}) { + return ($line,1,'New CODE value too large'); + } + if (length($args->{'CODE'}) < $$scantron_config{'CODElength'}) { + $args->{'CODE'}=sprintf('%-'.$$scantron_config{'CODElength'}.'s',$args->{'CODE'}); + } + substr($line,$$scantron_config{'CODEstart'}-1, + $$scantron_config{'CODElength'})=$args->{'CODE'}; + } + } elsif ($field eq 'answer') { + my $length=$scantron_config->{'Qlength'}; + my $off=$scantron_config->{'Qoff'}; + my $on=$scantron_config->{'Qon'}; + my $answer=${off}x$length; + if ($args->{'response'} eq 'none') { + &scan_data($scan_data, + "$whichline.no_bubble.".$args->{'question'},'1'); + } else { + substr($answer,$args->{'response'},1)=$on; + &scan_data($scan_data, + "$whichline.no_bubble.".$args->{'question'},undef,'1'); + } + my $where=$length*($args->{'question'}-1)+$scantron_config->{'Qstart'}; + substr($line,$where-1,$length)=$answer; + } + return $line; +} + +sub scan_data { + my ($scan_data,$key,$value,$delete)=@_; + my $filename=$ENV{'form.scantron_selectfile'}; + if (defined($value)) { + $scan_data->{$filename.'_'.$key} = $value; + } + if ($delete) { delete($scan_data->{$filename.'_'.$key}); } + return $scan_data->{$filename.'_'.$key}; +} + +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); + if ($$scantron_config{'CODElocation'} ne 0) { + if ($$scantron_config{'CODElocation'} < 0) { + $record{'scantron.CODE'}=substr($data, + $$scantron_config{'CODEstart'}-1, + $$scantron_config{'CODElength'}); + if (&scan_data($scan_data,"$whichline.useCODE")) { + $record{'scantron.useCODE'}=1; + } + if (&scan_data($scan_data,"$whichline.CODE_ignore_dup")) { + $record{'scantron.CODE_ignore_dup'}=1; + } + } else { + #FIXME interpret first N questions + } + } + $record{'scantron.ID'}=substr($data,$$scantron_config{'IDstart'}-1, + $$scantron_config{'IDlength'}); + $record{'scantron.PaperID'}= + substr($data,$$scantron_config{'PaperID'}-1, + $$scantron_config{'PaperIDlength'}); + $record{'scantron.FirstName'}= + substr($data,$$scantron_config{'FirstName'}-1, + $$scantron_config{'FirstNamelength'}); + $record{'scantron.LastName'}= + substr($data,$$scantron_config{'LastName'}-1, + $$scantron_config{'LastNamelength'}); + if ($justHeader) { return \%record; } + + my @alphabet=('A'..'Z'); + my $questnum=0; + while ($questions) { + $questnum++; + my $currentquest=substr($questions,0,$$scantron_config{'Qlength'}); + substr($questions,0,$$scantron_config{'Qlength'})=''; + if (length($currentquest) < $$scantron_config{'Qlength'}) { next; } + my @array=split($$scantron_config{'Qon'},$currentquest,-1); + if (length($array[0]) eq $$scantron_config{'Qlength'}) { + $record{"scantron.$questnum.answer"}=''; + if (!&scan_data($scan_data,"$whichline.no_bubble.$questnum")) { + push(@{$record{"scantron.missingerror"}},$questnum); + } + } else { + $record{"scantron.$questnum.answer"}=$alphabet[length($array[0])]; + } + if (scalar(@array) gt 2) { + push(@{$record{'scantron.doubleerror'}},$questnum); + my @ans=@array; + my $i=length($ans[0]);shift(@ans); + while ($#ans) { + $i+=length($ans[0])+1; + $record{"scantron.$questnum.answer"}.=$alphabet[$i]; + shift(@ans); + } + } + } + $record{'scantron.maxquest'}=$questnum; + return \%record; +} + +sub scantron_add_delay { + my ($delayqueue,$scanline,$errormessage,$errorcode)=@_; + push(@$delayqueue, + {'line' => $scanline, 'emsg' => $errormessage, + 'ecode' => $errorcode } + ); +} + +sub scantron_find_student { + my ($scantron_record,$scan_data,$idmap,$line)=@_; + my $scanID=$$scantron_record{'scantron.ID'}; + if ($scanID =~ /^\s*$/) { + return &scan_data($scan_data,"$line.user"); + } + foreach my $id (keys(%$idmap)) { + if (lc($id) eq lc($scanID)) { + return $$idmap{$id}; + } + } + return undef; +} + +sub scantron_filter { + my ($curres)=@_; + # randomout is dysfunctional at best for this purpose + if (ref($curres) && $curres->is_problem()) { #&& !$curres->randomout) { + return 1; + } + return 0; +} + +sub scantron_process_corrections { + my ($r) = @_; + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + my $classlist=&Apache::loncoursedata::get_classlist(); + my $which=$ENV{'form.scantron_line'}; + my $line=&scantron_get_line($scanlines,$scan_data,$which); + my ($skip,$err,$errmsg); + if ($ENV{'form.scantron_skip_record'}) { + $skip=1; + } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)ID$/) { + my $newstudent=$ENV{'form.scantron_username'}.':'. + $ENV{'form.scantron_domain'}; + my $newid=$classlist->{$newstudent}->[&Apache::loncoursedata::CL_ID]; + ($line,$err,$errmsg)= + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, + 'ID',{'newid'=>$newid, + 'username'=>$ENV{'form.scantron_username'}, + 'domain'=>$ENV{'form.scantron_domain'}}); + } elsif ($ENV{'form.scantron_corrections'} =~ /^(duplicate|incorrect)CODE$/) { + my $resolution=$ENV{'form.scantron_CODE_resolution'}; + my $newCODE; + my %args; + if ($resolution eq 'use_unfound') { + $newCODE='use_unfound'; + } elsif ($resolution eq 'use_found') { + $newCODE=$ENV{'form.scantron_CODE_selectedvalue'}; + } elsif ($resolution eq 'use_typed') { + $newCODE=$ENV{'form.scantron_CODE_newvalue'}; + } elsif ($resolution =~ /^use_closest_(\d+)/) { + $newCODE=$ENV{"form.scantron_CODE_closest_$1"}; + } + if ($ENV{'form.scantron_corrections'} eq 'duplicateCODE') { + $args{'CODE_ignore_dup'}=1; + } + $args{'CODE'}=$newCODE; + ($line,$err,$errmsg)= + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line,$which, + 'CODE',\%args); + } elsif ($ENV{'form.scantron_corrections'} =~ /^(missing|double)bubble$/) { + foreach my $question (split(',',$ENV{'form.scantron_questions'})) { + ($line,$err,$errmsg)= + &scantron_fixup_scanline(\%scantron_config,$scan_data,$line, + $which,'answer', + { 'question'=>$question, + 'response'=>$ENV{"form.scantron_correct_Q_$question"}}); + if ($err) { last; } + } + } + if ($err) { + $r->print("Unable to accept last correction, an error occurred :$errmsg:"); + } else { + &scantron_put_line($scanlines,$scan_data,$which,$line,$skip); + &scantron_putfile($scanlines,$scan_data); + } +} + +sub reset_skipping_status { + my ($scanlines,$scan_data)=&scantron_getfile(); + &scan_data($scan_data,'remember_skipping',undef,1); + &scantron_putfile(undef,$scan_data); +} + +sub allow_skipping { + my ($scan_data,$i)=@_; + my %remembered=split(':',&scan_data($scan_data,'remember_skipping')); + delete($remembered{$i}); + &scan_data($scan_data,'remember_skipping',join(':',%remembered)); +} + +sub should_be_skipped { + my ($scan_data,$i)=@_; + if ($ENV{'form.scantron_options_redo'} !~ /^redo_/) { + # not redoing old skips + return 0; + } + my %remembered=split(':',&scan_data($scan_data,'remember_skipping')); + if (exists($remembered{$i})) { return 0; } + return 1; +} + +sub remember_current_skipped { + my ($scanlines,$scan_data)=&scantron_getfile(); + my %to_remember; + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + if ($scanlines->{'skipped'}[$i]) { + $to_remember{$i}=1; + } + } + &Apache::lonnet::logthis('remembering '.join(':',%to_remember)); + &scan_data($scan_data,'remember_skipping',join(':',%to_remember)); + &scantron_putfile(undef,$scan_data); +} + +sub check_for_error { + my ($r,$result)=@_; + if ($result ne 'ok' && $result ne 'not_found' ) { + $r->print("An error occured ($result) when trying to Remove the existing corrections."); + } +} + +sub scantron_warning_screen { + my ($button_text)=@_; + my $title=&Apache::lonnet::gettitle($ENV{'form.selectpage'}); + return (< +Please double check the information + below before clicking on '$button_text' +

+
'. + ''. + ''. + ''; + + my ($depth,$question,$prob) = (1,1,1); + $iterator->next(); # skip the first BEGIN_MAP + my $curRes = $iterator->next(); # for "current resource" + while ($depth > 0) { + if($curRes == $iterator->BEGIN_MAP) { $depth++; } + if($curRes == $iterator->END_MAP) { $depth--; } + + if (ref($curRes) && $curRes->is_problem()) { + my $parts = $curRes->parts(); + my $title = $curRes->compTitle(); + my $symbx = $curRes->symb(); + $studentTable.=''; + $studentTable.='
 Prob.  '.($ENV{'form.vProb'} eq 'no' ? 'Title' : 'Problem Text').'/Grade
'.$prob. + (scalar(@{$parts}) == 1 ? '' : '
('.scalar(@{$parts}).' parts)').'
'; + if ($ENV{'form.vProb'} eq 'yes' ) { + $studentTable.=&show_problem($request,$symbx,$uname,$udom,1, + undef,'both'); + } else { + my $companswer = &Apache::loncommon::get_student_answers($symbx,$uname,$udom,$ENV{'request.course.id'}); + $companswer =~ s|||g; + $companswer =~ s|||g; +# while ($companswer =~ /()/s) { #\n"); +# } +# $companswer =~ s||
|g; + $studentTable.=' '.$title.' 
 Correct answer:
'.$companswer; + } + + my %record = &Apache::lonnet::restore($symbx,$ENV{'request.course.id'},$udom,$uname); + + if ($ENV{'form.lastSub'} eq 'datesub') { + if ($record{'version'} eq '') { + $studentTable.='
 No recorded submission for this problem
'; + } else { + my %responseType = (); + foreach my $partid (@{$parts}) { + my @responseIds =$curRes->responseIds($partid); + my @responseType =$curRes->responseType($partid); + my %responseIds; + for (my $i=0;$i<=$#responseIds;$i++) { + $responseIds{$responseIds[$i]}=$responseType[$i]; + } + $responseType{$partid} = \%responseIds; + } + $studentTable.= &displaySubByDates($symbx,\%record,$parts,\%responseType,$checkIcon,$uname,$udom); + + } + } elsif ($ENV{'form.lastSub'} eq 'all') { + my $last = ($ENV{'form.lastSub'} eq 'last' ? 'last' : ''); + $studentTable.=&Apache::loncommon::get_previous_attempt($symbx,$uname,$udom, + $ENV{'request.course.id'}, + '','.submission'); + + } + if (&canmodify($usec)) { + foreach my $partid (@{$parts}) { + $studentTable.=&gradeBox($request,$symbx,$uname,$udom,$question,$partid,\%record); + $studentTable.=''."\n"; + $question++; + } + $prob++; + } + $studentTable.=''; + + } + $curRes = $iterator->next(); + } + + $navmap->untieHashes(); + + $studentTable.='
'."\n". + ''. + ''."\n"; + $studentTable.=&show_grading_menu_form($symb,$url); + $request->print($studentTable); + + return ''; +} + +sub displaySubByDates { + my ($symb,$record,$parts,$responseType,$checkIcon,$uname,$udom) = @_; + my $studentTable='
'. + ''. + ''. + ''. + ''; + my ($version); + my %mark; + my %orders; + $mark{'correct_by_student'} = $checkIcon; + if (!exists($$record{'1:timestamp'})) { + return '
 Nothing submitted - no attempts
'; + } + for ($version=1;$version<=$$record{'version'};$version++) { + my $timestamp = scalar(localtime($$record{$version.':timestamp'})); + $studentTable.=''; + my @versionKeys = split(/\:/,$$record{$version.':keys'}); + my @displaySub = (); + foreach my $partid (@{$parts}) { + my @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys); +# next if ($$record{"$version:resource.$partid.solved"} eq ''); + my $display_part=&get_display_part($partid,undef,$symb); + foreach my $matchKey (@matchKey) { + if (exists($$record{$version.':'.$matchKey}) && + $$record{$version.':'.$matchKey} ne '') { + my ($responseId)=($matchKey=~ /^resource\.\Q$partid\E\.(.*?)\.submission$/); + $displaySub[0].='Part: '.$display_part.' '; + $displaySub[0].='(ID '. + $responseId.') '; + if ($$record{"$version:resource.$partid.tries"} eq '') { + $displaySub[0].='Trial not counted'; + } else { + $displaySub[0].='Trial '. + $$record{"$version:resource.$partid.tries"}; + } + my $responseType=$responseType->{$partid}->{$responseId}; + if (!exists($orders{$partid})) { $orders{$partid}={}; } + if (!exists($orders{$partid}->{$responseId})) { + $orders{$partid}->{$responseId}= + &get_order($partid,$responseId,$symb,$uname,$udom); + } + $displaySub[0].='  '. + &cleanRecord($$record{$version.':'.$matchKey},$responseType,$symb,$partid,$responseId,$record,$orders{$partid}->{$responseId},"$version:").'
'; + } + } + if (exists $$record{"$version:resource.$partid.award"}) { + $displaySub[1].='Part: '.$display_part.'  '. + lc($$record{"$version:resource.$partid.award"}).' '. + $mark{$$record{"$version:resource.$partid.solved"}}. + '
'; + } + if (exists $$record{"$version:resource.$partid.regrader"}) { + $displaySub[2].=$$record{"$version:resource.$partid.regrader"}. + ' ('.&mt('Part').': '.$display_part.')'; + } + } + # needed because old essay regrader has not parts info + if (exists $$record{"$version:resource.regrader"}) { + $displaySub[2].=$$record{"$version:resource.regrader"}; + } + $studentTable.=''; + + } + $studentTable.='
Date/TimeSubmissionStatus 
'.$timestamp.''.$displaySub[0].' '.$displaySub[1]; + if ($displaySub[2]) { + $studentTable.='Manually graded by '.$displaySub[2]; + } + $studentTable.=' 
'; + return $studentTable; +} + +sub updateGradeByPage { + my ($request) = shift; + + my $cdom = $ENV{"course.$ENV{'request.course.id'}.domain"}; + my $cnum = $ENV{"course.$ENV{'request.course.id'}.num"}; + my $getsec = $ENV{'form.section'} eq '' ? 'all' : $ENV{'form.section'}; + my $pageTitle = $ENV{'form.page'}; + my ($classlist,undef,$fullname) = &getclasslist($getsec,'1'); + my ($uname,$udom) = split(/:/,$ENV{'form.student'}); + my $usec=$classlist->{$ENV{'form.student'}}[5]; + if (!&canmodify($usec)) { + $request->print('Unable to modify requested student.('.$ENV{'form.student'}.''); + $request->print(&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'})); + return; + } + my $result='

 '.$ENV{'form.title'}.'

'; + $result.='

 Student: '.&nameUserString(undef,$ENV{'form.fullname'},$uname,$udom). + '

'."\n"; + + $request->print($result); + + my $navmap = Apache::lonnavmaps::navmap->new(); + my ($mapUrl, $id, $resUrl) = &Apache::lonnet::decode_symb( $ENV{'form.page'}); + my $map = $navmap->getResourceByUrl($resUrl); # add to navmaps + + my $iterator = $navmap->getIterator($map->map_start(), + $map->map_finish()); + + my $studentTable='
'. + ''. + ''. + ''. + ''. + ''; + + $iterator->next(); # skip the first BEGIN_MAP + my $curRes = $iterator->next(); # for "current resource" + my ($depth,$question,$prob,$changeflag)= (1,1,1,0); + while ($depth > 0) { + if($curRes == $iterator->BEGIN_MAP) { $depth++; } + if($curRes == $iterator->END_MAP) { $depth--; } + + if (ref($curRes) && $curRes->is_problem() && !$curRes->randomout) { + my $parts = $curRes->parts(); + my $title = $curRes->compTitle(); + my $symbx = $curRes->symb(); + $studentTable.=''; + $studentTable.=''; + + my %newrecord=(); + my @displayPts=(); + foreach my $partid (@{$parts}) { + my $newpts = $ENV{'form.GD_BOX'.$question.'_'.$partid}; + my $oldpts = $ENV{'form.oldpts'.$question.'_'.$partid}; + + my $wgt = $ENV{'form.WGT'.$question.'_'.$partid} != 0 ? + $ENV{'form.WGT'.$question.'_'.$partid} : 1; + my $partial = $newpts/$wgt; + my $score; + if ($partial > 0) { + $score = 'correct_by_override'; + } elsif ($newpts ne '') { #empty is taken as 0 + $score = 'incorrect_by_override'; + } + my $dropMenu = $ENV{'form.GD_SEL'.$question.'_'.$partid}; + if ($dropMenu eq 'excused') { + $partial = ''; + $score = 'excused'; + } elsif ($dropMenu eq 'reset status' + && $ENV{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists + $newrecord{'resource.'.$partid.'.tries'} = 0; + $newrecord{'resource.'.$partid.'.solved'} = ''; + $newrecord{'resource.'.$partid.'.award'} = ''; + $newrecord{'resource.'.$partid.'.awarded'} = 0; + $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}"; + $changeflag++; + $newpts = ''; + } + my $display_part=&get_display_part($partid,undef, + $curRes->symb()); + my $oldstatus = $ENV{'form.solved'.$question.'_'.$partid}; + $displayPts[0].=' Part: '.$display_part.' = '. + (($oldstatus eq 'excused') ? 'excused' : $oldpts). + ' 
'; + $displayPts[1].=' Part: '.$display_part.' = '. + (($score eq 'excused') ? 'excused' : $newpts). + ' 
'; + + $question++; + next if ($dropMenu eq 'reset status' || ($newpts == $oldpts && $score ne 'excused')); + + $newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne ''; + $newrecord{'resource.'.$partid.'.solved'} = $score if $score ne ''; + $newrecord{'resource.'.$partid.'.regrader'} = "$ENV{'user.name'}:$ENV{'user.domain'}" + if (scalar(keys(%newrecord)) > 0); + + $changeflag++; + } + if (scalar(keys(%newrecord)) > 0) { + &Apache::lonnet::cstore(\%newrecord,$symbx,$ENV{'request.course.id'}, + $udom,$uname); + } + + $studentTable.=''. + ''. + ''; + + $prob++; + } + $curRes = $iterator->next(); + } + + $navmap->untieHashes(); + + $studentTable.='
 Prob.  Title  Previous Score  New Score 
'.$prob. + (scalar(@{$parts}) == 1 ? '' : '
('.scalar(@{$parts}).' parts)').'
 '.$title.' '.$displayPts[0].''.$displayPts[1].'
'; + $studentTable.=&show_grading_menu_form($ENV{'form.symb'},$ENV{'form.url'}); + my $grademsg=($changeflag == 0 ? 'No score was changed or updated.' : + 'The scores were changed for '. + $changeflag.' problem'.($changeflag == 1 ? '.' : 's.')); + $request->print($grademsg.$studentTable); + + return ''; +} + +#-------- end of section for handling grading by page/sequence --------- +# +#------------------------------------------------------------------- + +#--------------------Scantron Grading----------------------------------- +# +#------ start of section for handling grading by page/sequence --------- + +sub defaultFormData { + my ($symb,$url)=@_; + return ' + '."\n". + ''."\n". + ''."\n". + ''."\n"; +} + +sub getSequenceDropDown { + my ($request,$symb)=@_; + my $result=''; + return $result; +} + +sub scantron_filenames { + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my @files=&Apache::lonnet::dirlist('userfiles',$cdom,$cname, + &Apache::loncommon::propath($cdom,$cname)); + my @possiblenames; + foreach my $filename (sort(@files)) { + ($filename)=split(/&/,$filename); + if ($filename!~/^scantron_orig_/) { next ; } + $filename=~s/^scantron_orig_//; + push(@possiblenames,$filename); + } + return @possiblenames; +} + +sub scantron_uploads { + my ($file2grade) = @_; + my $result= '"; + return $result; +} + +sub scantron_scantab { + my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.'/scantronformat.tab'); + my $result=''."\n"; + + return $result; +} + +sub scantron_CODElist { + my $cdom = $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $cnum = $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my @names=&Apache::lonnet::getkeys('CODEs',$cdom,$cnum); + my $namechoice=''; + foreach my $name (sort(@names)) { + if ($name =~ /^error: 2 /) { next; } + $namechoice.=''; + } + $namechoice=''; + return $namechoice; +} + +sub scantron_CODEunique { + my $result=' + Yes + + + No + '; + return $result; +} + +sub scantron_selectphase { + my ($r,$file2grade) = @_; + my ($symb,$url)=&get_symb_and_url($r); + if (!$symb) {return '';} + my $sequence_selector=&getSequenceDropDown($r,$symb); + my $default_form_data=&defaultFormData($symb,$url); + my $grading_menu_button=&show_grading_menu_form($symb,$url); + my $file_selector=&scantron_uploads($file2grade); + my $format_selector=&scantron_scantab(); + my $CODE_selector=&scantron_CODElist(); + my $CODE_unique=&scantron_CODEunique(); + my $result; + #FIXME allow instructor to be able to download the scantron file + # and to upload it, + $result.= < +
+
+ + $default_form_data + + + + + + + + + + + + + + + + + + + + + + + + + + +
+  Specify file and which Folder/Sequence to grade +
Sequence to grade: $sequence_selector
Filename of scoring office file: $file_selector
Format of data file: $format_selector
Saved CODEs to validate against: $CODE_selector
Each CODE is only to be used once: $CODE_unique
Options: + Do only previously skipped records
+ Remove all exisiting corrections +
+ +
+
+
+ + + + + + +
+  Specify a Scantron data file to upload. +
+SCANTRONFORM + my $default_form_data=&defaultFormData(&get_symb_and_url($r,1)); + my $cdom= $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $cnum= $ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + $r->print(< + 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(); + } + + +
+ $default_form_data + + + + File to upload: +
+ +
+UPLOAD + + $r->print(< +
+
+
+ + + + + + + + + + + +
+  Download a scoring office file +
Filename of scoring office file: $file_selector
+ +
+
+
+ + +
Sequence To be Graded:$title
Data File that will be used:$ENV{'form.scantron_selectfile'}
+ +
+

If this information is correct, please click on '$button_text'.

+

If something is incorrect, please click the 'Grading Menu' button to start over.

+ +
+STUFF +} + +sub scantron_do_warning { + my ($r)=@_; + my ($symb,$url)=&get_symb_and_url($r); + if (!$symb) {return '';} + my $default_form_data=&defaultFormData($symb,$url); + $r->print(&scantron_form_start().$default_form_data); + my $warning=&scantron_warning_screen('Validate Records'); + $r->print(< + + +STUFF + $r->print("
".&show_grading_menu_form($symb,$url).""); + return ''; +} + +sub scantron_form_start { + my ($max_bubble)=@_; + my $result= < + + + + + + + + +SCANTRONFORM + return $result; +} + +sub scantron_validate_file { + my ($r) = @_; + my ($symb,$url)=&get_symb_and_url($r); + if (!$symb) {return '';} + my $default_form_data=&defaultFormData($symb,$url); + + # do the detection of only doing skipped records first befroe we delete + # them when doing the corrections reset + if ($ENV{'form.scantron_options_redo'} ne 'redo_skipped_ready') { + &reset_skipping_status(); + } + if ($ENV{'form.scantron_options_redo'} eq 'redo_skipped') { + &remember_current_skipped(); + &scantron_remove_file('skipped'); + $ENV{'form.scantron_options_redo'}='redo_skipped_ready'; + } + + if ($ENV{'form.scantron_options_ignore'} eq 'ignore_corrections') { + &check_for_error($r,&scantron_remove_file('corrected')); + &check_for_error($r,&scantron_remove_file('skipped')); + &check_for_error($r,&scantron_remove_scan_data()); + $ENV{'form.scantron_options_ignore'}='done'; + } + + if ($ENV{'form.scantron_corrections'}) { + &scantron_process_corrections($r); + } + $r->print("

Gathering neccessary info.

");$r->rflush(); + #get the student pick code ready + $r->print(&Apache::loncommon::studentbrowser_javascript()); + my $max_bubble=&scantron_get_maxbubble($r); + my $result=&scantron_form_start($max_bubble).$default_form_data; + $r->print($result); + + my @validate_phases=( 'ID', + 'CODE', + 'doublebubble', + 'missingbubbles'); + if (!$ENV{'form.validatepass'}) { + $ENV{'form.validatepass'} = 0; + } + my $currentphase=$ENV{'form.validatepass'}; + + my $stop=0; + while (!$stop && $currentphase < scalar(@validate_phases)) { + $r->print("

Validating ".$validate_phases[$currentphase]."

"); + $r->rflush(); + my $which="scantron_validate_".$validate_phases[$currentphase]; + { + no strict 'refs'; + ($stop,$currentphase)=&$which($r,$currentphase); + } + } + if (!$stop) { + my $warning=&scantron_warning_screen('Start Grading'); + $r->print(< +$warning + + +STUFF + + } else { + $r->print(''); + $r->print(""); + } + if ($stop) { + $r->print(''); + $r->print(' using corrected info
'); + $r->print(""); + $r->print(" this scanline saving it for later."); + } + $r->print("
".&show_grading_menu_form($symb,$url). + ""); + return ''; +} + +sub scantron_remove_file { + my ($which)=@_; + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $file='scantron_'; + if ($which eq 'corrected' || $which eq 'skipped') { + $file.=$which.'_'; + } else { + return 'refused'; + } + $file.=$ENV{'form.scantron_selectfile'}; + return &Apache::lonnet::removeuserfile($cname,$cdom,$file); +} + +sub scantron_remove_scan_data { + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my @keys=&Apache::lonnet::getkeys('nohist_scantrondata',$cdom,$cname); + my @todelete; + my $filename=$ENV{'form.scantron_selectfile'}; + foreach my $key (@keys) { + if ($key=~/^\Q$filename\E_/) { + if ($ENV{'form.scantron_options_redo'} eq 'redo_skipped_ready' && + $key=~/remember_skipping/) { + next; + } + push(@todelete,$key); + } + } + my $result; + if (@todelete) { + $result=&Apache::lonnet::del('nohist_scantrondata',\@todelete,$cdom,$cname); + } + return $result; +} + +sub scantron_getfile { + #FIXME really would prefer a scantron directory + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $lines; + $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. + 'scantron_orig_'.$ENV{'form.scantron_selectfile'}); + my %scanlines; + $scanlines{'orig'}=[(split("\n",$lines,-1))]; + my $temp=$scanlines{'orig'}; + $scanlines{'count'}=$#$temp; + + $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. + 'scantron_corrected_'.$ENV{'form.scantron_selectfile'}); + if ($lines eq '-1') { + $scanlines{'corrected'}=[]; + } else { + $scanlines{'corrected'}=[(split("\n",$lines,-1))]; + } + $lines=&Apache::lonnet::getfile('/uploaded/'.$cdom.'/'.$cname.'/'. + 'scantron_skipped_'.$ENV{'form.scantron_selectfile'}); + if ($lines eq '-1') { + $scanlines{'skipped'}=[]; + } else { + $scanlines{'skipped'}=[(split("\n",$lines,-1))]; + } + my @tmp=&Apache::lonnet::dump('nohist_scantrondata',$cdom,$cname); + if ($tmp[0] =~ /^(error:|no_such_host)/) { @tmp=(); } + my %scan_data = @tmp; + return (\%scanlines,\%scan_data); +} + +sub lonnet_putfile { + my ($contents,$filename)=@_; + my $docuname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $docudom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $docuhome=$ENV{'course.'.$ENV{'request.course.id'}.'.home'}; + $ENV{'form.sillywaytopassafilearound'}=$contents; + &Apache::lonnet::finishuserfileupload($docuname,$docudom,$docuhome,'sillywaytopassafilearound',$filename); + +} + +sub scantron_putfile { + my ($scanlines,$scan_data) = @_; + #FIXME really would prefer a scantron directory + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + if ($scanlines) { + my $prefix='scantron_'; +# no need to update orig, shouldn't change +# &lonnet_putfile(join("\n",@{$scanlines->{'orig'}}),$prefix.'orig_'. +# $ENV{'form.scantron_selectfile'}); + &lonnet_putfile(join("\n",@{$scanlines->{'corrected'}}), + $prefix.'corrected_'. + $ENV{'form.scantron_selectfile'}); + &lonnet_putfile(join("\n",@{$scanlines->{'skipped'}}), + $prefix.'skipped_'. + $ENV{'form.scantron_selectfile'}); + } + &Apache::lonnet::put('nohist_scantrondata',$scan_data,$cdom,$cname); +} + +sub scantron_get_line { + my ($scanlines,$scan_data,$i)=@_; + if (&should_be_skipped($scan_data,$i)) { return undef; } + if ($scanlines->{'skipped'}[$i]) { return undef; } + if ($scanlines->{'corrected'}[$i]) {return $scanlines->{'corrected'}[$i];} + return $scanlines->{'orig'}[$i]; +} + +sub get_todo_count { + my ($scanlines,$scan_data)=@_; + my $count=0; + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + $count++; + } + return $count; +} + +sub scantron_put_line { + my ($scanlines,$scan_data,$i,$newline,$skip)=@_; + if ($skip) { + $scanlines->{'skipped'}[$i]=$newline; + &allow_skipping($scan_data,$i); + return; + } + $scanlines->{'corrected'}[$i]=$newline; +} + +sub scantron_validate_ID { + my ($r,$currentphase) = @_; + + #get student info + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + + #get scantron line setup + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + + my %found=('ids'=>{},'usernames'=>{}); + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + my $id=$$scan_record{'scantron.ID'}; + my $found; + foreach my $checkid (keys(%idmap)) { + if (lc($checkid) eq lc($id)) { $found=$checkid;last; } + } + if ($found) { + my $username=$idmap{$found}; + if ($found{'ids'}{$found}) { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'duplicateID',$found); + return(1,$currentphase); + } elsif ($found{'usernames'}{$username}) { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'duplicateID',$username); + return(1,$currentphase); + } + #FIXME store away line we previously saw the ID on to use above + $found{'ids'}{$found}++; + $found{'usernames'}{$username}++; + } else { + if ($id =~ /^\s*$/) { + my $username=&scan_data($scan_data,"$i.user"); + if (defined($username) && $found{'usernames'}{$username}) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'duplicateID',$username); + return(1,$currentphase); + } elsif (!defined($username)) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'incorrectID'); + return(1,$currentphase); + } + $found{'usernames'}{$username}++; + } else { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'incorrectID'); + return(1,$currentphase); + } + } + } + + return (0,$currentphase+1); +} + +sub scantron_get_correction { + my ($r,$i,$scan_record,$scan_config,$line,$error,$arg)=@_; + +#FIXME in the case of a duplicated ID the previous line, probaly need +#to show both the current line and the previous one and allow skipping +#the previous one or the current one + + $r->print("

An error was detected ($error)"); + if ( defined($$scan_record{'scantron.PaperID'}) ) { + $r->print(" for PaperID ". + $$scan_record{'scantron.PaperID'}." \n"); + } else { + $r->print(" in scanline $i

".
+		  $line."
\n"); + } + $r->print(''."\n"); + $r->print(''."\n"); + if ($error =~ /ID$/) { + if ($error eq 'incorrectID') { + $r->print("The encoded ID is not in the classlist

\n"); + } elsif ($error eq 'duplicateID') { + $r->print("The encoded ID has also been used by a previous paper $arg

\n"); + } + $r->print("

The ID on the form is ". + $$scan_record{'scantron.ID'}."
\n"); + $r->print("The name on the paper is ". + $$scan_record{'scantron.LastName'}.",". + $$scan_record{'scantron.FirstName'}."

"); + $r->print("

How should I handle this?
\n"); + $r->print("\n

  • "); + #FIXME it would be nice if this sent back the user ID and + #could do partial userID matches + $r->print(&Apache::loncommon::selectstudent_link('scantronupload', + 'scantron_username','scantron_domain')); + $r->print(": "); + $r->print("\n@". + &Apache::loncommon::select_dom_form($ENV{'request.role.domain'},'scantron_domain')); + + $r->print('
  • '); + } elsif ($error =~ /CODE$/) { + if ($error eq 'incorrectCODE') { + $r->print("

    The encoded CODE is not in the list of possible CODEs

    \n"); + } elsif ($error eq 'duplicateCODE') { + $r->print("

    The encoded CODE has also been used by a previous paper ".join(', ',@{$arg}).", and CODEs are supposed to be unique

    \n"); + } + $r->print("

    The CODE on the form is ". + $$scan_record{'scantron.CODE'}."
    \n"); + $r->print("

    The ID on the form is ". + $$scan_record{'scantron.ID'}."
    \n"); + $r->print("The name on the paper is ". + $$scan_record{'scantron.LastName'}.",". + $$scan_record{'scantron.FirstName'}."

    "); + $r->print("

    How should I handle this?
    \n"); + $r->print("\n
    "); + my $i=0; + if ($error eq 'incorrectCODE') { + my ($max,$closest)=&scantron_get_closely_matching_CODEs($arg,$$scan_record{'scantron.CODE'}); + foreach my $testcode (@{$closest}) { + my $checked=''; + if (!$i) { $checked=' checked="on" '; } + $r->print(" Use the similar CODE ".$testcode." instead."); + $r->print("\n
    "); + $i++; + } + } + my $checked; if (!$i) { $checked=' checked="on" '; } + $r->print(" Use the CODE ".$$scan_record{'scantron.CODE'}." that is was on the paper, ignoring the error."); + $r->print("\n
    "); + + $r->print(< +function change_radio(field) { + var slct=document.scantronupload.scantron_CODE_resolution; + var i; + for (i=0;i +ENDSCRIPT + my $href="/adm/pickcode?". + "form=".&Apache::lonnet::escape("scantronupload"). + "&scantron_format=".&Apache::lonnet::escape($ENV{'form.scantron_format'}). + "&scantron_CODElist=".&Apache::lonnet::escape($ENV{'form.scantron_CODElist'}). + "&curCODE=".&Apache::lonnet::escape($$scan_record{'scantron.CODE'}). + "&scantron_selectfile=".&Apache::lonnet::escape($ENV{'form.scantron_selectfile'}); + $r->print(" Select a CODE from the list of all CODEs and use it. Selected CODE is "); + $r->print("\n
    "); + $r->print(" Use as the CODE."); + $r->print("\n

    "); + } elsif ($error eq 'doublebubble') { + $r->print("

    There have been multiple bubbles scanned for a some question(s)

    \n"); + $r->print(''); + $r->print("

    Please indicate which bubble should be used for grading

    "); + foreach my $question (@{$arg}) { + my $selected=$$scan_record{"scantron.$question.answer"}; + &scantron_bubble_selector($r,$scan_config,$question,split('',$selected)); + } + } elsif ($error eq 'missingbubble') { + $r->print("

    There have been no bubbles scanned for some question(s)

    \n"); + $r->print("

    Please indicate which bubble should be used for grading

    "); + $r->print("Some questions have no scanned bubbles\n"); + $r->print(''); + foreach my $question (@{$arg}) { + my $selected=$$scan_record{"scantron.$question.answer"}; + &scantron_bubble_selector($r,$scan_config,$question); + } + } else { + $r->print("\n
      "); + } + $r->print("\n
    "); + +} + +sub scantron_bubble_selector { + my ($r,$scan_config,$quest,@selected)=@_; + my $max=$$scan_config{'Qlength'}; + my @alphabet=('A'..'Z'); + $r->print(""); + for (my $i=0;$i<$max+1;$i++) { + $r->print(''); + } + $r->print(''); + for (my $i=0;$i<$max;$i++) { + $r->print('"); + } + $r->print(''); + $r->print('
    $quest'); + if ($selected[0] eq $alphabet[$i]) { $r->print('X'); shift(@selected) } + else { $r->print(' '); } + $r->print('
    '.$alphabet[$i]." No bubble
    '); +} + +sub num_matches { + my ($orig,$code) = @_; + my @code=split(//,$code); + my @orig=split(//,$orig); + my $same=0; + for (my $i=0;$i{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + my $CODE=$$scan_record{'scantron.CODE'}; + my $error=0; + if (!exists($allcodes{$CODE}) && !$$scan_record{'scantron.useCODE'}) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'incorrectCODE',\%allcodes); + return(1,$currentphase); + } + if (exists($usedCODEs{$CODE}) + && $ENV{'form.scantron_CODEunique'} eq 'yes' + && !$$scan_record{'scantron.CODE_ignore_dup'}) { + &scantron_get_correction($r,$i,$scan_record, + \%scantron_config, + $line,'duplicateCODE',$usedCODEs{$CODE}); + return(1,$currentphase); + } + push (@{$usedCODEs{$CODE}},$$scan_record{'scantron.PaperID'}); + } + return (0,$currentphase+1); +} + +sub scantron_validate_doublebubble { + my ($r,$currentphase) = @_; + #get student info + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + + #get scantron line setup + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + if (!defined($$scan_record{'scantron.doubleerror'})) { next; } + &scantron_get_correction($r,$i,$scan_record,\%scantron_config,$line, + 'doublebubble', + $$scan_record{'scantron.doubleerror'}); + return (1,$currentphase); + } + return (0,$currentphase+1); +} + +sub scantron_get_maxbubble { + my ($r)=@_; + if (defined($ENV{'form.scantron_maxbubble'}) && + $ENV{'form.scantron_maxbubble'}) { + return $ENV{'form.scantron_maxbubble'}; + } + my $navmap=Apache::lonnavmaps::navmap->new(); + my (undef,undef,$sequence)= + &Apache::lonnet::decode_symb($ENV{'form.selectpage'}); + my $map=$navmap->getResourceByUrl($sequence); + my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); + &Apache::lonnet::delenv('form.counter'); + foreach my $resource (@resources) { + my $result=&Apache::lonnet::ssi($resource->src()); + } + &Apache::lonnet::delenv('scantron\.'); + my $envfile=$ENV{'user.environment'}; + $envfile=~/\/([^\/]+)\.id$/; + $envfile=$1; + &Apache::lonnet::transfer_profile_to_env($r->dir_config('lonIDsDir'), + $envfile); + $ENV{'form.scantron_maxbubble'}=$ENV{'form.counter'}-1; + return $ENV{'form.scantron_maxbubble'}; +} + +sub scantron_validate_missingbubbles { + my ($r,$currentphase) = @_; + #get student info + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + + #get scantron line setup + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + my $max_bubble=&scantron_get_maxbubble(); + if (!$max_bubble) { $max_bubble=2**31; } + for (my $i=0;$i<=$scanlines->{'count'};$i++) { + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + if (!defined($$scan_record{'scantron.missingerror'})) { next; } + my @to_correct; + foreach my $missing (@{$$scan_record{'scantron.missingerror'}}) { + if ($missing > $max_bubble) { next; } + push(@to_correct,$missing); + } + if (@to_correct) { + &scantron_get_correction($r,$i,$scan_record,\%scantron_config, + $line,'missingbubble',\@to_correct); + return (1,$currentphase); + } + + } + return (0,$currentphase+1); +} + +sub scantron_process_students { + my ($r) = @_; + my (undef,undef,$sequence)=&Apache::lonnet::decode_symb($ENV{'form.selectpage'}); + my ($symb,$url)=&get_symb_and_url($r); + if (!$symb) {return '';} + my $default_form_data=&defaultFormData($symb,$url); + + my %scantron_config=&get_scantron_config($ENV{'form.scantron_format'}); + my ($scanlines,$scan_data)=&scantron_getfile(); + my $classlist=&Apache::loncoursedata::get_classlist(); + my %idmap=&username_to_idmap($classlist); + my $navmap=Apache::lonnavmaps::navmap->new(); + my $map=$navmap->getResourceByUrl($sequence); + my @resources=$navmap->retrieveResources($map,\&scantron_filter,1,0); +# $r->print("geto ".scalar(@resources)."
    "); + my $result= < + + $default_form_data +SCANTRONFORM + $r->print($result); + + my @delayqueue; + my %completedstudents; + + my $count=&get_todo_count($scanlines,$scan_data); + my %prog_state=&Apache::lonhtmlcommon::Create_PrgWin($r,'Scantron Status', + 'Scantron Progress',$count, + 'inline',undef,'scantronupload'); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state, + 'Processing first student'); + my $start=&Time::HiRes::time(); + my $i=-1; + my ($uname,$udom,$started); + while ($i<$scanlines->{'count'}) { + ($uname,$udom)=('',''); + $i++; + my $line=&scantron_get_line($scanlines,$scan_data,$i); + if ($line=~/^[\s\cz]*$/) { next; } + if ($started) { + &Apache::lonhtmlcommon::Increment_PrgWin($r,\%prog_state, + 'last student'); + } + $started=1; + my $scan_record=&scantron_parse_scanline($line,$i,\%scantron_config, + $scan_data); + unless ($uname=&scantron_find_student($scan_record,$scan_data, + \%idmap,$i)) { + &scantron_add_delay(\@delayqueue,$line, + 'Unable to find a student that matches',1); + next; + } + if (exists $completedstudents{$uname}) { + &scantron_add_delay(\@delayqueue,$line, + 'Student '.$uname.' has multiple sheets',2); + next; + } + ($uname,$udom)=split(/:/,$uname); + &Apache::lonnet::delenv('form.counter'); + &Apache::lonnet::appenv(%$scan_record); + + my $i=0; + foreach my $resource (@resources) { + $i++; + my %form=('submitted' =>'scantron', + 'grade_target' =>'grade', + 'grade_username'=>$uname, + 'grade_domain' =>$udom, + 'grade_courseid'=>$ENV{'request.course.id'}, + 'grade_symb' =>$resource->symb()); + if (exists($scan_record->{'scantron.CODE'}) && + $scan_record->{'scantron.CODE'}) { + $form{'CODE'}=$scan_record->{'scantron.CODE'}; + } + my $result=&Apache::lonnet::ssi($resource->src(),%form); + if (&Apache::loncommon::connection_aborted($r)) { last; } + } + $completedstudents{$uname}={'line'=>$line}; + if (&Apache::loncommon::connection_aborted($r)) { last; } + } continue { + &Apache::lonnet::delenv('form.counter'); + &Apache::lonnet::delenv('scantron\.'); + } + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); +# my $lasttime = &Time::HiRes::time()-$start; +# $r->print("

    took $lasttime

    "); + + $navmap->untieHashes(); + $r->print(""); + $r->print(&show_grading_menu_form($symb,$url)); + return ''; +} + +sub scantron_upload_scantron_data { + my ($r)=@_; + $r->print(&Apache::loncommon::coursebrowser_javascript($ENV{'request.role.domain'})); + my $select_link=&Apache::loncommon::selectcourse_link('rules','courseid', + 'domainid', + 'coursename'); + my $domsel=&Apache::loncommon::select_dom_form($ENV{'request.role.domain'}, + 'domainid'); + my $default_form_data=&defaultFormData(&get_symb_and_url($r,1)); + $r->print(< + 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(); + } + + +
    +$default_form_data + + + + + + +
    $select_link
    Course ID:
    Course Name:
    Domain: $domsel
    File to upload:
    + + +
    +UPLOAD + return ''; +} + +sub scantron_upload_scantron_data_save { + my($r)=@_; + my ($symb,$url)=&get_symb_and_url($r,1); + my $doanotherupload= + '
    '."\n". + ''."\n". + ''."\n". + '
    '."\n"; + if (!&Apache::lonnet::allowed('usc',$ENV{'form.domainid'}) && + !&Apache::lonnet::allowed('usc', + $ENV{'form.domainid'}.'_'.$ENV{'form.courseid'})) { + $r->print("You are not allowed to upload Scantron data to the requested course.
    "); + if ($symb) { + $r->print(&show_grading_menu_form($symb,$url)); + } else { + $r->print($doanotherupload); + } + return ''; + } + my %coursedata=&Apache::lonnet::coursedescription($ENV{'form.domainid'}.'_'.$ENV{'form.courseid'}); + $r->print("Doing upload to ".$coursedata{'description'}."
    "); + my $home=&Apache::lonnet::homeserver($ENV{'form.courseid'}, + $ENV{'form.domainid'}); + my $fname=$ENV{'form.upfile.filename'}; + #FIXME + #copied from lonnet::userfileupload() + #make that function able to target a specified course + # Replace Windows backslashes by forward slashes + $fname=~s/\\/\//g; + # Get rid of everything but the actual filename + $fname=~s/^.*\/([^\/]+)$/$1/; + # Replace spaces by underscores + $fname=~s/\s+/\_/g; + # Replace all other weird characters by nothing + $fname=~s/[^\w\.\-]//g; + # See if there is anything left + unless ($fname) { return 'error: no uploaded file'; } + my $uploadedfile=$fname; + $fname='scantron_orig_'.$fname; + if (length($ENV{'form.upfile'}) < 2) { + $r->print("Error: The file you attempted to upload, ".&HTML::Entities::encode($ENV{'form.upfile.filename'},'<>&"').", contained no information. Please check that you entered the correct filename."); + } else { + my $result=&Apache::lonnet::finishuserfileupload($ENV{'form.courseid'},$ENV{'form.domainid'},$home,'upfile',$fname); + if ($result =~ m|^/uploaded/|) { + $r->print("Success: Successfully uploaded ".(length($ENV{'form.upfile'})-1)." bytes of data into location ".$result.""); + } else { + $r->print("Error: An error (".$result.") occurred when attempting to upload the file, ".&HTML::Entities::encode($ENV{'form.upfile.filename'},'<>&"').""); + } + } + if ($symb) { + $r->print(&scantron_selectphase($r,$uploadedfile)); + } else { + $r->print($doanotherupload); + } + return ''; +} + +sub valid_file { + my ($requested_file)=@_; + foreach my $filename (sort(&scantron_filenames())) { + &Apache::lonnet::logthis("$requested_file $filename"); + if ($requested_file eq $filename) { return 1; } + } + return 0; +} + +sub scantron_download_scantron_data { + my ($r)=@_; + my $default_form_data=&defaultFormData(&get_symb_and_url($r,1)); + my $cname=$ENV{'course.'.$ENV{'request.course.id'}.'.num'}; + my $cdom=$ENV{'course.'.$ENV{'request.course.id'}.'.domain'}; + my $file=$ENV{'form.scantron_selectfile'}; + if (! &valid_file($file)) { + $r->print(< + The requested file name was invalid. +

    +ERROR + $r->print(&show_grading_menu_form(&get_symb_and_url($r,1))); + return; + } + my $orig='/uploaded/'.$cdom.'/'.$cname.'/scantron_orig_'.$file; + my $corrected='/uploaded/'.$cdom.'/'.$cname.'/scantron_corrected_'.$file; + my $skipped='/uploaded/'.$cdom.'/'.$cname.'/scantron_skipped_'.$file; + &Apache::lonnet::allowuploaded('/adm/grades',$orig); + &Apache::lonnet::allowuploaded('/adm/grades',$corrected); + &Apache::lonnet::allowuploaded('/adm/grades',$skipped); + $r->print(< + Original file as uploaded by the scantron office. +

    +

    + Corrections, a file of corrected records that were used in grading. +

    +

    + Skipped, a file of records that were skipped. +

    +DOWNLOAD + $r->print(&show_grading_menu_form(&get_symb_and_url($r,1))); + return ''; +} + +#-------- end of section for handling grading scantron forms ------- +# +#------------------------------------------------------------------- + + +#-------------------------- Menu interface ------------------------- +# +#--- Show a Grading Menu button - Calls the next routine --- +sub show_grading_menu_form { + my ($symb,$url)=@_; + my $result.='
    '."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + '
    '."\n"; + return $result; +} + +# -- Retrieve choices for grading form +sub savedState { + my %savedState = (); + if ($ENV{'form.saveState'}) { + foreach (split(/:/,$ENV{'form.saveState'})) { + my ($key,$value) = split(/=/,$_,2); + $savedState{$key} = $value; + } + } + return \%savedState; +} + +#--- Displays the main menu page ------- +sub gradingmenu { + my ($request) = @_; + my ($symb,$url)=&get_symb_and_url($request); + if (!$symb) {return '';} + my $probTitle = &Apache::lonnet::gettitle($symb); + + $request->print(< + function checkChoice(formname,val,cmdx) { + if (val <= 2) { + var cmd = radioSelection(formname.radioChoice); + var cmdsave = cmd; + } else { + cmd = cmdx; + cmdsave = 'submission'; + } + formname.command.value = cmd; + formname.saveState.value = "saveCmd="+cmdsave+":saveSec="+pullDownSelection(formname.section)+ + ":saveSub="+pullDownSelection(formname.submitonly)+":saveStatus="+pullDownSelection(formname.Status); + if (val < 5) formname.submit(); + if (val == 5) { + if (!checkReceiptNo(formname,'notOK')) { return false;} + formname.submit(); + } + } + + function checkReceiptNo(formname,nospace) { + var receiptNo = formname.receipt.value; + var checkOpt = false; + if (nospace == "OK" && isNaN(receiptNo)) {checkOpt = true;} + if (nospace == "notOK" && (isNaN(receiptNo) || receiptNo == "")) {checkOpt = true;} + if (checkOpt) { + alert("Please enter a receipt number given by a student in the receipt box."); + formname.receipt.value = ""; + formname.receipt.focus(); + return false; + } + return true; + } + +GRADINGMENUJS + &commonJSfunctions($request); + my $result='

     Manual Grading/View Submission

    '; + my ($table,undef,$hdgrade) = &showResourceInfo($url,$probTitle); + $result.=$table; + my (undef,$sections) = &getclasslist('all','0'); + my $savedState = &savedState(); + my $saveCmd = ($$savedState{'saveCmd'} eq '' ? 'submission' : $$savedState{'saveCmd'}); + my $saveSec = ($$savedState{'saveSec'} eq '' ? 'all' : $$savedState{'saveSec'}); + my $saveSub = ($$savedState{'saveSub'} eq '' ? 'all' : $$savedState{'saveSub'}); + my $saveStatus = ($$savedState{'saveStatus'} eq '' ? 'Active' : $$savedState{'saveStatus'}); + + $result.='
    '."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n". + ''."\n"; + + $result.='
    '."\n". + ''."\n". + '
    '."\n". + ' Select a Grading/Viewing Option
    '."\n"; + + $result.=''; + $result.=''; + + $result.=''."\n"; + + $result.=''."\n"; + + $result.=''."\n"; + + $result.='
    '."\n". + ' '.&mt('Select Section').':   '; + + $result.=&mt('Student Status').':'.&Apache::lonhtmlcommon::StatusOptions($saveStatus,undef,1,undef); + + $result.='
    '. + ' '.''.&mt('Current Resource').': '.&mt('For one or more students'). + '
    '. + ' '. + 'Current Resource: For all students in selected section or course
    '. + ' '. + 'The complete set/page/sequence: For one student

    '. + ''. + '
    '."\n"; + + $result.='
    '; + + $result.=''; + $result.=''."\n"; + + $result.=''."\n"; + + if ((&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'})) && ($symb)) { + $result.=''."\n"; + } + + $result.='
    '. + ''. + ' '.&mt('scores from file').'
    '. + ' scantron forms
    '. + ''. + ' '.&mt('receipt').': '. + &Apache::lonnet::recprefix($ENV{'request.course.id'}). + '-'. + '
    '."\n". + '
    '."\n". + '
    '."\n"; + return $result; } sub handler { my $request=$_[0]; - + + undef(%perm); if ($ENV{'browser.mathml'}) { - $request->content_type('text/xml'); + &Apache::loncommon::content_type($request,'text/xml'); } else { - $request->content_type('text/html'); + &Apache::loncommon::content_type($request,'text/html'); } $request->send_http_header; - return OK if $request->header_only; + return '' if $request->header_only; &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); my $url=$ENV{'form.url'}; my $symb=$ENV{'form.symb'}; - my $command=$ENV{'form.command'}; + my @commands=&Apache::loncommon::get_env_multiple('form.command'); + my $command=$commands[0]; + if ($#commands > 0) { + &Apache::lonnet::logthis("grades got multiple commands ".join(':',@commands)); + } if (!$url) { my ($temp1,$temp2); - ($temp1,$temp2,$ENV{'form.url'})=split(/___/,$symb); + ($temp1,$temp2,$ENV{'form.url'})=&Apache::lonnet::decode_symb($symb); $url = $ENV{'form.url'}; } &send_header($request); - if ($url eq '' && $symb eq '') { + if ($url eq '' && $symb eq '' && $command eq '') { if ($ENV{'user.adv'}) { if (($ENV{'form.codeone'}) && ($ENV{'form.codetwo'}) && ($ENV{'form.codethree'})) { @@ -2042,51 +4999,65 @@ sub handler { my ($tsymb,$tuname,$tudom,$tcrsid)= &Apache::lonnet::checkin($token); if ($tsymb) { - my ($map,$id,$url)=split(/\_\_\_/,$tsymb); + my ($map,$id,$url)=&Apache::lonnet::decode_symb($tsymb); if (&Apache::lonnet::allowed('mgr',$tcrsid)) { - $request->print( - &Apache::lonnet::ssi('/res/'.$url, - ('grade_username' => $tuname, - 'grade_domain' => $tudom, - 'grade_courseid' => $tcrsid, - 'grade_symb' => $tsymb))); + $request->print(&Apache::lonnet::ssi_body('/res/'.$url, + ('grade_username' => $tuname, + 'grade_domain' => $tudom, + 'grade_courseid' => $tcrsid, + 'grade_symb' => $tsymb))); } else { - $request->print('

    Not authorized: '.$token.'

    '); - } + $request->print('

    Not authorized: '.$token.'

    '); + } } else { - $request->print('

    Not a valid DocID: '.$token.'

    '); + $request->print('

    Not a valid DocID: '.$token.'

    '); } } else { $request->print(&Apache::lonxml::tokeninputfield()); } } } else { - #&Apache::lonhomework::showhashsubset(\%ENV,'^form'); - $Apache::grades::viewgrades=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}); - if ($command eq 'submission') { - &listStudents($request) if ($ENV{'form.student'} eq ''); - &submission($request,0,0) if ($ENV{'form.student'} ne ''); - } elsif ($command eq 'processGroup') { + if (!($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}))) { + if ($perm{'vgr'}=&Apache::lonnet::allowed('vgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) { + $perm{'vgr_section'}=$ENV{'request.course.sec'}; + } else { + delete($perm{'vgr'}); + } + } + if (!($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}))) { + if ($perm{'mgr'}=&Apache::lonnet::allowed('mgr',$ENV{'request.course.id'}.'/'.$ENV{'request.course.sec'})) { + $perm{'mgr_section'}=$ENV{'request.course.sec'}; + } else { + delete($perm{'mgr'}); + } + } + if ($command eq 'submission' && $perm{'vgr'}) { + ($ENV{'form.student'} eq '' ? &listStudents($request) : &submission($request,0,0)); + } elsif ($command eq 'pickStudentPage' && $perm{'vgr'}) { + &pickStudentPage($request); + } elsif ($command eq 'displayPage' && $perm{'vgr'}) { + &displayPage($request); + } elsif ($command eq 'gradeByPage' && $perm{'mgr'}) { + &updateGradeByPage($request); + } elsif ($command eq 'processGroup' && $perm{'vgr'}) { &processGroup($request); - } elsif ($command eq 'gradingmenu') { + } elsif ($command eq 'gradingmenu' && $perm{'vgr'}) { $request->print(&gradingmenu($request)); - } elsif ($command eq 'viewgrades') { + } elsif ($command eq 'viewgrades' && $perm{'vgr'}) { $request->print(&viewgrades($request)); - } elsif ($command eq 'handgrade') { + } elsif ($command eq 'handgrade' && $perm{'mgr'}) { $request->print(&processHandGrade($request)); - } elsif ($command eq 'editgrades') { + } elsif ($command eq 'editgrades' && $perm{'mgr'}) { $request->print(&editgrades($request)); - } elsif ($command eq 'verify') { + } elsif ($command eq 'verify' && $perm{'vgr'}) { $request->print(&verifyreceipt($request)); - } elsif ($command eq 'csvupload') { + } elsif ($command eq 'csvform' && $perm{'mgr'}) { + $request->print(&upcsvScores_form($request)); + } elsif ($command eq 'csvupload' && $perm{'mgr'}) { $request->print(&csvupload($request)); - } elsif ($command eq 'viewclasslist') { - $request->print(&viewclasslist($request)); - } elsif ($command eq 'csvuploadmap') { + } elsif ($command eq 'csvuploadmap' && $perm{'mgr'} ) { $request->print(&csvuploadmap($request)); -# } elsif ($command eq 'receiptInput') { -# &receiptInput($request); - } elsif ($command eq 'csvuploadassign') { + } elsif ($command eq 'csvuploadassign' && $perm{'mgr'}) { if ($ENV{'form.associate'} ne 'Reverse Association') { $request->print(&csvuploadassign($request)); } else { @@ -2097,12 +5068,48 @@ sub handler { } $request->print(&csvuploadmap($request)); } - } else { - $request->print("Unknown action: $command:"); + } elsif ($command eq 'scantron_selectphase' && $perm{'mgr'}) { + $request->print(&scantron_selectphase($request)); + } elsif ($command eq 'scantron_warning' && $perm{'mgr'}) { + $request->print(&scantron_do_warning($request)); + } elsif ($command eq 'scantron_validate' && $perm{'mgr'}) { + $request->print(&scantron_validate_file($request)); + } elsif ($command eq 'scantron_process' && $perm{'mgr'}) { + $request->print(&scantron_process_students($request)); + } elsif ($command eq 'scantronupload' && + (&Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})|| + &Apache::lonnet::allowed('usc',$ENV{'request.course.id'}))) { + $request->print(&scantron_upload_scantron_data($request)); + } elsif ($command eq 'scantronupload_save' && + (&Apache::lonnet::allowed('usc',$ENV{'request.role.domain'})|| + &Apache::lonnet::allowed('usc',$ENV{'request.course.id'}))) { + $request->print(&scantron_upload_scantron_data_save($request)); + } elsif ($command eq 'scantron_download' && + &Apache::lonnet::allowed('usc',$ENV{'request.course.id'})) { + $request->print(&scantron_download_scantron_data($request)); + } elsif ($command) { + $request->print("Access Denied ($command)"); } } &send_footer($request); - return OK; + return ''; +} + +sub send_header { + my ($request)= @_; + $request->print(&Apache::lontexconvert::header()); +# $request->print(" +#"); + $request->print(&Apache::loncommon::bodytag('Grading')); + $request->rflush(); +} + +sub send_footer { + my ($request)= @_; + $request->print(''); } 1; 500 Internal Server Error

    Internal Server Error

    The server encountered an internal error or misconfiguration and was unable to complete your request.

    Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

    More information about this error may be available in the server error log.