# The LearningOnline Network with CAPA # The LON-CAPA Grading handler # # $Id: grades.pm,v 1.596.2.12.2.37 2016/10/14 17:33:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # package Apache::grades; use strict; use Apache::style; use Apache::lonxml; use Apache::lonnet; use Apache::loncommon; use Apache::lonhtmlcommon; use Apache::lonnavmaps; use Apache::lonhomework; use Apache::lonpickcode; use Apache::loncoursedata; use Apache::lonmsg(); use Apache::Constants qw(:common :http); use Apache::lonlocal; use Apache::lonenc; use Apache::bridgetask(); use String::Similarity; use LONCAPA; 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; 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; } # # Prodcuces an ssi retry failure error message to the user: # sub ssi_print_error { my ($r) = @_; my $helpurl = &Apache::loncommon::top_nav_help('Helpdesk'); $r->print('

'.&mt('An unrecoverable network error occurred:').'

'.&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). '

'); return; } # # --- Retrieve the parts from the metadata file.--- sub getpartlist { my ($symb,$errorref) = @_; my $navmap = Apache::lonnavmaps::navmap->new(); unless (ref($navmap)) { if (ref($errorref)) { $$errorref = 'navmap'; return; } } my $res = $navmap->getBySymb($symb); my $partlist = $res->parts(); my $url = $res->src(); my @metakeys = split(/,/,&Apache::lonnet::metadata($url,'keys')); my @stores; foreach my $part (@{ $partlist }) { foreach my $key (@metakeys) { if ($key =~ m/^stores_\Q$part\E_/) { push(@stores,$key); } } } return @stores; } # --- Get the symbolic name of a problem and the url sub get_symb { my ($request,$silent) = @_; my $symb=$env{'form.symb'}; unless ($symb) { (my $url=$env{'form.url'}) =~ s-^http://($ENV{'SERVER_NAME'}|$ENV{'HTTP_HOST'})--; $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 { my ($type,$fullname,$uname,$udom) = @_; if ($type eq 'header') { return ' '.&mt('Fullname').' ('.&mt('Username').')'; } else { return ' '.$fullname.' ('.$uname. ($env{'user.domain'} eq $udom ? '' : ' ('.$udom.')').')'; } } #--- Get the partlist and the response type for a given problem. --- #--- Indicate if a response type is coded handgraded or not. --- sub response_type { my ($symb,$response_error) = @_; my $navmap = Apache::lonnavmaps::navmap->new(); unless (ref($navmap)) { if (ref($response_error)) { $$response_error = 1; } return; } my $res = $navmap->getBySymb($symb); unless (ref($res)) { $$response_error = 1; return; } my $partlist = $res->parts(); 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++) { $response_types{$part}{$ids[$i]} = $types[$i]; $handgrade{$part.'_'.$ids[$i]} = &Apache::lonnet::EXT('resource.'.$part.'_'.$ids[$i]. '.handgrade',$symb); } } return ($partlist,\%handgrade,\%response_types); } 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 resource title #--- and parts and response type sub showResourceInfo { my ($symb,$probTitle,$checkboxes,$res_error) = @_; my $result = '

'.&mt('Current Resource').': '.$probTitle.'

