--- loncom/homework/structuretags.pm 2014/11/21 18:04:57 1.523 +++ loncom/homework/structuretags.pm 2014/11/24 02:36:26 1.524 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # definition of tags that give a structure to a document # -# $Id: structuretags.pm,v 1.523 2014/11/21 18:04:57 raeburn Exp $ +# $Id: structuretags.pm,v 1.524 2014/11/24 02:36:26 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -897,12 +897,37 @@ sub initialize_storage { =item finalize_storage() - Stores away the result hash to a student's environment + Stores away the result hash to a student's environment; checks form.grade_ for specific values, otherwise stores to the running user's environment. - Will increment totals for attempts, students, and corrects - if running user has student role. - + + &check_correctness_changes() is called in two circumstances + in which the results hash is to be stored permanently, for + grading triggered by a student's submission, where feedback on + correctness is to be provided to the student. + + 1. Immediately prior to storing the results hash + + To handle the case where a student's submission (and award) were + stored after history was retrieved in &initialize_storage(), e.g., + if a student submitted answers in quick succession (e.g., from + multiple tabs). &Apache::inputtags::hidealldata() is called for + any parts with out-of-order storage (i.e., correct then incorrect, + where awarded >= 1 when correct). + + 2. Immediately after storing the results hash + + To handle the case where lond on the student's homeserver returns + delay:N -- where N is the number of transactions between the last + retrieved in &initialize_storage() and the last stored immediately + before permanent storage of the current transaction via + lond::store_handler(). &Apache::grades::makehidden() is called + for any parts with out-of-order storage (i.e., correct then incorrect, + where awarded >= 1 when correct). + + Will call &store_aggregates() to increment totals for attempts, + students, and corrects, if running user has student role. + =cut @@ -923,8 +948,92 @@ sub finalize_storage { $namespace,'',$domain,$name); &Apache::lonxml::debug('Construct Store return message:'.$result); } else { + my ($laststore,$checkedparts,@parts,%postcorrect); + if (($env{'user.name'} eq $name) && ($env{'user.domain'} eq $domain) && + (!$Apache::lonhomework::scantronmode) && (!defined($env{'form.grade_symb'})) && + (!defined($env{'form.grade_courseid'}))) { + if ($Apache::lonhomework::history{'version'}) { + $laststore = $Apache::lonhomework::history{'version'}.'='. + $Apache::lonhomework::history{'timestamp'}; + } else { + $laststore = '0=0'; + } + my %record = &Apache::lonnet::restore($symb,$courseid,$domain,$name); + if ($record{'version'}) { + my ($newversion,$oldversion,$oldtimestamp); + if ($Apache::lonhomework::history{'version'}) { + $oldversion = $Apache::lonhomework::history{'version'}; + $oldtimestamp = $Apache::lonhomework::history{'timestamp'}; + } else { + $oldversion = 0; + $oldtimestamp = 0; + } + if ($record{'version'} > $oldversion) { + if ($record{'timestamp'} >= $oldtimestamp) { + $laststore = $record{'version'}.'='.$record{'timestamp'}; + $newversion = $record{'version'} + 1; + $checkedparts = 1; + foreach my $key (keys(%Apache::lonhomework::results)) { + if ($key =~ /^resource\.([^\.]+)\.solved$/) { + my $part = $1; + if ($Apache::lonhomework::results{$key} eq 'incorrect_attempted') { + push(@parts,$part); + } + } + } + if (@parts) { + my @parts_to_hide = &check_correctness_changes($symb,$courseid,$domain,$name, + \%record,\@parts,$newversion, + $oldversion); + if (@parts_to_hide) { + foreach my $part (@parts_to_hide) { + $postcorrect{$part} = 1; + &Apache::inputtags::hidealldata($part); + } + } + } + } + } + } + } $result=&Apache::lonnet::cstore(\%Apache::lonhomework::results, - $symb,$courseid,$domain,$name); + $symb,$courseid,$domain,$name,$laststore); + if ($result =~ /^delay\:(\d+)$/) { + my $numtrans = $1; + my ($oldversion) = split(/=/,$laststore); + if ($numtrans) { + my $newversion = $oldversion + 1 + $numtrans; + my @possparts; + if ($checkedparts) { + foreach my $part (@parts) { + unless ($postcorrect{$part}) { + push(@possparts,$part); + } + } + } else { + foreach my $key (keys(%Apache::lonhomework::results)) { + if ($key =~ /^resource\.([^\.]+)\.solved$/) { + my $part = $1; + unless ($postcorrect{$part}) { + if ($Apache::lonhomework::results{$key} eq 'incorrect_attempted') { + push(@possparts,$part); + } + } + } + } + } + if (@possparts) { + my %newrecord = &Apache::lonnet::restore($symb,$courseid,$domain,$name); + my @parts_to_hide = &check_correctness_changes($symb,$courseid,$domain,$name, + \%newrecord,\@possparts,$newversion, + $oldversion); + if (@parts_to_hide) { + my $partslist = join(',',@parts_to_hide); + &Apache::grades::makehidden($newversion,$partslist,\%newrecord,$symb,$domain,$name,1); + } + } + } + } &Apache::lonxml::debug('Store return message:'.$result); &store_aggregates($symb,$courseid); } @@ -935,6 +1044,62 @@ sub finalize_storage { } =pod + +=item check_correctness_changes() + + For all parts for which current results contain a solved status + of "incorrect_attempted", check if there was a transaction in which + solved was set to "correct_by_student" in the time since the last + transaction (retrieved when &initialize_storage() was called i.e., + when &start_problem() was called), unless: + (a) questiontype parameter is set to survey or anonymous survey (+/- credit) + (b) problemstatus is set to no or no_feedback_ever + If such a transaction exists, and did not occur after "reset status" + by a user with grading privileges, then the current transaction is an + example of an out-of-order transaction (i.e., incorrect occurring after + correct). Accordingly, the current transaction should be hidden. + +=cut + + +sub check_correctness_changes { + my ($symb,$courseid,$domain,$name,$record,$parts,$newversion,$oldversion) = @_; + my @parts_to_hide; + unless ((ref($record) eq 'HASH') && (ref($parts) eq 'ARRAY')) { + return @parts_to_hide; + } + if (@{$parts}) { + my $usec; + if (($env{'user.name'} eq $name) && ($env{'user.domain'} eq $domain) && + ($env{'request.course.id'} eq $courseid)) { + $usec = $env{'request.course.sec'}; + } else { + $usec = &Apache::lonnet::getsection($domain,$name,$courseid); + } + foreach my $id (@{$parts}) { + next if (($Apache::lonhomework::results{'resource.'.$id.'.type'} =~ /survey/) || + (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb, + $domain,$name,$usec,undef,$courseid) =~ /^no/)); + my $reset; + for (my $i=$newversion-1; $i>=$oldversion; $i--) { + if (($record->{$i.':resource.'.$id.'.regrader'}) && + ($record->{$i.':resource.'.$id.'.tries'} eq '') && + ($record->{$i.':resource.'.$id.'.award'} eq '')) { + $reset = 1; + } elsif (($record->{$i.":resource.$id.solved"} eq 'correct_by_student') && + ($record->{$i.":resource.$id.awarded"} >= 1)) { + unless ($reset) { + push(@parts_to_hide,$id); + last; + } + } + } + } + } + return @parts_to_hide; +} + +=pod item store_aggregates()