--- loncom/homework/grades.pm 2012/08/09 23:25:48 1.596.2.13 +++ loncom/homework/grades.pm 2024/12/03 23:34:10 1.796 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # The LON-CAPA Grading handler # -# $Id: grades.pm,v 1.596.2.13 2012/08/09 23:25:48 raeburn Exp $ +# $Id: grades.pm,v 1.796 2024/12/03 23:34:10 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -43,9 +43,16 @@ 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); @@ -98,6 +105,9 @@ sub ssi_print_error { # # --- Retrieve the parts from the metadata file.--- +# Returns an array of everything that the resources stores away +# + sub getpartlist { my ($symb,$errorref) = @_; @@ -111,7 +121,11 @@ sub getpartlist { my $res = $navmap->getBySymb($symb); my $partlist = $res->parts(); my $url = $res->src(); - my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys')); + my $toolsymb; + if ($url =~ /ext\.tool$/) { + $toolsymb = $symb; + } + my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys',$toolsymb)); my @stores; foreach my $part (@{ $partlist }) { @@ -122,21 +136,6 @@ sub getpartlist { return @stores; } -# --- Get the symbolic name of a problem and the url -sub get_symb { - my ($request,$silent) = @_; - (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; - my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url))); - if ($symb eq '') { - if (!$silent) { - $request->print(&mt("Unable to handle ambiguous references: [_1].",$url)); - return (); - } - } - &Apache::lonenc::check_decrypt(\$symb); - return ($symb); -} - #--- Format fullname, username:domain if different for display #--- Use anywhere where the student names are listed sub nameUserString { @@ -150,7 +149,8 @@ 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,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); @@ -176,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 { @@ -209,53 +217,127 @@ sub get_display_part { return $display; } -#--- Show resource title -#--- and parts and response type +#--- Show parts and response type sub showResourceInfo { - my ($symb,$probTitle,$checkboxes,$res_error) = @_; - my $result = '
'. + &mt('No dropbox items or essayresponse items with uploadedfiletypes set.'). + '
'; + } else { + return ''.&keywords_highlight($answer).''; } elsif ( $response eq 'organic') { - my $result='Smile representation: "'.$answer.'"'; + 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; @@ -490,19 +591,20 @@ sub cleanRecord { $result.=''; return $result; } - } elsif ( $response =~ m/(?:numerical|formula)/) { + } elsif ( $response =~ m/(?:numerical|formula|custom)/) { + # Respect multiple input fields, see Bug #5409 $answer = &Apache::loncommon::format_previous_attempt_value('submission', $answer); + return $answer; } - return $answer; + return &HTML::Entities::encode($answer, '"<>&'); } #-- A couple of common js functions sub commonJSfunctions { my $request = shift; - $request->print(<
'.$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]',
+ '→').
+ '
'.&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; +} + +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)'; + } + } + } + if ($linkuri =~ m{^\Q/tiny/$cdom/\E(\w+)$}) { + my $key = $1; + my $tinyurl; + my ($result,$cached)=&Apache::lonnet::is_cached_new('tiny',$cdom."\0".$key); + if (defined($cached)) { + $tinyurl = $result; + } else { + my $configuname = &Apache::lonnet::get_domainconfiguser($cdom); + my %currtiny = &Apache::lonnet::get('tiny',[$key],$cdom,$configuname); + if ($currtiny{$key} ne '') { + $tinyurl = $currtiny{$key}; + &Apache::lonnet::do_cache_new('tiny',$cdom."\0".$key,$currtiny{$key},600); + } + } + if ($tinyurl) { + my ($crsnum,$launchsymb) = split(/\&/,$tinyurl); + if ($crsnum eq $cnum) { + 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 ''. + &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"; } #--- This is called by a number of programs. @@ -882,141 +1671,179 @@ sub verifyreceipt { #--- Also called directly when one clicks on the subm button # on the problem page. sub listStudents { - my ($request) = shift; + my ($request,$symb,$submitonly,$divforres) = @_; - my ($symb) = &get_symb($request); + my $is_tool = ($symb =~ /ext\.tool$/); 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 $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='