'."\n"; my ($partlist,$handgrade,$responseType) = &response_type($symb,$res_error); if (ref($res_error)) { if ($$res_error) { return; } } $result.=&Apache::loncommon::start_data_table() .&Apache::loncommon::start_data_table_header_row(); if ($checkboxes) { $result.=' '; } $result.=''.&mt('Problem Part').'' .''.&mt('Res. ID').'' .''.&mt('Type').'' .&Apache::loncommon::end_data_table_header_row(); my %resptype = (); my $hdgrade='no'; my %partsseen; foreach my $partID (sort(keys(%$responseType))) { foreach my $resID (sort(keys(%{ $responseType->{$partID} }))) { my $handgrade=$$handgrade{$partID.'_'.$resID}; my $responsetype = $responseType->{$partID}->{$resID}; $hdgrade = $handgrade if ($handgrade eq 'yes'); $result.=&Apache::loncommon::start_data_table_row(); if ($checkboxes) { if (exists($partsseen{$partID})) { $result.=" "; } else { $result.=""; } $partsseen{$partID}=1; } my $display_part=&get_display_part($partID,$symb); $result.=''.$display_part.'' .''.''.$resID.'' .''.&mt($responsetype).'' # .''.&mt('Handgrade: [_1]',$handgrade).'' .&Apache::loncommon::end_data_table_row(); } } $result.=&Apache::loncommon::end_data_table(); return $result,$responseType,$hdgrade,$partlist,$handgrade; } sub reset_caches { &reset_analyze_cache(); &reset_perm(); &reset_old_essays(); } { my %analyze_cache; my %analyze_cache_formkeys; sub reset_analyze_cache { undef(%analyze_cache); undef(%analyze_cache_formkeys); } sub get_analyze { my ($symb,$uname,$udom,$no_increment,$add_to_hash,$type,$trial,$rndseed,$bubbles_per_row)=@_; my $key = "$symb\0$uname\0$udom"; if ($type eq 'randomizetry') { if ($trial ne '') { $key .= "\0".$trial; } } if (exists($analyze_cache{$key})) { my $getupdate = 0; if (ref($add_to_hash) eq 'HASH') { foreach my $item (keys(%{$add_to_hash})) { if (ref($analyze_cache_formkeys{$key}) eq 'HASH') { if (!exists($analyze_cache_formkeys{$key}{$item})) { $getupdate = 1; last; } } else { $getupdate = 1; } } } if (!$getupdate) { return $analyze_cache{$key}; } } my (undef,undef,$url)=&Apache::lonnet::decode_symb($symb); $url=&Apache::lonnet::clutter($url); my %form = ('grade_target' => 'analyze', 'grade_domain' => $udom, 'grade_symb' => $symb, 'grade_courseid' => $env{'request.course.id'}, 'grade_username' => $uname, 'grade_noincrement' => $no_increment); if ($bubbles_per_row ne '') { $form{'bubbles_per_row'} = $bubbles_per_row; } if ($type eq 'randomizetry') { $form{'grade_questiontype'} = $type; if ($rndseed ne '') { $form{'grade_rndseed'} = $rndseed; } } if (ref($add_to_hash)) { %form = (%form,%{$add_to_hash}); } my $subresult=&ssi_with_retries($url, $ssi_retries,%form); (undef,$subresult)=split(/_HASH_REF__/,$subresult,2); my %analyze=&Apache::lonnet::str2hash($subresult); if (ref($add_to_hash) eq 'HASH') { $analyze_cache_formkeys{$key} = $add_to_hash; } else { $analyze_cache_formkeys{$key} = {}; } return $analyze_cache{$key} = \%analyze; } sub get_order { my ($partid,$respid,$symb,$uname,$udom,$no_increment,$type,$trial,$rndseed)=@_; my $analyze = &get_analyze($symb,$uname,$udom,$no_increment,undef,$type,$trial,$rndseed); return $analyze->{"$partid.$respid.shown"}; } sub get_radiobutton_correct_foil { my ($partid,$respid,$symb,$uname,$udom,$type,$trial,$rndseed)=@_; my $analyze = &get_analyze($symb,$uname,$udom,undef,undef,$type,$trial,$rndseed); my $foils = &get_order($partid,$respid,$symb,$uname,$udom,undef,$type,$trial,$rndseed); if (ref($foils) eq 'ARRAY') { foreach my $foil (@{$foils}) { if ($analyze->{"$partid.$respid.foil.value.$foil"} eq 'true') { return $foil; } } } } sub scantron_partids_tograde { my ($resource,$cid,$uname,$udom,$check_for_randomlist,$bubbles_per_row) = @_; my (%analysis,@parts); if (ref($resource)) { my $symb = $resource->symb(); my $add_to_form; if ($check_for_randomlist) { $add_to_form = { 'check_parts_withrandomlist' => 1,}; } my $analyze = &get_analyze($symb,$uname,$udom,undef,$add_to_form, undef,undef,undef,$bubbles_per_row); if (ref($analyze) eq 'HASH') { %analysis = %{$analyze}; } if (ref($analysis{'parts'}) eq 'ARRAY') { foreach my $part (@{$analysis{'parts'}}) { my ($id,$respid) = split(/\./,$part); if (!&Apache::loncommon::check_if_partid_hidden($id,$symb,$udom,$uname)) { push(@parts,$part); } } } } return (\%analysis,\@parts); } } #--- Clean response type for display #--- Currently filters option/rank/radiobutton/match/essay/Task # response types only. sub cleanRecord { my ($answer,$response,$symb,$partid,$respid,$record,$order,$version, $uname,$udom,$type,$trial,$rndseed) = @_; my $grayFont = ''; if ($response =~ /^(option|rank)$/) { 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 ($toprow,$bottomrow); foreach my $foil (@$order) { if ($grading{$foil} == 1) { $toprow.=''.$answer{$foil}.' '; } else { $toprow.=''.$answer{$foil}.' '; } $bottomrow.=''.$grayFont.$foil.' '; } return '
'. ''.$toprow.''. ''. $bottomrow.'
'.&mt('Answer').'
'.$grayFont.&mt('Option ID').'
'; } 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.=''.$item.' '; $middlerow.=''.$grayFont.$answer{$foil}.' '; } else { $toprow.=''.$item.' '; $middlerow.=''.$grayFont.$answer{$foil}.' '; } $bottomrow.=''.$grayFont.$foil.' '; } return '
'. ''.$toprow.''. ''. $middlerow.''. ''. $bottomrow.'
'.&mt('Answer').'
'.$grayFont.&mt('Item ID').'
'.$grayFont.&mt('Option ID').'
'; } elsif ($response eq 'radiobutton') { my %answer=&Apache::lonnet::str2hash($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('true').''; } else { $toprow.=''.&mt('true').''; } } else { $toprow.=''.&mt('false').''; } $bottomrow.=''.$grayFont.$foil.' '; } return '
'. ''.$toprow.''. ''. $bottomrow.'
'.&mt('Answer').'
'.$grayFont.&mt('Option ID').'
'; } elsif ($response eq 'essay') { if (! exists ($env{'form.'.$symb})) { my (%keyhash) = &Apache::lonnet::dump('nohist_handgrade', $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'}); my $loginuser = $env{'user.name'}.':'.$env{'user.domain'}; $env{'form.keywords'} = $keyhash{$symb.'_keywords'} ne '' ? $keyhash{$symb.'_keywords'} : ''; $env{'form.kwclr'} = $keyhash{$loginuser.'_kwclr'} ne '' ? $keyhash{$loginuser.'_kwclr'} : 'red'; $env{'form.kwsize'} = $keyhash{$loginuser.'_kwsize'} ne '' ? $keyhash{$loginuser.'_kwsize'} : '0'; $env{'form.kwstyle'} = $keyhash{$loginuser.'_kwstyle'} ne '' ? $keyhash{$loginuser.'_kwstyle'} : ''; $env{'form.'.$symb} = 1; # so that we don't have to read it from disk for multiple sub of the same prob. } return '

