--- loncom/homework/grades.pm 2002/07/01 21:20:29 1.34 +++ loncom/homework/grades.pm 2024/12/14 17:47:39 1.806 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.34 2002/07/01 21:20:29 ng Exp $ +# $Id: grades.pm,v 1.806 2024/12/14 17:47:39 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,13 +25,8 @@ # # 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 2002 H.K. Ng -# + + package Apache::grades; use strict; @@ -39,833 +34,5739 @@ use Apache::style; use Apache::lonxml; use Apache::lonnet; use Apache::loncommon; +use Apache::lonhtmlcommon; +use Apache::lonnavmaps; use Apache::lonhomework; -use Apache::Constants qw(:common); +use Apache::lonpickcode; +use Apache::loncoursedata; +use Apache::lonmsg(); +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); + + + +my %perm=(); +my %old_essays=(); + +# These variables are used to recover from ssi errors + +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) = @_; + my ($content, $response) = &Apache::loncommon::ssi_with_retries($resource, $retries, %form); + if ($response->is_error) { + $ssi_error = 1; + $ssi_error_resource = $resource; + $ssi_error_message = $response->code . " " . $response->message; + } + + return $content; -sub moreinfo { - my ($request,$reason) = @_; - $request->print("Unable to process request: $reason"); - if ( $Apache::grades::viewgrades eq 'F' ) { - $request->print('
'); - } - return ''; } +# +# Prodcuces an ssi retry failure error message to the user: +# -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'}); +sub ssi_print_error { + my ($r) = @_; + my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk'); + $r->print(' +
+'.&mt('Unable to retrieve a resource from a server:').'
+'.&mt('Resource:').' '.$ssi_error_resource.'
+'.&mt('Error:').' '.$ssi_error_message.'
+
'.
+&mt('It is recommended that you try again later, as this error may mean the server was just temporarily unavailable, or is down for maintenance.').'
'.
+&mt('If the error persists, please contact the [_1] for assistance.',$helpurl).
+'
'.$matches." match%s
",$matches <= 1 ? '' : 'es'); -# needs to print who is matched + return; } - return ''; + my $res = $navmap->getBySymb($symb); + unless (ref($res)) { + $$response_error = 1; + 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); + foreach my $part (@{ $partlist }) { + next if (%vPart && !exists($vPart{$part})); + + my @types = $res->responseType($part); + my @ids = $res->responseIds($part); + for (my $i=0; $i < scalar(@ids); $i++) { + $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); } +sub flatten_responseType { + my ($responseType) = @_; + my @part_response_id = + map { + my $part = $_; + map { + [$part,$_] + } sort(keys(%{ $responseType->{$part} })); + } sort(keys(%$responseType)); + return @part_response_id; +} + +sub get_display_part { + my ($partID,$symb)=@_; + my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',$symb); + if (defined($display) and $display ne '') { + $display.= ' (' + .&mt('Part ID: [_1]',$partID).')'; + } else { + $display=$partID; + } + 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 ''. + &mt('No dropbox items or essayresponse items with uploadedfiletypes set.'). + '
'; + } else { + return ''; + } elsif ($response eq 'match') { + my %answer=&Apache::lonnet::str2hash($answer); + my @answer = %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); + foreach my $foil (@$order) { + my $item=shift(@items); + if ($grading{$foil} == 1) { + $toprow.=''. + '
'. + ' '.&mt('Answer').' '.$toprow.''.$grayFont.&mt('Option ID').' '. + $bottomrow.'
'; + } elsif ($response eq 'radiobutton') { + my %answer=&Apache::lonnet::str2hash($answer); + my @answer = %answer; + %answer = map {&HTML::Entities::encode($_, '"<>&')} @answer; + my ($toprow,$bottomrow); + my $correct = + &get_radiobutton_correct_foil($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed); + foreach my $foil (@$order) { + if (exists($answer{$foil})) { + if ($foil eq $correct) { + $toprow.=''. + '
'. + ' '.&mt('Answer').' '.$toprow.''. + ' '.$grayFont.&mt('Item ID').' '. + $middlerow.''.$grayFont.&mt('Option ID').' '. + $bottomrow.'
'; + } 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 = &Apache::lontexconvert::msgtexconverted($answer); + return ''. + '
'. + ' '.&mt('Answer').' '.$toprow.''.$grayFont.&mt('Option ID').' '. + $bottomrow.'
'.&keywords_highlight($answer).''; + } elsif ( $response eq 'organic') { + my $result=&mt('Smile representation: [_1]', + '"'.&HTML::Entities::encode($answer, '"<>&').'"'); + my $jme=$record->{$version."resource.$partid.$respid.molecule"}; + $result.=&Apache::chemresponse::jme_img($jme,$answer,400); + return $result; + } elsif ( $response eq 'Task') { + if ( $answer eq 'SUBMITTED') { + my $files = $record->{$version."resource.$respid.$partid.bridgetask.portfiles"}; + my $result = &Apache::bridgetask::file_list($files,$uname,$udom); + return $result; + } elsif ( grep(/^\Q$version\E.*?\.instance$/, keys(%{$record})) ) { + my @matches = grep(/^\Q$version\E.*?\.instance$/, + keys(%{$record})); + return join('
' + .&mt('Overall result: [_1]', + $record->{$version."resource.$respid.$partid.status"}) + .'
'; + + $result .= '
-
|
' + .&mt('No match found for the above receipt number.') + .'
'; + } else { + $string = &jscriptNform($symb).$title. + ''. + &mt('The above receipt number matches the following [quant,_1,student].',$matches). + '
'. + $header. + $contents. + &Apache::loncommon::end_data_table()."\n"; + } + return $string; } -#FIXME - needs to handle multiple matches -sub finduser { - my ($name) = @_; - my $domain = ''; - - if ( $Apache::grades::viewgrades eq 'F' ) { - #get classlist -# my ($cdom,$cnum) = split(/_/,$ENV{'request.course.id'}); - #print "Found $cdom:$cnum'.$output.'
'; + } + return &Apache::loncourserespicker::create_picker($navmap,'passback',$formname,$crstype,undef, + undef,undef,undef,undef,undef,undef, + \%passback,$readonly); } -sub getclasslist { - my ($getsec,$hideexpired) = @_; - my ($coursedomain,$coursenum) = split(/_/,$ENV{'request.course.id'}); - my %classlist=&Apache::lonnet::dump('classlist',$coursedomain,$coursenum); - my $now = time; - my (@holdsec,@sections); - foreach my $student (keys(%classlist)) { - my ($end,$start)=split(/:/,$classlist{$student}); - # still a student? - if (($hideexpired) && ($end) && ($end < $now)) { - next; - } - my ($unam,$udom) = split(/:/,$student,2); - my $section = &Apache::lonnet::usection($udom,$unam,$ENV{'request.course.id'}); - push @holdsec,$section; - push (@{ $classlist{$getsec} }, $student) if ($getsec eq 'all' or $getsec == $section); - } - my %seen = (); - foreach my $item (@holdsec) { - push (@sections, $item) unless $seen{$item}++; - } - return (\%classlist,\@sections); +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]',
+ '→').
+ '
'.&mt('Scores sent to launcher CMS').'
'.$outcome.'
'); + } else { + $request->print(''.&mt('No scores sent to launcher CMS').'
'); + } + if (keys(%tosend)) { + $request->print(''.&mt('No scores sent for following')); + my ($zeros,$nopbcreds,$noconfirm,$noscore); + foreach my $student (sort + { + if (lc($$fullname{$a}) ne lc($$fullname{$b})) { + return (lc($$fullname{$a}) cmp lc($$fullname{$b})); + } + return $a cmp $b; + } (keys(%$fullname))) { + next unless ($tosend{$student}); + my ($uname,$udom) = split(/:/,$student); + my $line = '
'.$error.'
'); + } + return; } -#FIXME need to look at the metadata'. + &Apache::lonhtmlcommon::start_pick_box(). + &Apache::lonhtmlcommon::row_title(&mt('Launch Item Title')). + &Apache::lonnet::gettitle($launcher). + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('Deep-link')). + $linkuri. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('Launcher')). + $appname.' '.$setter. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title(&mt('Score Type')). + $shownscope. + &Apache::lonhtmlcommon::row_closure(1). + &Apache::lonhtmlcommon::end_pick_box().'
'."\n"; +} - $result.="Stored away ".scalar(keys(%newrecord))." elements.