--- loncom/homework/grades.pm 2019/02/23 15:18:33 1.760
+++ loncom/homework/grades.pm 2025/06/28 14:35:00 1.811
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.760 2019/02/23 15:18:33 raeburn Exp $
+# $Id: grades.pm,v 1.811 2025/06/28 14:35:00 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -47,10 +47,12 @@ use Apache::lonstathelpers;
use Apache::lonquickgrades;
use Apache::bridgetask();
use Apache::lontexconvert();
+use Apache::loncourserespicker;
use String::Similarity;
use HTML::Parser();
use File::MMagic;
use LONCAPA;
+use LONCAPA::ltiutils();
use POSIX qw(floor);
@@ -65,7 +67,7 @@ my $ssi_retries = 5;
my $ssi_error;
my $ssi_error_resource;
my $ssi_error_message;
-
+my $registered_cleanup;
sub ssi_with_retries {
my ($resource, $retries, %form) = @_;
@@ -147,7 +149,7 @@ sub nameUserString {
}
#--- Get the partlist and the response type for a given problem. ---
-#--- Indicate if a response type is coded handgraded or not. ---
+#--- Count responseIDs, essayresponse items, and dropbox items ---
#--- Sets response_error pointer to "1" if navmaps object broken ---
sub response_type {
my ($symb,$response_error) = @_;
@@ -165,6 +167,7 @@ sub response_type {
return;
}
my $partlist = $res->parts();
+ my ($numresp,$numessay,$numdropbox) = (0,0,0);
my %vPart =
map { $_ => 1 } (&Apache::loncommon::get_env_multiple('form.vPart'));
my (%response_types,%handgrade);
@@ -174,13 +177,20 @@ sub response_type {
my @types = $res->responseType($part);
my @ids = $res->responseIds($part);
for (my $i=0; $i < scalar(@ids); $i++) {
+ $numresp ++;
$response_types{$part}{$ids[$i]} = $types[$i];
+ if ($types[$i] eq 'essay') {
+ $numessay ++;
+ if (&Apache::lonnet::EXT("resource.$part".'_'.$ids[$i].".uploadedfiletypes",$symb)) {
+ $numdropbox ++;
+ }
+ }
$handgrade{$part.'_'.$ids[$i]} =
&Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i].
'.handgrade',$symb);
}
}
- return ($partlist,\%handgrade,\%response_types);
+ return ($partlist,\%handgrade,\%response_types,$numresp,$numessay,$numdropbox);
}
sub flatten_responseType {
@@ -207,6 +217,129 @@ sub get_display_part {
return $display;
}
+#--- Show parts and response type
+sub showResourceInfo {
+ my ($symb,$partlist,$responseType,$formname,$checkboxes,$uploads) = @_;
+ unless ((ref($partlist) eq 'ARRAY') && (ref($responseType) eq 'HASH')) {
+ return ' ';
+ }
+ my $coltitle = &mt('Problem Part Shown');
+ if ($checkboxes) {
+ $coltitle = &mt('Problem Part');
+ } else {
+ my $checkedparts = 0;
+ foreach my $partid (&Apache::loncommon::get_env_multiple('form.vPart')) {
+ if (grep(/^\Q$partid\E$/,@{$partlist})) {
+ $checkedparts ++;
+ }
+ }
+ if ($checkedparts == scalar(@{$partlist})) {
+ return ' ';
+ }
+ if ($uploads) {
+ $coltitle = &mt('Problem Part Selected');
+ }
+ }
+ my $result = '
';
+ if ($checkboxes) {
+ my $legend = &mt('Parts to display');
+ if ($uploads) {
+ $legend = &mt('Part(s) with dropbox');
+ }
+ $result .= '';
+ }
+ $result .= '
';
+ if (!keys(%partsseen)) {
+ $result = '';
+ if ($uploads) {
+ return ''.
+ '
'.
+ &mt('No dropbox items or essayresponse items with uploadedfiletypes set.').
+ '
';
+ } else {
+ return ' ';
+ }
+ }
+ return $result;
+}
+
+sub part_selector_js {
+ my $js = <<"END";
+function toggleParts(formname) {
+ if (document.getElementById('LC_partselector')) {
+ var index = '';
+ if (document.forms.length) {
+ for (var i=0; i 1)) {
+ for (var i=0; i $scancode,};
}
}
- my $analyze =
+ my $analyze =
&get_analyze($symb,$uname,$udom,undef,$add_to_form,
undef,undef,undef,$bubbles_per_row);
if (ref($analyze) eq 'HASH') {
@@ -345,7 +478,7 @@ sub cleanRecord {
if ($response =~ /^(option|rank)$/) {
my %answer=&Apache::lonnet::str2hash($answer);
my @answer = %answer;
- %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
+ %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
my %grading=&Apache::lonnet::str2hash($record->{$version."resource.$partid.$respid.submissiongrading"});
my ($toprow,$bottomrow);
foreach my $foil (@$order) {
@@ -363,7 +496,7 @@ sub cleanRecord {
} elsif ($response eq 'match') {
my %answer=&Apache::lonnet::str2hash($answer);
my @answer = %answer;
- %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer;
+ %answer = map {&HTML::Entities::encode($_, '"<>&')} @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);
@@ -422,7 +555,6 @@ sub cleanRecord {
}
$answer = &Apache::lontexconvert::msgtexconverted($answer);
return '
'.&keywords_highlight($answer).'
';
-
} elsif ( $response eq 'organic') {
my $result=&mt('Smile representation: [_1]',
'"'.&HTML::Entities::encode($answer, '"<>&').'"');
@@ -506,7 +638,7 @@ COMMONJSFUNCTIONS
#--- Dumps the class list with usernames,list of sections,
#--- section, ids and fullnames for each user.
sub getclasslist {
- my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus) = @_;
+ my ($getsec,$filterbyaccstatus,$getgroup,$symb,$submitonly,$filterbysubmstatus,$filterbypbid,$possibles) = @_;
my @getsec;
my @getgroup;
my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -534,12 +666,16 @@ sub getclasslist {
#
my %sections;
my %fullnames;
+ my %passback;
my ($cdom,$cnum,$partlist);
if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
$cdom = $env{"course.$env{'request.course.id'}.domain"};
$cnum = $env{"course.$env{'request.course.id'}.num"};
my $res_error;
- ($partlist,my $handgrade,my $responseType) = &response_type($symb,\$res_error);
+ ($partlist) = &response_type($symb,\$res_error);
+ } elsif ($filterbypbid) {
+ $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ $cnum = $env{"course.$env{'request.course.id'}.num"};
}
foreach my $student (keys(%$classlist)) {
my $end =
@@ -626,6 +762,27 @@ sub getclasslist {
}
}
}
+ if ($filterbypbid) {
+ if (ref($possibles) eq 'HASH') {
+ unless (exists($possibles->{$student})) {
+ delete($classlist->{$student});
+ next;
+ }
+ }
+ my $udom =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
+ my $uname =
+ $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
+ if (($udom ne '') && ($uname ne '')) {
+ my %pbinfo = &Apache::lonnet::get('nohist_'.$cdom.'_'.$cnum.'_linkprot_pb',[$filterbypbid],$udom,$uname);
+ if (ref($pbinfo{$filterbypbid}) eq 'ARRAY') {
+ $passback{$student} = $pbinfo{$filterbypbid};
+ } else {
+ delete($classlist->{$student});
+ next;
+ }
+ }
+ }
$section = ($section ne '' ? $section : 'none');
if (&canview($section)) {
if (!@getsec || grep(/^\Q$section\E$/,@getsec)) {
@@ -641,7 +798,7 @@ sub getclasslist {
}
}
my @sections = sort(keys(%sections));
- return ($classlist,\@sections,\%fullnames);
+ return ($classlist,\@sections,\%fullnames,\%passback);
}
sub canmodify {
@@ -655,7 +812,7 @@ sub canmodify {
#can modify the requested section
return 1;
} else {
- # can't modify the request section
+ # can't modify the requested section
return 0;
}
}
@@ -668,19 +825,19 @@ sub canview {
my ($sec)=@_;
if ($perm{'vgr'}) {
if (!defined($perm{'vgr_section'})) {
- # can modify whole class
+ # can view whole class
return 1;
} else {
if ($sec eq $perm{'vgr_section'}) {
- #can modify the requested section
+ #can view the requested section
return 1;
} else {
- # can't modify the request section
+ # can't view the requested section
return 0;
}
}
}
- #can't modify
+ #can't view
return 0;
}
@@ -724,14 +881,20 @@ sub jscriptNform {
-# Given the score (as a number [0-1] and the weight) what is the final
-# point value? This function will round to the nearest tenth, third,
-# or quarter if one of those is within the tolerance of .00001.
+# Given the score (as a number [0-1], the weight, and a posible
+# reduction for submission between duedate and overduedate)
+# what is the final point value? This function will round to
+# the nearest tenth, third, or quarter if one of those is
+# within the tolerance of .00001.
sub compute_points {
- my ($score, $weight) = @_;
+ my ($score, $weight, $latefrac) = @_;
my $tolerance = .00001;
my $points = $score * $weight;
+ if (($latefrac ne '') &&
+ ($latefrac < 1) && ($latefrac >= 0)) {
+ $points = $points * $latefrac;
+ }
# Check for nearness to 1/x.
my $check_for_nearness = sub {
@@ -821,14 +984,14 @@ sub initialverifyreceipt {
#--- Check whether a receipt number is valid.---
sub verifyreceipt {
- my ($request,$symb) = @_;
+ my ($request,$symb) = @_;
my $courseid = $env{'request.course.id'};
my $receipt = &Apache::lonnet::recprefix($courseid).'-'.
$env{'form.receipt'};
$receipt =~ s/[^\-\d]//g;
- my $title.=
+ my $title =
'
'.
&mt('Verifying Receipt Number [_1]',$receipt).
'
'."\n";
@@ -904,12 +1067,883 @@ sub verifyreceipt {
return $string;
}
+#-------------------------------------------------------------------
+
+#------------------------------------------- Grade Passback Routines
+#
+
+sub initialpassback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my %passback = &Apache::lonnet::dump('nohist_linkprot_passback',$cdom,$cnum);
+ my $readonly;
+ unless ($perm{'mgr'}) {
+ $readonly = 1;
+ }
+ my $formname = 'initialpassback';
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my $output;
+ if (!defined($navmap)) {
+ if ($crstype eq 'Community') {
+ $output = &mt('Unable to retrieve information about community contents');
+ } else {
+ $output = &mt('Unable to retrieve information about course contents');
+ }
+ return '
'.$output.'
';
+ }
+ return &Apache::loncourserespicker::create_picker($navmap,'passback',$formname,$crstype,undef,
+ undef,undef,undef,undef,undef,undef,
+ \%passback,$readonly);
+}
+
+sub passback_filters {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launcher,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launcher,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ my $result;
+ if ($launcher ne '') {
+ $result = &launcher_info_box($launcher,$appname,$setter,$linkuri,$scope).
+ '
'.&mt('Set criteria to use to list students for possible passback of scores, then push Next [_1]',
+ '→').
+ '
';
+ }
+ $result .= ''."\n";
+ return $result;
+}
+
+sub names_for_passback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launcher,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launcher,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ my ($result,$ctr,$newcommand,$submittext);
+ if ($launcher ne '') {
+ $result = &launcher_info_box($launcher,$appname,$setter,$linkuri,$scope);
+ }
+ $ctr = 0;
+ my @statuses = &Apache::loncommon::get_env_multiple('form.Status');
+ my $stu_status = join(':',@statuses);
+ $result .= ''."\n";
+ return $result;
+}
+
+sub do_passback {
+ my ($request,$symb) = @_;
+ my $cdom = $env{"course.$env{'request.course.id'}.domain"};
+ my $cnum = $env{"course.$env{'request.course.id'}.num"};
+ my $crstype = &Apache::loncommon::course_type();
+ my ($launchsymb,$appname,$setter,$linkuri,$linkprotector,$scope,$chosen);
+ if ($env{'form.passback'} ne '') {
+ $chosen = &unescape($env{'form.passback'});
+ ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ ($launchsymb,$appname,$setter) = &get_passback_launcher($cdom,$cnum,$chosen);
+ }
+ if ($launchsymb ne '') {
+ $request->print(&launcher_info_box($launchsymb,$appname,$setter,$linkuri,$scope));
+ }
+ my $error;
+ if ($perm{'mgr'}) {
+ if ($launchsymb ne '') {
+ my @poss_students = &Apache::loncommon::get_env_multiple('form.stuinfo');
+ if (@poss_students) {
+ my %possibles;
+ foreach my $item (@poss_students) {
+ my ($stuname,$studom) = split(/:/,$item,3);
+ $possibles{$stuname.':'.$studom} = 1;
+ }
+ my ($sections,$groups,$group_display,$disabled) = §ions_and_groups();
+ my ($classlist,undef,$fullname,$pbinfo) =
+ &getclasslist($sections,'1',$groups,'','','',$chosen,\%possibles);
+ if ((ref($classlist) eq 'HASH') && (ref($pbinfo) eq 'HASH')) {
+ my %passback = %{$pbinfo};
+ my (%tosend,%remotenotok,%scorenotok,%zeroposs,%nopbinfo);
+ foreach my $possible (keys(%possibles)) {
+ if ((exists($classlist->{$possible})) &&
+ (exists($passback{$possible})) && (ref($passback{$possible}) eq 'ARRAY')) {
+ $tosend{$possible} = 1;
+ }
+ }
+ if (keys(%tosend)) {
+ my ($lti_in_use,$crsdef);
+ my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
+ if ($ltitype eq 'c') {
+ my %crslti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+ $lti_in_use = $crslti{$ltinum};
+ $crsdef = 1;
+ } else {
+ my %domlti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
+ $lti_in_use = $domlti{$ltinum};
+ }
+ if (ref($lti_in_use) eq 'HASH') {
+ my $msgformat = $lti_in_use->{'passbackformat'};
+ my $keynum = $lti_in_use->{'cipher'};
+ my $scoretype = 'decimal';
+ if ($lti_in_use->{'scoreformat'} =~ /^(decimal|ratio|percentage)$/) {
+ $scoretype = $1;
+ }
+ my $pbmap;
+ if ($launchsymb =~ /\.(page|sequence)$/) {
+ $pbmap = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($launchsymb))[2]);
+ } else {
+ $pbmap = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($launchsymb))[0]);
+ }
+ $pbmap = &Apache::lonnet::clutter($pbmap);
+ my $pbscope;
+ if ($scope eq 'res') {
+ $pbscope = 'resource';
+ } elsif ($scope eq 'map') {
+ $pbscope = 'nonrec';
+ } elsif ($scope eq 'rec') {
+ $pbscope = 'map';
+ }
+ my %pb = &common_passback_info();
+ my $numstudents = scalar(keys(%tosend));
+ my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($request,$numstudents);
+ my $outcome = &Apache::loncommon::start_data_table().
+ &Apache::loncommon::start_data_table_header_row();
+ my $loop = 0;
+ while ($loop < 2) {
+ $outcome .= '
');
+ }
+ if ($noconfirm) {
+ $request->print(' '.&mt('Score receipt not confirmed by receiving CMS').':'.
+ '
'.$noconfirm.'
');
+ }
+ if ($noscore) {
+ $request->print(' '.&mt('Score computation or transmission failed').':'.
+ '
'.$noscore.'
');
+ }
+ $request->print('');
+ }
+ } else {
+ $error = &mt('Settings for deep-link launch target unavailable, so no scores were sent');
+ }
+ } else {
+ $error = &mt('No available students for whom scores can be sent.');
+ }
+ } else {
+ $error = &mt('Classlist could not be retrieved so no scores were sent.');
+ }
+ } else {
+ $error = &mt('No students selected to receive scores so none were sent.');
+ }
+ } else {
+ if ($env{'form.passback'}) {
+ $error = &mt('Deep-link launch target was invalid so no scores were sent.');
+ } else {
+ $error = &mt('Deep-link launch target was missing so no scores were sent.');
+ }
+ }
+ } else {
+ $error = &mt('You do not have permission to manage grades, so no scores were sent');
+ }
+ if ($error) {
+ $request->print('
'.$error.'
');
+ }
+ return;
+}
+
+sub get_passback_launcher {
+ my ($cdom,$cnum,$chosen) = @_;
+ my ($linkuri,$linkprotector,$scope) = split("\0",$chosen);
+ my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
+ my ($appname,$setter);
+ if ($ltitype eq 'c') {
+ my %lti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in course)';
+ }
+ }
+ } elsif ($ltitype eq 'd') {
+ my %lti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
+ if (ref($lti{$ltinum}) eq 'HASH') {
+ $appname = $lti{$ltinum}{'name'};
+ if ($appname) {
+ $setter = ' (defined in domain)';
+ }
+ }
+ }
+ my $launchsymb = &Apache::loncommon::symb_from_tinyurl($linkuri,$cnum,$cdom);
+ if ($launchsymb eq '') {
+ my %passback = &Apache::lonnet::dump('nohist_linkprot_passback',$cdom,$cnum);
+ foreach my $poss_symb (keys(%passback)) {
+ if (ref($passback{$poss_symb}) eq 'HASH') {
+ if (exists($passback{$poss_symb}{$chosen})) {
+ $launchsymb = $poss_symb;
+ last;
+ }
+ }
+ }
+ if ($launchsymb ne '') {
+ return ($launchsymb,$appname,$setter);
+ }
+ } else {
+ my %passback = &Apache::lonnet::get('nohist_linkprot_passback',[$launchsymb],$cdom,$cnum);
+ if (ref($passback{$launchsymb}) eq 'HASH') {
+ if (exists($passback{$launchsymb}{$chosen})) {
+ return ($launchsymb,$appname,$setter);
+ }
+ }
+ }
+ return ();
+}
+
+sub sections_and_groups {
+ my (@sections,@groups,$group_display);
+ @groups = &Apache::loncommon::get_env_multiple('form.group');
+ if (grep(/^all$/,@groups)) {
+ @groups = ('all');
+ $group_display = 'all';
+ } elsif (grep(/^none$/,@groups)) {
+ @groups = ('none');
+ $group_display = 'none';
+ } elsif (@groups > 0) {
+ $group_display = join(', ',@groups);
+ }
+ if ($env{'request.course.sec'} ne '') {
+ @sections = ($env{'request.course.sec'});
+ } else {
+ @sections = &Apache::loncommon::get_env_multiple('form.section');
+ }
+ my $disabled = ' disabled="disabled"';
+ if ($perm{'mgr'}) {
+ if (grep(/^all$/,@sections)) {
+ undef($disabled);
+ } else {
+ foreach my $sec (@sections) {
+ if (&canmodify($sec)) {
+ undef($disabled);
+ last;
+ }
+ }
+ }
+ }
+ if (grep(/^all$/,@sections)) {
+ @sections = ('all');
+ }
+ return(\@sections,\@groups,$group_display,$disabled);
+}
+
+sub launcher_info_box {
+ my ($launcher,$appname,$setter,$linkuri,$scope) = @_;
+ my $shownscope;
+ if ($scope eq 'res') {
+ $shownscope = &mt('Resource');
+ } elsif ($scope eq 'map') {
+ $shownscope = &mt('Folder');
+ } elsif ($scope eq 'rec') {
+ $shownscope = &mt('Folder + sub-folders');
+ }
+ return '
';
- next;
- }
- foreach my $submission (@$string) {
- my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
- if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
- my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
- # Similarity check
+ ''.&mt('Nothing submitted - no attempts.').'
';
+ next;
+ }
+ foreach my $submission (@$string) {
+ my ($partid,$respid) = ($submission =~ /^resource\.([^\.]*)\.([^\.]*)\.submission/);
+ if (join('_',@{$part}) ne ($partid.'_'.$respid)) { next; }
+ my ($ressub,$hide,$draft,$subval) = split(/:/,$submission,4);
+ # Similarity check
my $similar='';
my ($type,$trial,$rndseed);
if ($hide eq 'rand') {
$type = 'randomizetry';
- $trial = $record{"resource.$partid.tries"};
- $rndseed = $record{"resource.$partid.rndseed"};
+ $trial = $record->{"resource.$partid.tries"};
+ $rndseed = $record->{"resource.$partid.rndseed"};
}
- if ($env{'form.checkPlag'}) {
- my ($oname,$odom,$ocrsid,$oessay,$osim)=
- &most_similar($uname,$udom,$symb,$subval);
- if ($osim) {
- $osim=int($osim*100.0);
+ if ($env{'form.checkPlag'}) {
+ my ($oname,$odom,$ocrsid,$oessay,$osim)=
+ &most_similar($uname,$udom,$symb,$subval);
+ if ($osim) {
+ $osim=int($osim*100.0);
if ($hide eq 'anon') {
$similar=''.&mt("Essay was found to be similar to another essay submitted for this assignment.").' '.
&mt('As the current submission is for an anonymous survey, no other details are available.').'';
} else {
- $similar='';
+ $similar='';
if ($essayurl eq 'lib/templates/simpleproblem.problem') {
$similar .= '
'.
&mt('Essay is [_1]% similar to an essay by [_2]',
@@ -2436,8 +3703,8 @@ sub submission {
} else {
my %old_course_desc;
if ($ocrsid ne '') {
- if (ref($coursedesc_by_cid{$ocrsid}) eq 'HASH') {
- %old_course_desc = %{$coursedesc_by_cid{$ocrsid}};
+ if (ref($coursedesc_by_cid->{$ocrsid}) eq 'HASH') {
+ %old_course_desc = %{$coursedesc_by_cid->{$ocrsid}};
} else {
my $args;
if ($ocrsid ne $env{'request.course.id'}) {
@@ -2445,7 +3712,7 @@ sub submission {
}
%old_course_desc =
&Apache::lonnet::coursedescription($ocrsid,$args);
- $coursedesc_by_cid{$ocrsid} = \%old_course_desc;
+ $coursedesc_by_cid->{$ocrsid} = \%old_course_desc;
}
$similar .=
'
'.
''.&mt('Part: [_1]',$display_part).''.
' '.
'('.&mt('Response ID: [_1]',$respid).')'.
' ';
- my $files=&get_submitted_files($udom,$uname,$partid,$respid,\%record);
-
- if (@$files) {
+ my $files=&get_submitted_files($udom,$uname,$partid,$respid,$record);
+ if (@$files) {
if ($hide eq 'anon') {
$lastsubonly.=' '.&mt('[quant,_1,file] uploaded to this anonymous survey',scalar(@{$files}));
} else {
@@ -2494,162 +3761,37 @@ sub submission {
} else {
$lastsubonly .= &mt('Like all files provided by users, these files may contain viruses!');
}
- $lastsubonly .= '';
+ $lastsubonly .= '';
foreach my $file (@$files) {
&Apache::lonnet::allowuploaded('/adm/grades',$file);
$lastsubonly.=' '.$file.'';
}
}
- $lastsubonly.=' ';
+ $lastsubonly.=' ';
}
if ($hide eq 'anon') {
- $lastsubonly.=' '.&mt('Anonymous Survey').'';
+ $lastsubonly.=' '.&mt('Anonymous Survey').'';
} else {
- $lastsubonly.=' '.&mt('Submitted Answer:').' ';
+ $lastsubonly.=' '.&mt('Submitted Answer:').' ';
if ($draft) {
$lastsubonly.= ' '.&mt('Draft Copy').'';
}
$subval =
- &cleanRecord($subval,$responsetype,$symb,$partid,
- $respid,\%record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
+ &cleanRecord($subval,$responsetype,$symb,$partid,
+ $respid,$record,$order,undef,$uname,$udom,$type,$trial,$rndseed);
if ($responsetype eq 'essay') {
$subval =~ s{\n}{ }g;
}
$lastsubonly.=$subval."\n";
}
- if ($similar) {$lastsubonly.="
$similar\n";}
- $lastsubonly.='
';
- }
+ if ($similar) {$lastsubonly.="
$similar\n";}
+ $lastsubonly.='';
+ }
}
- }
- $lastsubonly.=''."\n"; # End: LC_grade_submissions_body
- }
- $request->print($lastsubonly);
- if ($env{'form.lastSub'} eq 'datesub') {
- my ($parts,$handgrade,$responseType) = &response_type($symb,\$res_error);
- $request->print(&displaySubByDates($symb,\%record,$parts,$responseType,$checkIcon,$uname,$udom));
-
- }
- if ($env{'form.lastSub'} =~ /^(last|all)$/) {
- my $identifier = (&canmodify($usec)? $counter : '');
- $request->print(&Apache::loncommon::get_previous_attempt($symb,$uname,$udom,
- $env{'request.course.id'},
- $last,'.submission',
- 'Apache::grades::keywords_highlight',
- $usec,$identifier));
- }
- $request->print(''."\n");
- # return if view submission with no grading option
- if (!&canmodify($usec)) {
- $request->print('
'.&mt('No grading privileges').'
');
- return;
- } else {
- $request->print(''."\n");
- }
-
- # essay grading message center
-# if ($env{'form.handgrade'} eq 'yes') {
- if (1) {
- my $result='
';
-
- $result.='
'.
- &mt('Send Message').'
';
- 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.=' '.
- &mt('Compose message to student'.(scalar(@$col_fullnames) >= 1 ? 's' : '')).')'.
- ' '."\n".
- ' ('.
- &mt('Message will be sent when you click on Save & Next below.').")\n";
- $result.='
';
- $request->print($result);
- }
-
- my %seen = ();
- my @partlist;
- my @gradePartRespid;
- my @part_response_id;
- if ($is_tool) {
- @part_response_id = ([0,'']);
- } else {
- @part_response_id = &flatten_responseType($responseType);
- }
- $request->print(
- '
'
- .'
'.&mt('Assign Grades').'
'
- );
- $request->print(&gradeBox_start());
- foreach my $part_response_id (@part_response_id) {
- my ($partid,$respid) = @{ $part_response_id };
- my $part_resp = join('_',@{ $part_response_id });
- next if ($seen{$partid} > 0);
- $seen{$partid}++;
- next if ($$handgrade{$part_resp} ne 'yes'
- && $env{'form.lastSub'} eq 'hdgrade');
- push(@partlist,$partid);
- push(@gradePartRespid,$partid.'.'.$respid);
- $request->print(&gradeBox($request,$symb,$uname,$udom,$counter,$partid,\%record));
- }
- $request->print(&gradeBox_end()); #
- $request->print('');
-
- $request->print('
');
- $request->print('
');
-
- $result=''."\n";
- $result.=''."\n" if ($counter == 0);
- my $ctr = 0;
- while ($ctr < scalar(@partlist)) {
- $result.=''."\n";
- $ctr++;
- }
- $request->print($result.''."\n");
-
-# Done with printing info for one student
-
- $request->print('');#LC_grade_show_user
-
-
- # print end of form
- if ($counter == $total) {
- my $endform='
'."\n";
- $endform.=' '."\n";
- my $ntstu =''."\n";
- my $nsel = ($env{'form.NTSTU'} ne '' ? $env{'form.NTSTU'} : '1');
- $ntstu =~ s/
';
- $request->print($endform);
+ }
+ $lastsubonly.=''."\n"; # End: LC_grade_submissions_body
}
- return '';
+ return ($lastsubonly,$partinfo);
}
sub check_collaborators {
@@ -2711,18 +3853,51 @@ sub check_collaborators {
#--- Retrieve the last submission for all the parts
sub get_last_submission {
my ($returnhash,$is_tool)=@_;
- my (@string,$timestamp,%lasthidden);
+ my (@string,$timestamp,$lastgradetime,$lastsubmittime);
if ($$returnhash{'version'}) {
my %lasthash=();
- my ($version);
+ my %prevsolved=();
+ my %solved=();
+ my $version;
for ($version=1;$version<=$$returnhash{'version'};$version++) {
+ my %handgraded = ();
foreach my $key (sort(split(/\:/,
$$returnhash{$version.':keys'}))) {
$lasthash{$key}=$$returnhash{$version.':'.$key};
- $timestamp =
- &Apache::lonlocal::locallocaltime($$returnhash{$version.':timestamp'});
+ if ($key =~ /\.([^.]+)\.regrader$/) {
+ $handgraded{$1} = 1;
+ } elsif ($key =~ /\.portfiles$/) {
+ if (($$returnhash{$version.':'.$key} ne '') &&
+ ($$returnhash{$version.':'.$key} !~ /\.\d+\.\w+$/)) {
+ $lastsubmittime = $$returnhash{$version.':timestamp'};
+ }
+ } elsif ($key =~ /\.submission$/) {
+ if ($$returnhash{$version.':'.$key} ne '') {
+ $lastsubmittime = $$returnhash{$version.':timestamp'};
+ }
+ } elsif ($key =~ /\.([^.]+)\.solved$/) {
+ $prevsolved{$1} = $solved{$1};
+ $solved{$1} = $lasthash{$key};
+ }
+ }
+ foreach my $partid (keys(%handgraded)) {
+ if (($prevsolved{$partid} eq 'ungraded_attempted') &&
+ (($solved{$partid} eq 'incorrect_by_override') ||
+ ($solved{$partid} eq 'correct_by_override'))) {
+ $lastgradetime = $$returnhash{$version.':timestamp'};
+ }
+ if ($solved{$partid} ne '') {
+ $prevsolved{$partid} = $solved{$partid};
+ }
}
}
+#
+# Timestamp is for last transaction for this resource, which does not
+# necessarily correspond to the time of last submission for problem (or part).
+#
+ if ($lasthash{'timestamp'} ne '') {
+ $timestamp = &Apache::lonlocal::locallocaltime($lasthash{'timestamp'});
+ }
my (%typeparts,%randombytry);
my $showsurv =
&Apache::lonnet::allowed('vas',$env{'request.course.id'});
@@ -2785,7 +3960,7 @@ sub get_last_submission {
$string[0] =
''.$msg.'';
}
- return (\@string,\$timestamp);
+ return (\@string,$timestamp,$lastgradetime,$lastsubmittime);
}
#--- High light keywords, with style choosen by user.
@@ -2992,13 +4167,33 @@ sub processHandGrade {
my $ntstu = $env{'form.NTSTU'};
my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my ($res_error,%queueable);
+ my ($partlist,$handgrade,$responseType,$numresp,$numessay) = &response_type($symb,\$res_error);
+ if ($res_error) {
+ $request->print(&navmap_errormsg());
+ return;
+ } else {
+ foreach my $part (@{$partlist}) {
+ if (ref($responseType->{$part}) eq 'HASH') {
+ foreach my $id (keys(%{$responseType->{$part}})) {
+ if (($responseType->{$part}->{$id} eq 'essay') ||
+ (lc($handgrade->{$part.'_'.$id}) eq 'yes')) {
+ $queueable{$part} = 1;
+ last;
+ }
+ }
+ }
+ }
+ }
if ($button eq 'Save & Next') {
+ my %needpb = &passbacks_for_symb($cdom,$cnum,$symb);
+ my (%skip_passback,%pbsave,%pbcollab);
my $ctr = 0;
while ($ctr < $ngrade) {
my ($uname,$udom) = split(/:/,$env{'form.unamedom'.$ctr});
my ($errorflag,$pts,$wgt,$numhidden) =
- &saveHandGrade($request,$symb,$uname,$udom,$ctr);
+ &saveHandGrade($request,$symb,$uname,$udom,$ctr,undef,undef,\%queueable,\%needpb,\%skip_passback,\%pbsave);
if ($errorflag eq 'no_score') {
$ctr++;
next;
@@ -3051,37 +4246,85 @@ sub processHandGrade {
foreach my $collabstr (@collabstrs) {
my ($part,@collaborators) = split(/:/,$collabstr);
foreach my $collaborator (@collaborators) {
- my ($errorflag,$pts,$wgt) =
+ my ($errorflag,$pts,$wgt,$numchg,$numupdate) =
&saveHandGrade($request,$symb,$collaborator,$udom,$ctr,
- $env{'form.unamedom'.$ctr},$part);
+ $env{'form.unamedom'.$ctr},$part,\%queueable);
if ($errorflag eq 'not_allowed') {
$request->print("".&mt('Not allowed to modify grades for [_1]',"$collaborator:$udom")."");
next;
- } elsif ($message ne '') {
- my ($baseurl,$showsymb) =
- &get_feedurl_and_symb($symb,$collaborator,
- $udom);
- if ($env{'form.withgrades'.$ctr}) {
- $messagetail = " for $restitle";
+ } else {
+ if ($numchg || $numupdate) {
+ $pbcollab{$collaborator}{$part} = [$pts,$wgt];
+ }
+ if ($message ne '') {
+ my ($baseurl,$showsymb) =
+ &get_feedurl_and_symb($symb,$collaborator,
+ $udom);
+ if ($env{'form.withgrades'.$ctr}) {
+ $messagetail = " for $restitle";
+ }
+ $msgstatus =
+ &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
}
- $msgstatus =
- &Apache::lonmsg::user_normal_msg($collaborator,$udom,$subject,$message.$messagetail,undef,$baseurl,undef,undef,undef,$showsymb,$restitle);
- }
+ }
}
}
}
$ctr++;
}
+ if ((keys(%pbcollab)) && (keys(%needpb))) {
+ foreach my $user (keys(%pbcollab)) {
+ my ($clbuname,$clbudom) = split(/:/,$user);
+ my $clbusec = &Apache::lonnet::getsection($clbudom,$clbuname,$cdom.'_'.$cnum);
+ if (ref($pbcollab{$user}) eq 'HASH') {
+ my @clparts = keys(%{$pbcollab{$user}});
+ if (@clparts) {
+ my $navmap = Apache::lonnavmaps::navmap->new($clbuname,$clbudom,$clbusec);
+ if (ref($navmap)) {
+ my $res = $navmap->getBySymb($symb);
+ if (ref($res)) {
+ my $partlist = $res->parts();
+ if (ref($partlist) eq 'ARRAY') {
+ my (%weights,%awardeds,%excuseds);
+ foreach my $part (@{$partlist}) {
+ if ($res->status($part) eq $res->EXCUSED) {
+ $excuseds{$symb}{$part} = 1;
+ } else {
+ $excuseds{$symb}{$part} = '';
+ }
+ if ((exists($pbcollab{$user}{$part})) && (ref($pbcollab{$user}{$part}) eq 'ARRAY')) {
+ my $pts = $pbcollab{$user}{$part}[0];
+ my $wt = $pbcollab{$user}{$part}[1];
+ if ($wt) {
+ $awardeds{$symb}{$part} = $pts/$wt;
+ $weights{$symb}{$part} = $wt;
+ } else {
+ $awardeds{$symb}{$part} = 0;
+ $weights{$symb}{$part} = 0;
+ }
+ } else {
+ $awardeds{$symb}{$part} = $res->awarded($part);
+ $weights{$symb}{$part} = $res->weight($part);
+ }
+ }
+ &process_passbacks('handgrade',[$symb],$cdom,$cnum,$clbudom,$clbuname,$clbusec,\%weights,
+ \%awardeds,\%excuseds,\%needpb,\%skip_passback,\%pbsave);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
-# if ($env{'form.handgrade'} eq 'yes') {
- if (1) {
+ my %keyhash = ();
+ if ($numessay) {
# 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+$//;
+ $env{'form.keywords'} =~ s/^\s+|\s+$//g;
my (@keywords) = sort(split(/\s+/,$env{'form.keywords'}));
$env{'form.keywords'} = join(' ',@keywords);
$keyhash{$symb.'_keywords'} = $env{'form.keywords'};
@@ -3089,7 +4332,9 @@ sub processHandGrade {
$keyhash{$loginuser.'_kwclr'} = $env{'form.kwclr'};
$keyhash{$loginuser.'_kwsize'} = $env{'form.kwsize'};
$keyhash{$loginuser.'_kwstyle'} = $env{'form.kwstyle'};
+ }
+ if ($env{'form.compmsg'}) {
# 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
@@ -3104,17 +4349,20 @@ sub processHandGrade {
$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++;
+ $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,$cdom,$cnum);
}
+ if (($numessay) || ($env{'form.compmsg'})) {
+ my $putresult = &Apache::lonnet::put
+ ('nohist_handgrade',\%keyhash,$cdom,$cnum);
+ }
+
# Called by Save & Refresh from Highlight Attribute Window
my (undef,undef,$fullname) = &getclasslist($env{'form.section'},'1');
if ($env{'form.refresh'} eq 'on') {
@@ -3154,7 +4402,6 @@ sub processHandGrade {
}
return $a cmp $b;
} (keys(%$fullname))) {
-# FIXME: this is fishy, looks like the button label
if ($nextflg == 1 && $button =~ /Next$/) {
push(@parsedlist,$item);
}
@@ -3165,14 +4412,7 @@ sub processHandGrade {
}
}
$ctr = 0;
-# FIXME: this is fishy, looks like the button label
@parsedlist = reverse @parsedlist if ($button eq 'Previous');
- my $res_error;
- my ($partlist) = &response_type($symb,\$res_error);
- if ($res_error) {
- $request->print(&navmap_errormsg());
- return;
- }
foreach my $student (@parsedlist) {
my $submitonly=$env{'form.submitonly'};
my ($uname,$udom) = split(/:/,$student);
@@ -3230,7 +4470,8 @@ sub processHandGrade {
#---- Save the score and award for each student, if changed
sub saveHandGrade {
- my ($request,$symb,$stuname,$domain,$newflg,$submitter,$part) = @_;
+ my ($request,$symb,$stuname,$domain,$newflg,$submitter,
+ $part,$queueable,$needpb,$skip_passback,$pbsave) = @_;
my @version_parts;
my $usec = &Apache::lonnet::getsection($domain,$stuname,
$env{'request.course.id'});
@@ -3238,7 +4479,7 @@ sub saveHandGrade {
my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$stuname);
my @parts_graded;
my %newrecord = ();
- my ($pts,$wgt,$totchg) = ('','',0);
+ my ($pts,$wgt,$totchg,$sendupdate,$poss_pb) = ('','',0,0,0);
my %aggregate = ();
my $aggregateflag = 0;
if ($env{'form.HIDE'.$newflg}) {
@@ -3246,18 +4487,33 @@ sub saveHandGrade {
my $numchgs = &makehidden($version,$parts,\%record,$symb,$domain,$stuname,1);
$totchg += $numchgs;
}
+ if ((ref($needpb) eq 'HASH') && (keys(%{$needpb}))) {
+ $poss_pb = 1;
+ }
+ my (%weights,%awardeds,%excuseds);
my @parts = split(/:/,$env{'form.partlist'.$newflg});
foreach my $new_part (@parts) {
- #collaborator ($submi may vary for different parts
+ #collaborator ($submitter may vary for different parts)
if ($submitter && $new_part ne $part) { next; }
my $dropMenu = $env{'form.GD_SEL'.$newflg.'_'.$new_part};
+ if ($poss_pb) {
+ $weights{$symb}{$new_part} =
+ &Apache::lonnet::EXT('resource.'.$new_part.'.weight',$symb,$domain,$stuname);
+ } elsif ($env{'form.WGT'.$newflg.'_'.$new_part} eq '') {
+ $weights{$symb}{$new_part} = 1;
+ } else {
+ $weights{$symb}{$new_part} = $env{'form.WGT'.$newflg.'_'.$new_part};
+ }
if ($dropMenu eq 'excused') {
+ $excuseds{$symb}{$new_part} = 1;
+ $awardeds{$symb}{$new_part} = '';
if ($record{'resource.'.$new_part.'.solved'} ne 'excused') {
$newrecord{'resource.'.$new_part.'.solved'} = 'excused';
if (exists($record{'resource.'.$new_part.'.awarded'})) {
$newrecord{'resource.'.$new_part.'.awarded'} = '';
}
$newrecord{'resource.'.$new_part.'.regrader'}="$env{'user.name'}:$env{'user.domain'}";
+ $sendupdate ++;
}
} elsif ($dropMenu eq 'reset status'
&& exists($record{'resource.'.$new_part.'.solved'})) { #don't bother if no old records -> no attempts
@@ -3281,6 +4537,9 @@ sub saveHandGrade {
&decrement_aggs($symb,$new_part,\%aggregate,$aggtries,$totaltries,$solvedstatus);
$aggregateflag = 1;
}
+ $sendupdate ++;
+ $excuseds{$symb}{$new_part} = '';
+ $awardeds{$symb}{$new_part} = '';
} elsif ($dropMenu eq '') {
$pts = ($env{'form.GD_BOX'.$newflg.'_'.$new_part} ne '' ?
$env{'form.GD_BOX'.$newflg.'_'.$new_part} :
@@ -3291,12 +4550,15 @@ sub saveHandGrade {
$wgt = $env{'form.WGT'.$newflg.'_'.$new_part} eq '' ? 1 :
$env{'form.WGT'.$newflg.'_'.$new_part};
my $partial= $pts/$wgt;
+ $awardeds{$symb}{$new_part} = $partial;
+ $excuseds{$symb}{$new_part} = '';
if ($partial eq $record{'resource.'.$new_part.'.awarded'}) {
#do not update score for part if not changed.
&handback_files($request,$symb,$stuname,$domain,$newflg,$new_part,\%newrecord);
next;
} else {
push(@parts_graded,$new_part);
+ $sendupdate ++;
}
if ($record{'resource.'.$new_part.'.awarded'} ne $partial) {
$newrecord{'resource.'.$new_part.'.awarded'} = $partial;
@@ -3342,13 +4604,17 @@ sub saveHandGrade {
&Apache::lonnet::cstore(\%newrecord,$symb,
$env{'request.course.id'},$domain,$stuname);
&check_and_remove_from_queue(\@parts,\%record,\%newrecord,$symb,
- $cdom,$cnum,$domain,$stuname);
+ $cdom,$cnum,$domain,$stuname,$queueable);
}
if ($aggregateflag) {
&Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
$cdom,$cnum);
}
- return ('',$pts,$wgt,$totchg);
+ if (($sendupdate || $totchg) && (!$submitter) && ($poss_pb)) {
+ &process_passbacks('handgrade',[$symb],$cdom,$cnum,$domain,$stuname,$usec,\%weights,
+ \%awardeds,\%excuseds,$needpb,$skip_passback,$pbsave);
+ }
+ return ('',$pts,$wgt,$totchg,$sendupdate);
}
sub makehidden {
@@ -3382,7 +4648,7 @@ sub makehidden {
}
sub check_and_remove_from_queue {
- my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname) = @_;
+ my ($parts,$record,$newrecord,$symb,$cdom,$cnum,$domain,$stuname,$queueable) = @_;
my @ungraded_parts;
foreach my $part (@{$parts}) {
if ( $record->{ 'resource.'.$part.'.awarded'} eq ''
@@ -3390,7 +4656,9 @@ sub check_and_remove_from_queue {
&& $newrecord->{'resource.'.$part.'.awarded'} eq ''
&& $newrecord->{'resource.'.$part.'.solved' } ne 'excused'
) {
- push(@ungraded_parts, $part);
+ if ($queueable->{$part}) {
+ push(@ungraded_parts, $part);
+ }
}
}
if ( !@ungraded_parts ) {
@@ -3425,7 +4693,7 @@ sub handback_files {
&Apache::lonnet::file_name_version_ext($answer_file);
my ($portfolio_path) = ($directory =~ /^.+$stuname\/portfolio(.*)/);
my $getpropath = 1;
- my ($dir_list,$listerror) =
+ my ($dir_list,$listerror) =
&Apache::lonnet::dirlist($portfolio_root.$portfolio_path,
$domain,$stuname,$getpropath);
my $version = &Apache::lonnet::get_next_version($answer_name,$answer_ext,$dir_list);
@@ -3589,8 +4857,8 @@ sub version_portfiles {
$$record{$key} = join(',',@versioned_portfiles);
push(@returned_keys,$key);
}
- }
- return (@returned_keys);
+ }
+ return (@returned_keys);
}
#--------------------------------------------------------------------------------------
@@ -3809,7 +5077,26 @@ sub viewgrades {
}
my ($common_header,$specific_header,@sections,$section_display);
- @sections = &Apache::loncommon::get_env_multiple('form.section');
+ if ($env{'request.course.sec'} ne '') {
+ @sections = ($env{'request.course.sec'});
+ } else {
+ @sections = &Apache::loncommon::get_env_multiple('form.section');
+ }
+
+# Check if Save button should be usable
+ my $disabled = ' disabled="disabled"';
+ if ($perm{'mgr'}) {
+ if (grep(/^all$/,@sections)) {
+ undef($disabled);
+ } else {
+ foreach my $sec (@sections) {
+ if (&canmodify($sec)) {
+ undef($disabled);
+ last;
+ }
+ }
+ }
+ }
if (grep(/^all$/,@sections)) {
@sections = ('all');
if ($group_display) {
@@ -3885,7 +5172,6 @@ sub viewgrades {
my $part_resp = join('_',@{ $part_response_id });
next if $seen{$partid};
$seen{$partid}++;
-# my $handgrade=$$handgrade{$part_resp};
my $wgt = &Apache::lonnet::EXT('resource.'.$partid.'.weight',$symb);
$weight{$partid} = $wgt eq '' ? '1' : $wgt;
@@ -4002,7 +5288,7 @@ sub viewgrades {
}
$result.=&Apache::loncommon::end_data_table();
$result.=''."\n";
- $result.=''."\n";
if ($ctr == 0) {
my $stu_status = join(' or ',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -4140,6 +5426,7 @@ sub viewstudentgrade {
foreach my $apart (@$parts) {
my ($part,$type) = &split_part_type($apart);
my $score=$record{"resource.$part.$type"};
+ my $latefrac=$record{"resource.$part.latefrac"};
$result.='
';
my ($aggtries,$totaltries);
unless (exists($aggregates{$part})) {
@@ -4156,7 +5443,7 @@ sub viewstudentgrade {
$aggregates{$part} = 1;
}
if ($type eq 'awarded') {
- my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part});
+ my $pts = $score eq '' ? '' : &compute_points($score,$$weight{$part},$latefrac);
$result.=''."\n";
$result.=''.&mt('Current Grade Status').'';
- $title.='
'.&mt('Section: [_1]',$section_display).'
'."\n";
+ $title.='
'.&mt('Section:').' '.$section_display.'
'."\n";
my $result= &Apache::loncommon::start_data_table().
&Apache::loncommon::start_data_table_header_row().
@@ -4215,6 +5502,10 @@ sub editgrades {
);
my ($classlist,undef,$fullname) = &getclasslist($env{'form.section'},'0');
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my %needpb = &passbacks_for_symb($cdom,$cnum,$symb);
+
my (@partid);
my %weight = ();
my %columns = ();
@@ -4265,6 +5556,7 @@ sub editgrades {
&Apache::loncommon::end_data_table_header_row();
my @noupdate;
my ($updateCtr,$noupdateCtr) = (1,1);
+ my ($got_types,%queueable,%pbsave,%skip_passback);
for ($i=0; $i<$env{'form.total'}; $i++) {
my $user = $env{'form.ctr'.$i};
my ($uname,$udom)=split(/:/,$user);
@@ -4283,6 +5575,7 @@ sub editgrades {
my %aggregate = ();
my $aggregateflag = 0;
$user=~s/:/_/; # colon doen't work in javascript for names
+ my (%weights,%awardeds,%excuseds);
foreach (@partid) {
my $old_aw = $env{'form.GD_'.$user.'_'.$_.'_awarded_s'};
my $old_part_pcr = $old_aw/($weight{$_} ne '0' ? $weight{$_}:1);
@@ -4291,6 +5584,7 @@ sub editgrades {
my $awarded = $env{'form.GD_'.$user.'_'.$_.'_awarded'};
my $pcr = $awarded/($weight{$_} ne '0' ? $weight{$_} : 1);
my $partial = $awarded eq '' ? '' : $pcr;
+ $awardeds{$symb}{$_} = $partial;
my $score;
if ($partial eq '') {
$score = $scoreptr{$env{'form.GD_'.$user.'_'.$_.'_solved_s'}};
@@ -4331,6 +5625,11 @@ sub editgrades {
my $partid=$_;
+ if ($score eq 'excused') {
+ $excuseds{$symb}{$partid} = 1;
+ } else {
+ $excuseds{$symb}{$partid} = '';
+ }
foreach my $stores (@parts) {
my ($part,$type) = &split_part_type($stores);
if ($part !~ m/^\Q$partid\E/) { next;}
@@ -4348,9 +5647,6 @@ sub editgrades {
}
$line.="\n";
- my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
- my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-
if ($updateflag) {
$count++;
&Apache::lonnet::cstore(\%newrecord,$symb,$env{'request.course.id'},
@@ -4364,14 +5660,33 @@ sub editgrades {
$udom,$uname);
my $all_graded = 1;
my $none_graded = 1;
+ unless ($got_types) {
+ my $error;
+ my ($plist,$handgrd,$resptype) = &response_type($symb,\$error);
+ unless ($error) {
+ foreach my $part (@parts) {
+ if (ref($resptype->{$part}) eq 'HASH') {
+ foreach my $id (keys(%{$resptype->{$part}})) {
+ if (($resptype->{$part}->{$id} eq 'essay') ||
+ (lc($handgrd->{$part.'_'.$id}) eq 'yes')) {
+ $queueable{$part} = 1;
+ last;
+ }
+ }
+ }
+ }
+ }
+ $got_types = 1;
+ }
foreach my $part (@parts) {
- if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
- $all_graded = 0;
- } else {
- $none_graded = 0;
+ if ($queueable{$part}) {
+ if ( $record{'resource.'.$part.'.awarded'} eq '' ) {
+ $all_graded = 0;
+ } else {
+ $none_graded = 0;
+ }
}
- }
-
+ }
if ($all_graded || $none_graded) {
&Apache::bridgetask::remove_from_queue('gradingqueue',
$symb,$cdom,$cnum,
@@ -4383,6 +5698,11 @@ sub editgrades {
'
'.$line);
@@ -4642,7 +5962,7 @@ ENDUPFORM
sub csvuploadmap {
- my ($request,$symb)= @_;
+ my ($request,$symb) = @_;
if (!$symb) {return '';}
my $datatoken;
@@ -4738,7 +6058,7 @@ sub get_fields {
}
sub csvuploadassign {
- my ($request,$symb)= @_;
+ my ($request,$symb) = @_;
if (!$symb) {return '';}
my $error_msg = '';
my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'});
@@ -4748,11 +6068,33 @@ sub csvuploadassign {
my @gradedata = &Apache::loncommon::upfile_record_sep();
my %fields=&get_fields();
my $courseid=$env{'request.course.id'};
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
my ($classlist) = &getclasslist('all',0);
my @notallowed;
my @skipped;
my @warnings;
my $countdone=0;
+ my @parts;
+ my %needpb = &passbacks_for_symb($cdom,$cnum,$symb);
+ my $passback;
+ if (keys(%needpb)) {
+ $passback = 1;
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (ref($navmap)) {
+ my $res = $navmap->getBySymb($symb);
+ if (ref($res)) {
+ my $partlist = $res->parts();
+ if (ref($partlist) eq 'ARRAY') {
+ @parts = sort(@{$partlist});
+ }
+ }
+ } else {
+ return &navmap_errormsg();
+ }
+ }
+ my (%skip_passback,%pbsave,%weights,%awardeds,%excuseds);
+
foreach my $grade (@gradedata) {
my %entries=&Apache::loncommon::record_sep($grade);
my $domain;
@@ -4827,9 +6169,14 @@ sub csvuploadassign {
my $part=$1;
my $wgt =&Apache::lonnet::EXT('resource.'.$part.'.weight',
$symb,$domain,$username);
+ $weights{$symb}{$part} = $wgt;
if ($wgt) {
$entries{$fields{$dest}}=~s/\s//g;
my $pcr=$entries{$fields{$dest}} / $wgt;
+ if ($passback) {
+ $awardeds{$symb}{$part} = $pcr;
+ $excuseds{$symb}{$part} = '';
+ }
my $award=($pcr == 0) ? 'incorrect_by_override'
: 'correct_by_override';
if ($pcr>1) {
@@ -4849,12 +6196,28 @@ sub csvuploadassign {
if ($dest=~/stores_(.*)_awarded/) { if ($points{$1}) {next;} }
if ($dest=~/stores_(.*)_solved/) { if ($points{$1}) {next;} }
my $store_key=$dest;
+ if ($passback) {
+ if ($store_key=~/stores_(.*)_(awarded|solved)/) {
+ my ($part,$key) = ($1,$2);
+ unless ((ref($weights{$symb}) eq 'HASH') && (exists($weights{$symb}{$part}))) {
+ $weights{$symb}{$part} = &Apache::lonnet::EXT('resource.'.$part.'.weight',
+ $symb,$domain,$username);
+ }
+ if ($key eq 'awarded') {
+ $awardeds{$symb}{$part} = $entries{$fields{$dest}};
+ } elsif ($key eq 'solved') {
+ if ($entries{$fields{$dest}} =~ /^excused/) {
+ $excuseds{$symb}{$part} = 1;
+ }
+ }
+ }
+ }
$store_key=~s/^stores/resource/;
$store_key=~s/_/\./g;
$grades{$store_key}=$entries{$fields{$dest}};
}
}
- if (! %grades) {
+ if (! %grades) {
push(@skipped,&mt("[_1]: no data to save","$username:$domain"));
} else {
$grades{"resource.regrader"}="$env{'user.name'}:$env{'user.domain'}";
@@ -4865,11 +6228,32 @@ sub csvuploadassign {
# Successfully stored
$request->print('.');
# Remove from grading queue
- &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,
- $env{'course.'.$env{'request.course.id'}.'.domain'},
- $env{'course.'.$env{'request.course.id'}.'.num'},
- $domain,$username);
+ &Apache::bridgetask::remove_from_queue('gradingqueue',$symb,$cdom,$cnum,
+ $domain,$username);
$countdone++;
+ if ($passback) {
+ my @parts_in_upload;
+ if (ref($weights{$symb}) eq 'HASH') {
+ @parts_in_upload = sort(keys(%{$weights{$symb}}));
+ }
+ my @diffs = &Apache::loncommon::compare_arrays(\@parts_in_upload,\@parts);
+ if (@diffs > 0) {
+ my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$domain,$username);
+ foreach my $part (@parts) {
+ next if (grep(/^\Q$part\E$/,@parts_in_upload));
+ $weights{$symb}{$part} = &Apache::lonnet::EXT('resource.'.$part.'.weight',
+ $symb,$domain,$username);
+ if ($record{"resource.$part.solved"} =~/^excused/) {
+ $excuseds{$symb}{$part} = 1;
+ } else {
+ $excuseds{$symb}{$part} = '';
+ }
+ $awardeds{$symb}{$part} = $record{"resource.$part.awarded"};
+ }
+ }
+ &process_passbacks('csvupload',[$symb],$cdom,$cnum,$domain,$username,$usec,\%weights,
+ \%awardeds,\%excuseds,\%needpb,\%skip_passback,\%pbsave);
+ }
} else {
$request->print("
".
&mt("Failed to save data for student [_1]. Message when trying to save was: [_2]",
@@ -4925,6 +6309,7 @@ LISTJAVASCRIPT
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 $getgroup = $env{'form.group'} eq '' ? 'all' : $env{'form.group'};
my $result='
'.
&mt('Manual Grading by Page or Sequence').'
';
@@ -5014,7 +6399,7 @@ LISTJAVASCRIPT
'
'.&nameUserString('header').'
'.
&Apache::loncommon::end_data_table_header_row();
- my (undef,undef,$fullname) = &getclasslist($getsec,'1');
+ my (undef,undef,$fullname) = &getclasslist($getsec,'1',$getgroup);
my $ptr = 1;
foreach my $student (sort
{
@@ -5241,10 +6626,14 @@ sub displayPage {
}
$curRes = $iterator->next();
}
+ my $disabled;
+ unless (&canmodify($usec)) {
+ $disabled = ' disabled="disabled"';
+ }
$studentTable.=
''."\n".
- ''.
''."\n";
$request->print($studentTable);
@@ -5310,11 +6699,11 @@ sub displaySubByDates {
}
my @matchKey;
if ($isTask) {
- @matchKey = sort(grep /^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys);
+ @matchKey = sort(grep(/^resource\.\d+\.\Q$partid\E\.award$/,@versionKeys));
} elsif ($is_tool) {
- @matchKey = sort(grep /^resource\.\Q$partid\E\.awarded$/,@versionKeys);
+ @matchKey = sort(grep(/^resource\.\Q$partid\E\.awarded$/,@versionKeys));
} else {
- @matchKey = sort(grep /^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys);
+ @matchKey = sort(grep(/^resource\.\Q$partid\E\..*?\.submission$/,@versionKeys));
}
# next if ($$record{"$version:resource.$partid.solved"} eq '');
my $display_part=&get_display_part($partid,$symb);
@@ -5460,6 +6849,7 @@ sub updateGradeByPage {
$iterator->next(); # skip the first BEGIN_MAP
my $curRes = $iterator->next(); # for "current resource"
my ($depth,$question,$prob,$changeflag,$hideflag)= (1,1,1,0,0);
+ my (@updates,%weights,%excuseds,%awardeds,@symbs_in_map);
while ($depth > 0) {
if($curRes == $iterator->BEGIN_MAP) { $depth++; }
if($curRes == $iterator->END_MAP) { $depth--; }
@@ -5468,6 +6858,7 @@ sub updateGradeByPage {
my $parts = $curRes->parts();
my $title = $curRes->compTitle();
my $symbx = $curRes->symb();
+ push(@symbs_in_map,$symbx);
$studentTable.=
&Apache::loncommon::start_data_table_row().
'
'.$prob.
@@ -5480,18 +6871,37 @@ sub updateGradeByPage {
my @displayPts=();
my %aggregate = ();
my $aggregateflag = 0;
+ my %queueable;
if ($env{'form.HIDE'.$prob}) {
my %record = &Apache::lonnet::restore($symbx,$env{'request.course.id'},$udom,$uname);
my ($version,$parts) = split(/:/,$env{'form.HIDE'.$prob},2);
my $numchgs = &makehidden($version,$parts,\%record,$symbx,$udom,$uname,1);
+ if ($numchgs) {
+ push(@updates,$symbx);
+ }
$hideflag += $numchgs;
}
foreach my $partid (@{$parts}) {
my $newpts = $env{'form.GD_BOX'.$question.'_'.$partid};
my $oldpts = $env{'form.oldpts'.$question.'_'.$partid};
-
+ my @types = $curRes->responseType($partid);
+ if (grep(/^essay$/,@types)) {
+ $queueable{$partid} = 1;
+ } else {
+ my @ids = $curRes->responseIds($partid);
+ for (my $i=0; $i < scalar(@ids); $i++) {
+ my $hndgrd = &Apache::lonnet::EXT('resource.'.$partid.'_'.$ids[$i].
+ '.handgrade',$symb);
+ if (lc($hndgrd) eq 'yes') {
+ $queueable{$partid} = 1;
+ last;
+ }
+ }
+ }
my $wgt = $env{'form.WGT'.$question.'_'.$partid} != 0 ?
$env{'form.WGT'.$question.'_'.$partid} : 1;
+ $weights{$symbx}{$partid} = $wgt;
+ $excuseds{$symbx}{$partid} = '';
my $partial = $newpts/$wgt;
my $score;
if ($partial > 0) {
@@ -5503,6 +6913,7 @@ sub updateGradeByPage {
if ($dropMenu eq 'excused') {
$partial = '';
$score = 'excused';
+ $excuseds{$symbx}{$partid} = 1;
} elsif ($dropMenu eq 'reset status'
&& $env{'form.solved'.$question.'_'.$partid} ne '') { #update only if previous record exists
$newrecord{'resource.'.$partid.'.tries'} = 0;
@@ -5530,6 +6941,11 @@ sub updateGradeByPage {
(($score eq 'excused') ? 'excused' : $newpts).
' ';
$question++;
+ if (($newpts eq '') || ($partial eq '')) {
+ $awardeds{$symbx}{$partid} = 0;
+ } else {
+ $awardeds{$symbx}{$partid} = $partial;
+ }
next if ($dropMenu eq 'reset status' || ($newpts eq $oldpts && $score ne 'excused'));
$newrecord{'resource.'.$partid.'.awarded'} = $partial if $partial ne '';
@@ -5555,7 +6971,7 @@ sub updateGradeByPage {
$env{'request.course.id'},
$udom,$uname);
&check_and_remove_from_queue($parts,\%record,undef,$symbx,
- $cdom,$cnum,$udom,$uname);
+ $cdom,$cnum,$udom,$uname,\%queueable);
}
if ($aggregateflag) {
@@ -5569,6 +6985,9 @@ sub updateGradeByPage {
&Apache::loncommon::end_data_table_row();
$prob++;
+ if ($changeflag) {
+ push(@updates,$symbx);
+ }
}
$curRes = $iterator->next();
}
@@ -5582,9 +7001,95 @@ sub updateGradeByPage {
$hideflag).' ');
$request->print($hidemsg.$grademsg.$studentTable);
+ if (@updates) {
+ my (@allsymbs,$mapsymb,@recurseup,%parentmapsymbs,%possmappb,%possrespb);
+ @allsymbs = @updates;
+ if (ref($map)) {
+ $mapsymb = $map->symb();
+ push(@allsymbs,$mapsymb);
+ @recurseup = $navmap->recurseup_maps($map->src,1);
+ }
+ if (@recurseup) {
+ push(@allsymbs,@recurseup);
+ map { $parentmapsymbs{$_} = 1; } @recurseup;
+ }
+ my %passback = &Apache::lonnet::get('nohist_linkprot_passback',\@allsymbs,$cdom,$cnum);
+ my (%uniqsymbs,$use_symbs_in_map,%launch_to_symb);
+ if (keys(%passback)) {
+ foreach my $possible (keys(%passback)) {
+ if (ref($passback{$possible}) eq 'HASH') {
+ if ($possible eq $mapsymb) {
+ foreach my $launcher (keys(%{$passback{$possible}})) {
+ $possmappb{$launcher} = 1;
+ $launch_to_symb{$launcher} = $possible;
+ }
+ $use_symbs_in_map = 1;
+ } elsif (exists($parentmapsymbs{$possible})) {
+ foreach my $launcher (keys(%{$passback{$possible}})) {
+ my ($linkuri,$linkprotector,$scope) = split(/\0/,$launcher);
+ if ($scope eq 'rec') {
+ $possmappb{$launcher} = 1;
+ $use_symbs_in_map = 1;
+ $launch_to_symb{$launcher} = $possible;
+ }
+ }
+ } elsif (grep(/^\Q$possible$\E$/,@updates)) {
+ foreach my $launcher (keys(%{$passback{$possible}})) {
+ $possrespb{$launcher} = 1;
+ $launch_to_symb{$launcher} = $possible;
+ }
+ $uniqsymbs{$possible} = 1;
+ }
+ }
+ }
+ }
+ if ($use_symbs_in_map) {
+ map { $uniqsymbs{$_} = 1; } @symbs_in_map;
+ }
+ my @posslaunchers;
+ if (keys(%possmappb)) {
+ push(@posslaunchers,keys(%possmappb));
+ }
+ if (keys(%possrespb)) {
+ push(@posslaunchers,keys(%possrespb));
+ }
+ if (@posslaunchers) {
+ my (%pbsave,%skip_passback,%needpb);
+ my %pbids = &Apache::lonnet::get('nohist_'.$cdom.'_'.$cnum.'_linkprot_pb',\@posslaunchers,$udom,$uname);
+ foreach my $key (keys(%pbids)) {
+ if (ref($pbids{$key}) eq 'ARRAY') {
+ if ($launch_to_symb{$key}) {
+ $needpb{$key} = $launch_to_symb{$key};
+ }
+ }
+ }
+ my @symbs = keys(%uniqsymbs);
+ &process_passbacks('updatebypage',\@symbs,$cdom,$cnum,$udom,$uname,$usec,\%weights,
+ \%awardeds,\%excuseds,\%needpb,\%skip_passback,\%pbsave,\%pbids);
+ if (@Apache::grades::ltipassback) {
+ unless ($registered_cleanup) {
+ my $handlers = $request->get_handlers('PerlCleanupHandler');
+ $request->set_handlers('PerlCleanupHandler' =>
+ [\&Apache::grades::make_passback,@{$handlers}]);
+ $registered_cleanup=1;
+ }
+ }
+ }
+ }
return '';
}
+sub make_passback {
+ if (@Apache::grades::ltipassback) {
+ my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+ my $ip = &Apache::lonnet::get_host_ip($lonhost);
+ foreach my $item (@Apache::grades::ltipassback) {
+ &Apache::lonhomework::run_passback($item,$lonhost,$ip);
+ }
+ undef(@Apache::grades::ltipassback);
+ }
+}
+
#-------- end of section for handling grading by page/sequence ---------
#
#-------------------------------------------------------------------
@@ -5654,7 +7159,7 @@ the homework problem.
sub defaultFormData {
my ($symb)=@_;
- return '';
+ return '';
}
@@ -5897,8 +7402,7 @@ sub scantron_selectphase {
$ssi_error = 0;
- if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) ||
- &Apache::lonnet::allowed('usc',$env{'request.course.id'})) {
+ if (&Apache::lonnet::allowed('usc',$env{'request.role.domain'}) || $perm{'usc'}) {
# Chunk of form to prompt for a scantron file upload.
@@ -5906,6 +7410,7 @@ sub scantron_selectphase {
');
my $cdom= $env{'course.'.$env{'request.course.id'}.'.domain'};
my $cnum= $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $csec= $env{'request.course.sec'};
my $alertmsg = &mt('Please use the browse button to select a file from your local directory.');
&js_escape(\$alertmsg);
my ($formatoptions,$formattitle,$formatjs) = &scantron_upload_dataformat($cdom);
@@ -5921,6 +7426,7 @@ sub scantron_selectphase {
'.
&Apache::loncommon::end_data_table_row().
@@ -10225,7 +12268,7 @@ ENDPERCFORM
}
sub process_clicker_file {
- my ($r,$symb)=@_;
+ my ($r,$symb) = @_;
if (!$symb) {return '';}
my %Saveable_Parameters=&clicker_grading_parameters();
@@ -10399,7 +12442,7 @@ ENDHEADER
"\n".&mt("Username").": ".
"\n".&mt("Domain").": ".
&Apache::loncommon::select_dom_form($env{'course.'.$env{'request.course.id'}.'.domain'},'udom'.$id).' '.
- &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,0,$id);
+ &Apache::loncommon::selectstudent_link('clickeranalysis','uname'.$id,'udom'.$id,'',$id);
$unknown_count++;
}
}
@@ -10555,7 +12598,7 @@ sub turning_eval {
sub assign_clicker_grades {
- my ($r,$symb)=@_;
+ my ($r,$symb) = @_;
if (!$symb) {return '';}
# See which part we are saving to
my $res_error;
@@ -10563,14 +12606,18 @@ sub assign_clicker_grades {
if ($res_error) {
return &navmap_errormsg();
}
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my %needpb = &passbacks_for_symb($cdom,$cnum,$symb);
+ my (%skip_passback,%pbsave);
# FIXME: This should probably look for the first handgradeable part
my $part=$$partlist[0];
# Start screen output
- my $result=&Apache::loncommon::start_data_table().
- &Apache::loncommon::start_data_table_header_row().
- '
');
@@ -10937,7 +13152,7 @@ sub handler {
}
if ($env{'form.inhibitmenu'}) {
$request->print(&Apache::loncommon::end_page());
- } else {
+ } elsif ($env{'request.course.id'}) {
&Apache::lonquickgrades::endGradeScreen($request);
}
&reset_caches();
@@ -11105,8 +13320,8 @@ Side Effects: None.
- missingbubble - array ref of the bubble lines that have missing
bubble errors
- $randomorder - True if exam folder has randomorder set
- $randompick - True if exam folder has randompick set
+ $randomorder - True if exam folder (or a sub-folder) has randomorder set
+ $randompick - True if exam folder (or a sub-folder) has randompick set
$respnumlookup - Reference to HASH mapping question numbers in bubble lines
for current line to question number used for same question
in "Master Seqence" (as seen by Course Coordinator).
@@ -11175,7 +13390,12 @@ Side Effects: None.
=item scantron_upload_scantron_data_save() :
Adds a provided bubble information data file to the course if user
- has the correct privileges to do so.
+ has the correct privileges to do so.
+
+= item scantron_upload_delete() :
+
+ Deletes a previously uploaded bubble information data file, if user
+ was the one who uploaded the file, and has the privileges to do so.
=item valid_file() :