'.&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('
',($version,@matches)); } else { my $result = '

' .&mt('Overall result: [_1]', $record->{$version."resource.$respid.$partid.status"}) .'

'; $result .= ''; return $result; } } 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 &HTML::Entities::encode($answer, '"<>&'); } #-- A couple of common js functions sub commonJSfunctions { my $request = shift; $request->print(< function radioSelection(radioButton) { var selection=null; if (radioButton.length > 1) { for (var i=0; i 1) { for (var i=0; i COMMONJSFUNCTIONS } #--- Dumps the class list with usernames,list of sections, #--- section, ids and fullnames for each user. sub getclasslist { my ($getsec,$filterlist,$getgroup) = @_; my @getsec; my @getgroup; my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); if (!ref($getsec)) { if ($getsec ne '' && $getsec ne 'all') { @getsec=($getsec); } } else { @getsec=@{$getsec}; } if (grep(/^all$/,@getsec)) { undef(@getsec); } if (!ref($getgroup)) { if ($getgroup ne '' && $getgroup ne 'all') { @getgroup=($getgroup); } } else { @getgroup=@{$getgroup}; } if (grep(/^all$/,@getgroup)) { undef(@getgroup); } my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist(); # Bail out if we were unable to get the classlist return if (! defined($classlist)); &Apache::loncoursedata::get_group_memberships($classlist,$keylist); # my %sections; my %fullnames; foreach my $student (keys(%$classlist)) { my $end = $classlist->{$student}->[&Apache::loncoursedata::CL_END()]; my $start = $classlist->{$student}->[&Apache::loncoursedata::CL_START()]; my $id = $classlist->{$student}->[&Apache::loncoursedata::CL_ID()]; my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()]; my $fullname = $classlist->{$student}->[&Apache::loncoursedata::CL_FULLNAME()]; my $status = $classlist->{$student}->[&Apache::loncoursedata::CL_STATUS()]; my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()]; # filter students according to status selected if ($filterlist && (!($stu_status =~ /Any/))) { if (!($stu_status =~ $status)) { delete($classlist->{$student}); next; } } # filter students according to groups selected my @stu_groups = split(/,/,$group); if (@getgroup) { my $exclude = 1; foreach my $grp (@getgroup) { foreach my $stu_group (@stu_groups) { if ($stu_group eq $grp) { $exclude = 0; } } if (($grp eq 'none') && !$group) { $exclude = 0; } } if ($exclude) { delete($classlist->{$student}); } } $section = ($section ne '' ? $section : 'none'); if (&canview($section)) { if (!@getsec || grep(/^\Q$section\E$/,@getsec)) { $sections{$section}++; if ($classlist->{$student}) { $fullnames{$student}=$fullname; } } else { delete($classlist->{$student}); } } else { delete($classlist->{$student}); } } my %seen = (); my @sections = sort(keys(%sections)); return ($classlist,\@sections,\%fullnames); } sub canmodify { my ($sec)=@_; if ($perm{'mgr'}) { if (!defined($perm{'mgr_section'})) { # can modify whole class return 1; } else { if ($sec eq $perm{'mgr_section'}) { #can modify the requested section return 1; } else { # can't modify the request section return 0; } } } #can't modify return 0; } sub canview { my ($sec)=@_; if ($perm{'vgr'}) { if (!defined($perm{'vgr_section'})) { # can modify whole class return 1; } else { if ($sec eq $perm{'vgr_section'}) { #can modify the requested section return 1; } else { # can't modify the request section return 0; } } } #can't modify return 0; } #--- Retrieve the grade status of a student for all the parts sub student_gradeStatus { my ($symb,$udom,$uname,$partlist) = @_; my %record = &Apache::lonnet::restore($symb,$env{'request.course.id'},$udom,$uname); my %partstatus = (); foreach (@$partlist) { my ($status,undef) = split(/_/,$record{"resource.$_.solved"},2); $status = 'nothing' if ($status eq ''); $partstatus{$_} = $status; my $subkey = "resource.$_.submitted_by"; $partstatus{$subkey} = $record{$subkey} if ($record{$subkey} ne ''); } return %partstatus; } # hidden form and javascript that calls the form # Use by verifyscript and viewgrades # Shows a student's view of problem and submission sub jscriptNform { my ($symb) = @_; my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); my $jscript=''."\n"; $jscript.= '
'."\n". ''."\n". ''."\n". ''."\n". ''."\n". ''."\n". ''."\n". ''."\n". '
'."\n"; return $jscript; } # 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. sub compute_points { my ($score, $weight) = @_; my $tolerance = .00001; my $points = $score * $weight; # Check for nearness to 1/x. my $check_for_nearness = sub { my ($factor) = @_; my $num = ($points * $factor) + $tolerance; my $floored_num = floor($num); if ($num - $floored_num < 2 * $tolerance * $factor) { return $floored_num / $factor; } return $points; }; $points = $check_for_nearness->(10); $points = $check_for_nearness->(3); $points = $check_for_nearness->(4); return $points; } #------------------ End of general use routines -------------------- # # Find most similar essay # sub most_similar { my ($uname,$udom,$symb,$uessay)=@_; unless ($symb) { return ''; } unless (ref($old_essays{$symb}) eq 'HASH') { return ''; } # ignore spaces and punctuation $uessay=~s/\W+/ /gs; # ignore empty submissions (occuring when only files are sent) unless ($uessay=~/\w+/s) { return ''; } # these will be returned. Do not care if not at least 50 percent similar my $limit=0.6; my $sname=''; my $sdom=''; my $scrsid=''; my $sessay=''; # go through all essays ... foreach my $tkey (keys(%{$old_essays{$symb}})) { my ($tname,$tdom,$tcrsid)=map {&unescape($_)} (split(/\./,$tkey)); # ... except the same student next if (($tname eq $uname) && ($tdom eq $udom)); my $tessay=$old_essays{$symb}{$tkey}; $tessay=~s/\W+/ /gs; # String similarity gives up if not even limit my $tsimilar=&String::Similarity::similarity($uessay,$tessay,$limit); # Found one if ($tsimilar>$limit) { $limit=$tsimilar; $sname=$tname; $sdom=$tdom; $scrsid=$tcrsid; $sessay=$old_essays{$symb}{$tkey}; } } if ($limit>0.6) { return ($sname,$sdom,$scrsid,$sessay,$limit); } else { return ('','','','',0); } } #------------------------------------------------------------------- #------------------------------------ Receipt Verification Routines # #--- Check whether a receipt number is valid.--- sub verifyreceipt { my $request = shift; my $courseid = $env{'request.course.id'}; my $receipt = &Apache::lonnet::recprefix($courseid).'-'. $env{'form.receipt'}; $receipt =~ s/[^\-\d]//g; my ($symb) = &get_symb($request); my $title.= '

