--- loncom/homework/grades.pm 2025/01/17 03:46:07 1.596.2.12.2.60.2.7
+++ loncom/homework/grades.pm 2017/12/31 14:00:41 1.749
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# The LON-CAPA Grading handler
#
-# $Id: grades.pm,v 1.596.2.12.2.60.2.7 2025/01/17 03:46:07 raeburn Exp $
+# $Id: grades.pm,v 1.749 2017/12/31 14:00:41 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -44,14 +44,10 @@ use Apache::Constants qw(:common :http);
use Apache::lonlocal;
use Apache::lonenc;
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);
@@ -66,7 +62,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) = @_;
@@ -149,7 +145,6 @@ 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) = @_;
@@ -167,7 +162,6 @@ 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);
@@ -177,20 +171,13 @@ 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,$numresp,$numessay,$numdropbox);
+ return ($partlist,\%handgrade,\%response_types);
}
sub flatten_responseType {
@@ -217,129 +204,6 @@ 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') {
@@ -478,7 +342,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) {
@@ -496,7 +360,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);
@@ -553,8 +417,8 @@ sub cleanRecord {
$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 = &Apache::lontexconvert::msgtexconverted($answer);
return '
'.&keywords_highlight($answer).'
';
+
} elsif ( $response eq 'organic') {
my $result=&mt('Smile representation: [_1]',
'"'.&HTML::Entities::encode($answer, '"<>&').'"');
@@ -638,7 +502,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,$filterbypbid,$possibles) = @_;
+ my ($getsec,$filterlist,$getgroup) = @_;
my @getsec;
my @getgroup;
my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status'));
@@ -666,17 +530,6 @@ 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) = &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 =
$classlist->{$student}->[&Apache::loncoursedata::CL_END()];
@@ -693,7 +546,7 @@ sub getclasslist {
my $group =
$classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()];
# filter students according to status selected
- if ($filterbyaccstatus && (!($stu_status =~ /Any/))) {
+ if ($filterlist && (!($stu_status =~ /Any/))) {
if (!($stu_status =~ $status)) {
delete($classlist->{$student});
next;
@@ -710,79 +563,13 @@ sub getclasslist {
}
}
if (($grp eq 'none') && !$group) {
- $exclude = 0;
+ $exclude = 0;
}
}
if ($exclude) {
delete($classlist->{$student});
- next;
}
}
- if (($filterbysubmstatus) && ($submitonly ne 'all') && ($symb ne '')) {
- my $udom =
- $classlist->{$student}->[&Apache::loncoursedata::CL_SDOM()];
- my $uname =
- $classlist->{$student}->[&Apache::loncoursedata::CL_SNAME()];
- if (($symb ne '') && ($udom ne '') && ($uname ne '')) {
- if ($submitonly eq 'queued') {
- my %queue_status =
- &Apache::bridgetask::get_student_status($symb,$cdom,$cnum,
- $udom,$uname);
- if (!defined($queue_status{'gradingqueue'})) {
- delete($classlist->{$student});
- next;
- }
- } else {
- my (%status) =&student_gradeStatus($symb,$udom,$uname,$partlist);
- my $submitted = 0;
- my $graded = 0;
- my $incorrect = 0;
- foreach (keys(%status)) {
- $submitted = 1 if ($status{$_} ne 'nothing');
- $graded = 1 if ($status{$_} =~ /^ungraded/);
- $incorrect = 1 if ($status{$_} =~ /^incorrect/);
-
- my ($foo,$partid,$foo1) = split(/\./,$_);
- if ($status{'resource.'.$partid.'.submitted_by'} ne '') {
- $submitted = 0;
- }
- }
- if (!$submitted && ($submitonly eq 'yes' ||
- $submitonly eq 'incorrect' ||
- $submitonly eq 'graded')) {
- delete($classlist->{$student});
- next;
- } elsif (!$graded && ($submitonly eq 'graded')) {
- delete($classlist->{$student});
- next;
- } elsif (!$incorrect && $submitonly eq 'incorrect') {
- delete($classlist->{$student});
- next;
- }
- }
- }
- }
- 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)) {
@@ -797,8 +584,9 @@ sub getclasslist {
delete($classlist->{$student});
}
}
+ my %seen = ();
my @sections = sort(keys(%sections));
- return ($classlist,\@sections,\%fullnames,\%passback);
+ return ($classlist,\@sections,\%fullnames);
}
sub canmodify {
@@ -812,7 +600,7 @@ sub canmodify {
#can modify the requested section
return 1;
} else {
- # can't modify the requested section
+ # can't modify the request section
return 0;
}
}
@@ -825,19 +613,19 @@ sub canview {
my ($sec)=@_;
if ($perm{'vgr'}) {
if (!defined($perm{'vgr_section'})) {
- # can view whole class
+ # can modify whole class
return 1;
} else {
if ($sec eq $perm{'vgr_section'}) {
- #can view the requested section
+ #can modify the requested section
return 1;
} else {
- # can't view the requested section
+ # can't modify the request section
return 0;
}
}
}
- #can't view
+ #can't modify
return 0;
}
@@ -978,14 +766,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";
@@ -1061,883 +849,12 @@ 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 '