'. &mt('Verifying Receipt No. [_1]',$receipt). '

'."\n". '

'.&mt('[_1]Resource: [_2]','',''.$env{'form.probTitle'}). '

'."\n"; my ($string,$contents,$matches) = ('','',0); my (undef,undef,$fullname) = &getclasslist('all','0'); my $receiptparts=0; if ($env{"course.$courseid.receiptalg"} eq 'receipt2' || $env{"course.$courseid.receiptalg"} eq 'receipt3') { $receiptparts=1; } my $parts=['0']; if ($receiptparts) { my $res_error; ($parts)=&response_type($symb,\$res_error); if ($res_error) { return &navmap_errormsg(); } } my $header = &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(). ' '.&mt('Fullname').' '."\n". ' '.&mt('Username').' '."\n". ' '.&mt('Domain').' '; if ($receiptparts) { $header.=' '.&mt('Problem Part').' '; } $header.= &Apache::loncommon::end_data_table_header_row(); foreach (sort { if (lc($$fullname{$a}) ne lc($$fullname{$b})) { return (lc($$fullname{$a}) cmp lc($$fullname{$b})); } return $a cmp $b; } (keys(%$fullname))) { my ($uname,$udom)=split(/\:/); foreach my $part (@$parts) { if ($receipt eq &Apache::lonnet::ireceipt($uname,$udom,$courseid,$symb,$part)) { $contents.= &Apache::loncommon::start_data_table_row(). ' '."\n". ''.$$fullname{$_}.' '."\n". ' '.$uname.' '. ' '.$udom.' '; if ($receiptparts) { $contents.=' '.$part.' '; } $contents.= &Apache::loncommon::end_data_table_row()."\n"; $matches++; } } } if ($matches == 0) { $string = $title .'

' .&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.&show_grading_menu_form($symb); } #--- This is called by a number of programs. #--- Called from the Grading Menu - View/Grade an individual student #--- Also called directly when one clicks on the subm button # on the problem page. sub listStudents { my ($request) = shift; my ($symb) = &get_symb($request); my $cdom = $env{"course.$env{'request.course.id'}.domain"}; my $cnum = $env{"course.$env{'request.course.id'}.num"}; my $getsec = $env{'form.section'} eq '' ? 'all' : $env{'form.section'}; my $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='

 ' .&mt("$viewgrade Submissions for a Student or a Group of Students") .'

'; my ($table,undef,$hdgrade,$partlist,$handgrade) = &showResourceInfo($symb,$env{'form.probTitle'},($env{'form.showgrading'} eq 'yes')); my %js_lt = &Apache::lonlocal::texthash ( 'multiple' => 'Please select a student or group of students before clicking on the Next button.', 'single' => 'Please select the student before clicking on the Next button.', ); &js_escape(\%js_lt); $request->print(< function checkSelect(checkBox) { var ctr=0; var sense=""; if (checkBox.length > 1) { for (var i=0; i LISTJAVASCRIPT &commonJSfunctions($request); $request->print($result); my $checkhdgrade = ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1 ) ? 'checked="checked"' : ''; my $checklastsub = $checkhdgrade eq '' ? 'checked="checked"' : ''; my $gradeTable='
'. "\n".$table; $gradeTable .= &Apache::lonhtmlcommon::start_pick_box(); $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Problem Text')) .''."\n" .''."\n" .'
'."\n" .&Apache::lonhtmlcommon::row_closure(); $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('View Answer')) .''."\n" .''."\n" .'
'."\n" .&Apache::lonhtmlcommon::row_closure(); my $submission_options; if ($env{'form.handgrade'} eq 'yes' && scalar(@$partlist) > 1) { $submission_options.= ''."\n"; } my $stu_status = join(':',&Apache::loncommon::get_env_multiple('form.Status')); my $saveStatus = $stu_status eq '' ? 'Active' : $stu_status; $env{'form.Status'} = $saveStatus; $submission_options.= ''. ''."\n". ''. ''."\n". ''. ''."\n". ''. ''; $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Submissions')) .$submission_options .&Apache::lonhtmlcommon::row_closure(); $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Grading Increments')) .'' .&Apache::lonhtmlcommon::row_closure(); $gradeTable .= &build_section_inputs(). ''."\n". '
'."\n". '
'."\n". ''."\n". ''."\n". ''."\n". ''."\n"; if (exists($env{'form.gradingMenu'}) && exists($env{'form.Status'})) { $gradeTable .= ''."\n"; } else { $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Student Status')) .&Apache::lonhtmlcommon::StatusOptions( $saveStatus,undef,1,'javascript:reLoadList(this.form);') .&Apache::lonhtmlcommon::row_closure(); } $gradeTable .= &Apache::lonhtmlcommon::row_title(&mt('Check For Plagiarism')) .'' .&Apache::lonhtmlcommon::row_closure(1) .&Apache::lonhtmlcommon::end_pick_box(); $gradeTable .= '

' .&mt('To '.lc($viewgrade)." a submission or a group of submissions, click on the check box(es) next to the student's name(s). Then click on the Next button.")."\n" .'' .'

'; # checkall buttons $gradeTable.=&check_script('gradesub', 'stuinfo'); $gradeTable.='
'."\n"; $gradeTable.=&check_buttons(); my ($classlist, undef, $fullname) = &getclasslist($getsec,'1',$getgroup); $gradeTable.= &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(); my $loop = 0; while ($loop < 2) { $gradeTable.=''.&mt('No.').''.&mt('Select').''. ''.&nameUserString('header').' '.&mt('Section/Group').''; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'queued' && $submitonly ne 'all') { foreach my $part (sort(@$partlist)) { my $display_part= &get_display_part((split(/_/,$part))[0],$symb); $gradeTable.= ''.&mt('Part: [_1] Status',$display_part).''; } } elsif ($submitonly eq 'queued') { $gradeTable.=''.&mt('Queue Status').' '; } $loop++; # $gradeTable.='' if ($loop%2 ==1); } $gradeTable.=&Apache::loncommon::end_data_table_header_row()."\n"; my $ctr = 0; 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))) { my ($uname,$udom) = split(/:/,$student); my %status = (); if ($submitonly eq 'queued') { my %queue_status = &Apache::bridgetask::get_student_status($symb,$cdom,$cnum, $udom,$uname); next if (!defined($queue_status{'gradingqueue'})); $status{'gradingqueue'} = $queue_status{'gradingqueue'}; } if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'queued' && $submitonly ne 'all') { (%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; my ($part)=split(/\./,$partid); $gradeTable.=''; } } next if (!$submitted && ($submitonly eq 'yes' || $submitonly eq 'incorrect' || $submitonly eq 'graded')); next if (!$graded && ($submitonly eq 'graded')); next if (!$incorrect && $submitonly eq 'incorrect'); } $ctr++; my $section = $classlist->{$student}->[&Apache::loncoursedata::CL_SECTION()]; my $group = $classlist->{$student}->[&Apache::loncoursedata::CL_GROUP()]; if ( $perm{'vgr'} eq 'F' ) { if ($ctr%2 ==1) { $gradeTable.= &Apache::loncommon::start_data_table_row(); } $gradeTable.=''.$ctr.' '. ''."\n".''. &nameUserString(undef,$$fullname{$student},$uname,$udom). ' '.$section.($group ne '' ?'/'.$group:'').''."\n"; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'all') { foreach (sort(keys(%status))) { next if ($_ =~ /^resource.*?submitted_by$/); $gradeTable.=' '.&mt($status{$_}).' '."\n"; } } # $gradeTable.='' if ($ctr%2 ==1); if ($ctr%2 ==0) { $gradeTable.=&Apache::loncommon::end_data_table_row()."\n"; } } } if ($ctr%2 ==1) { $gradeTable.='   '; if ($env{'form.showgrading'} eq 'yes' && $submitonly ne 'queued' && $submitonly ne 'all') { foreach (@$partlist) { $gradeTable.=' '; } } elsif ($submitonly eq 'queued') { $gradeTable.=' '; } $gradeTable.=&Apache::loncommon::end_data_table_row(); } $gradeTable.=&Apache::loncommon::end_data_table()."\n". '
'."\n"; if ($ctr == 0) { my $num_students=(scalar(keys(%$fullname))); if ($num_students eq 0) { $gradeTable='
 '.&mt('There are no students currently enrolled.').''; } else { my $submissions='submissions'; if ($submitonly eq 'incorrect') { $submissions = 'incorrect submissions'; } if ($submitonly eq 'graded' ) { $submissions = 'ungraded submissions'; } if ($submitonly eq 'queued' ) { $submissions = 'queued submissions'; } $gradeTable='
 '. &mt('No '.$submissions.' found for this resource for any students. ([quant,_1,student] checked for '.$submissions.')', $num_students). '
'; } } elsif ($ctr == 1) { $gradeTable =~ s/type="checkbox"/type="checkbox" checked="checked"/; } $gradeTable.=&show_grading_menu_form($symb); $request->print($gradeTable); return ''; } #---- Called from the listStudents routine sub check_script { my ($form, $type)=@_; my $chkallscript=''."\n"; return $chkallscript; } sub check_buttons { my $buttons.=''; $buttons.=' '; $buttons.=''; $buttons.=' '; return $buttons; } # Displays the submissions for one student or a group of students sub processGroup { my ($request) = shift; my $ctr = 0; my @stuchecked = &Apache::loncommon::get_env_multiple('form.stuinfo'); my $total = scalar(@stuchecked)-1; foreach my $student (@stuchecked) { my ($uname,$udom,$fullname) = split(/:/,$student); $env{'form.student'} = $uname; $env{'form.userdom'} = $udom; $env{'form.fullname'} = $fullname; &submission($request,$ctr,$total); $ctr++; } return ''; } #------------------------------------------------------------------------------------ # #-------------------------- Next few routines handles grading by student, essentially # handles essay response type problem/part # #--- Javascript to handle the submission page functionality --- sub sub_page_js { my $request = shift; my $alertmsg = &mt('A number equal or greater than 0 is expected. Entered value = '); &js_escape(\$alertmsg); $request->print(< function updateRadio(formname,id,weight) { var gradeBox = formname["GD_BOX"+id]; var radioButton = formname["RADVAL"+id]; var oldpts = formname["oldpts"+id].value; var pts = checkSolved(formname,id) == 'update' ? gradeBox.value : oldpts; gradeBox.value = pts; var resetbox = false; if (isNaN(pts) || pts < 0) { alert("$alertmsg"+pts); for (var i=0; i weight) { var resp = confirm("You entered a value ("+pts+ ") greater than the weight for the part. Accept?"); if (resp == false) { gradeBox.value = oldpts; return; } } for (var i=0; i SUBJAVASCRIPT } #--- javascript for essay type problem -- sub sub_page_kw_js { my $request = shift; my $iconpath = $request->dir_config('lonIconsURL'); &commonJSfunctions($request); my $inner_js_msg_central=< function checkInput() { opener.document.SCORE.msgsub.value = opener.checkEntities(document.msgcenter.msgsub.value); var nmsg = opener.document.SCORE.savemsgN.value; var usrctr = document.msgcenter.usrctr.value; var newval = opener.document.SCORE["newmsg"+usrctr]; newval.value = opener.checkEntities(document.msgcenter.newmsg.value); var msgchk = ""; if (document.msgcenter.subchk.checked) { msgchk = "msgsub,"; } var includemsg = 0; for (var i=1; i<=nmsg; i++) { var opnmsg = opener.document.SCORE["savemsg"+i]; var frmmsg = document.msgcenter["msg"+i]; opnmsg.value = opener.checkEntities(frmmsg.value); var showflg = opener.document.SCORE["shownOnce"+i]; showflg.value = "1"; var chkbox = document.msgcenter["msgn"+i]; if (chkbox.checked) { msgchk += "savemsg"+i+","; includemsg = 1; } } if (document.msgcenter.newmsgchk.checked) { msgchk += "newmsg"+usrctr; includemsg = 1; } imgformname = opener.document.SCORE["mailicon"+usrctr]; imgformname.src = "$iconpath/"+((includemsg) ? "mailto.gif" : "mailbkgrd.gif"); var includemsg = opener.document.SCORE["includemsg"+usrctr]; includemsg.value = msgchk; self.close() } INNERJS my $inner_js_highlight_central=< function updateChoice(flag) { opener.document.SCORE.kwclr.value = opener.radioSelection(document.hlCenter.kwdclr); opener.document.SCORE.kwsize.value = opener.radioSelection(document.hlCenter.kwdsize); opener.document.SCORE.kwstyle.value = opener.radioSelection(document.hlCenter.kwdstyle); opener.document.SCORE.refresh.value = "on"; if (opener.document.SCORE.keywords.value!=""){ opener.document.SCORE.submit(); } self.close() } INNERJS my $start_page_msg_central = &Apache::loncommon::start_page('Message Central',$inner_js_msg_central, {'js_ready' => 1, 'only_body' => 1, 'bgcolor' =>'#FFFFFF',}); my $end_page_msg_central = &Apache::loncommon::end_page({'js_ready' => 1}); my $start_page_highlight_central = &Apache::loncommon::start_page('Highlight Central', $inner_js_highlight_central, {'js_ready' => 1, 'only_body' => 1, 'bgcolor' =>'#FFFFFF',}); my $end_page_highlight_central = &Apache::loncommon::end_page({'js_ready' => 1}); my $docopen=&Apache::lonhtmlcommon::javascript_docopen(); $docopen=~s/^document\.//; my %js_lt = &Apache::lonlocal::texthash( keyw => 'Keywords list, separated by a space. Add/delete to list if desired.', plse => 'Please select a word or group of words from document and then click this link.', adds => 'Add selection to keyword list? Edit if desired.', col1 => 'red', col2 => 'green', col3 => 'blue', siz1 => 'normal', siz2 => '+1', siz3 => '+2', sty1 => 'normal', sty2 => 'italic', sty3 => 'bold', ); my %html_js_lt = &Apache::lonlocal::texthash( comp => 'Compose Message for: ', incl => 'Include', type => 'Type', subj => 'Subject', mesa => 'Message', new => 'New', save => 'Save', canc => 'Cancel', kehi => 'Keyword Highlight Options', txtc => 'Text Color', font => 'Font Size', fnst => 'Font Style', ); &js_escape(\%js_lt); &html_escape(\%html_js_lt); &js_escape(\%html_js_lt); $request->print(< //===================== Show list of keywords ==================== function keywords(formname) { var nret = prompt("$js_lt{'keyw'}",formname.keywords.value); if (nret==null) return; formname.keywords.value = nret; if (formname.keywords.value != "") { formname.refresh.value = "on"; formname.submit(); } return; } //===================== Script to view submitted by ================== function viewSubmitter(submitter) { document.SCORE.refresh.value = "on"; document.SCORE.NCT.value = "1"; document.SCORE.unamedom0.value = submitter; document.SCORE.submit(); return; } //===================== Script to add keyword(s) ================== function getSel() { if (document.getSelection) txt = document.getSelection(); else if (document.selection) txt = document.selection.createRange().text; else return; var cleantxt = txt.replace(new RegExp('([\\f\\n\\r\\t\\v ])+', 'g')," "); if (cleantxt=="") { alert("$js_lt{'plse'}"); return; } var nret = prompt("$js_lt{'adds'}",cleantxt); if (nret==null) return; document.SCORE.keywords.value = document.SCORE.keywords.value+" "+nret; if (document.SCORE.keywords.value != "") { document.SCORE.refresh.value = "on"; document.SCORE.submit(); } return; } //====================== Script for composing message ============== // preload images img1 = new Image(); img1.src = "$iconpath/mailbkgrd.gif"; img2 = new Image(); img2.src = "$iconpath/mailto.gif"; function msgCenter(msgform,usrctr,fullname) { var Nmsg = msgform.savemsgN.value; savedMsgHeader(Nmsg,usrctr,fullname); var subject = msgform.msgsub.value; var msgchk = document.SCORE["includemsg"+usrctr].value; re = /msgsub/; var shwsel = ""; if (re.test(msgchk)) { shwsel = "checked" } subject = (document.SCORE.shownSub.value == 0 ? checkEntities(subject) : subject); displaySubject(checkEntities(subject),shwsel); for (var i=1; i<=Nmsg; i++) { var testmsg = "savemsg"+i+","; re = new RegExp(testmsg,"g"); shwsel = ""; if (re.test(msgchk)) { shwsel = "checked" } var message = document.SCORE["savemsg"+i].value; message = (document.SCORE["shownOnce"+i].value == 0 ? checkEntities(message) : message); displaySavedMsg(i,message,shwsel); //I do not get it. w/o checkEntities on saved messages, //any < is already converted to <, etc. However, only once!! } newmsg = document.SCORE["newmsg"+usrctr].value; shwsel = ""; re = /newmsg/; if (re.test(msgchk)) { shwsel = "checked" } newMsg(newmsg,shwsel); msgTail(); return; } function checkEntities(strx) { if (strx.length == 0) return strx; var orgStr = ["&", "<", ">", '"']; var newStr = ["&", "<", ">", """]; var counter = 0; while (counter < 4) { strx = strReplace(strx,orgStr[counter],newStr[counter]); counter++; } return strx; } function strReplace(strx, orgStr, newStr) { return strx.split(orgStr).join(newStr); } function savedMsgHeader(Nmsg,usrctr,fullname) { var height = 70*Nmsg+250; if (height > 600) { height = 600; } var xpos = (screen.width-600)/2; xpos = (xpos < 0) ? '0' : xpos; var ypos = (screen.height-height)/2-30; ypos = (ypos < 0) ? '0' : ypos; pWin = window.open('', 'MessageCenter', 'resizable=yes,toolbar=no,location=no,scrollbars=yes,screenx='+xpos+',screeny='+ypos+',width=700,height='+height); pWin.focus(); pDoc = pWin.document; pDoc.$docopen; pDoc.write('$start_page_msg_central'); pDoc.write("
"); pDoc.write(""); pDoc.write("

 $html_js_lt{'comp'}\"+fullname+\"<\\/span><\\/h3>

"); pDoc.write('
'); pDoc.write(''); pDoc.write(""); pDoc.write(""); pDoc.write("
$html_js_lt{'type'}<\\/b><\\/td>$html_js_lt{'incl'}<\\/b><\\/td>$html_js_lt{'mesa'}<\\/td><\\/tr>"); } function displaySubject(msg,shwsel) { pDoc = pWin.document; pDoc.write("
$html_js_lt{'subj'}<\\/td>"); pDoc.write("<\\/td>"); pDoc.write("<\\/td><\\/tr>"); } function displaySavedMsg(ctr,msg,shwsel) { pDoc = pWin.document; pDoc.write("
"+ctr+"<\\/td>"); pDoc.write("<\\/td>"); pDoc.write("