';
return $result;
@@ -495,7 +1048,7 @@ sub initialize_storage {
}
%Apache::lonhomework::history=
&Apache::lonnet::tmprestore($namespace,'',$domain,$name);
- my ($temp)=keys %Apache::lonhomework::history ;
+ my ($temp)=keys(%Apache::lonhomework::history);
&Apache::lonxml::debug("Return message of $temp");
} else {
%Apache::lonhomework::history=
@@ -503,16 +1056,48 @@ sub initialize_storage {
}
#ignore error conditions
- my ($temp)=keys %Apache::lonhomework::history ;
+ my ($temp)=keys(%Apache::lonhomework::history);
if ($temp =~ m/^error:.*/) { %Apache::lonhomework::history=(); }
}
-# -------------------------------------------------------------finalize_storage
-# Stores away the result has to a student's environment
-# checks form.grade_ for specific values, other wises stores
-# to the running users environment
-# Will increment totals for attempts, students, and corrects
-# if running user has student role.
+=pod
+
+=item finalize_storage()
+
+ Stores away the result hash to a student's environment;
+ checks form.grade_ for specific values, otherwise stores
+ to the running user's environment.
+
+ &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
+
+
sub finalize_storage {
my ($given_symb) = @_;
my $result;
@@ -530,12 +1115,94 @@ 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);
- &Apache::lonxml::debug('Store return message:'.$result);
- if ($env{'request.role'} =~/^st/) {
- &store_aggregates($symb,$courseid);
+ $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);
}
} else {
&Apache::lonxml::debug('Nothing to store');
@@ -543,13 +1210,75 @@ sub finalize_storage {
return $result;
}
-# -------------------------------------------------------------store_aggregates
-# Sends hash of values to be incremented in nohist_resourcetracker.db
-# for the course. Increments total number of attempts, unique students
-# and corrects for each part for an instance of a problem, as appropriate.
+=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()
+
+ Sends hash of values to be incremented in nohist_resourcetracker.db
+ for the course. Increments total number of attempts, unique students
+ and corrects for each part for an instance of a problem, as appropriate.
+
+=cut
+
sub store_aggregates {
my ($symb,$courseid) = @_;
- my %aggregate;
+ my (%aggregate,%anoncounter,%randtrycounter);
my @parts;
my $cdomain = $env{'course.'.$env{'request.course.id'}.'.domain'};
my $cname = $env{'course.'.$env{'request.course.id'}.'.num'};
@@ -559,28 +1288,60 @@ sub store_aggregates {
}
}
foreach my $part (@parts) {
- if ($Apache::lonhomework::results{'resource.'.$part.'.award'}
- eq 'APPROX_ANS' ||
- $Apache::lonhomework::results{'resource.'.$part.'.award'}
- eq 'EXACT_ANS') {
- $aggregate{$symb."\0".$part."\0correct"} = 1;
+ if ($env{'request.role'} =~/^st/) {
+ if ($Apache::lonhomework::results{'resource.'.$part.'.award'}
+ eq 'APPROX_ANS' ||
+ $Apache::lonhomework::results{'resource.'.$part.'.award'}
+ eq 'EXACT_ANS') {
+ $aggregate{$symb."\0".$part."\0correct"} = 1;
+ }
+ if ($Apache::lonhomework::results{'resource.'.$part.'.tries'} == 1) {
+ $aggregate{$symb."\0".$part."\0users"} = 1;
+ } else {
+ my (undef,$last_reset) = &Apache::grades::get_last_resets($symb,$env{'request.course.id'},[$part]);
+ if ($last_reset) {
+ if (&Apache::grades::get_num_tries(\%Apache::lonhomework::history,$last_reset,$part) == 0) {
+ $aggregate{$symb."\0".$part."\0users"} = 1;
+ }
+ }
+ }
+ $aggregate{$symb."\0".$part."\0attempts"} = 1;
}
- if ($Apache::lonhomework::results{'resource.'.$part.'.tries'} == 1) {
- $aggregate{$symb."\0".$part."\0users"} = 1;
- } else {
- my (undef,$last_reset) = &Apache::grades::get_last_resets($symb,$env{'request.course.id'},[$part]);
- if ($last_reset) {
- if (&Apache::grades::get_num_tries(\%Apache::lonhomework::history,$last_reset,$part) == 0) {
- $aggregate{$symb."\0".$part."\0users"} = 1;
+ if (($Apache::lonhomework::results{'resource.'.$part.'.type'} eq 'anonsurvey') ||
+ ($Apache::lonhomework::results{'resource.'.$part.'.type'} eq 'anonsurveycred') ||
+ ($Apache::lonhomework::results{'resource.'.$part.'.type'} eq 'randomizetry')) {
+ if ($Apache::lonhomework::results{'resource.'.$part.'.type'} eq 'randomizetry') {
+ $randtrycounter{$symb."\0".$part} = 1;
+ } else {
+ $anoncounter{$symb."\0".$part} = 1;
+ }
+ my $needsrelease = $Apache::lonnet::needsrelease{'parameter:type:'.$Apache::lonhomework::results{'resource.'.$part.'.type'}};
+ if ($needsrelease) {
+ my $curr_required = $env{'course.'.$env{'request.course.id'}.'.internal.releaserequired'};
+ if ($curr_required eq '') {
+ &Apache::lonnet::update_released_required($needsrelease);
+ } else {
+ my ($currmajor,$currminor) = split(/\./,$curr_required);
+ my ($needsmajor,$needsminor) = split(/\./,$needsrelease);
+ if (($currmajor < $needsmajor) || ($currmajor == $needsmajor && $currminor < $needsminor)) {
+ &Apache::lonnet::update_released_required($needsrelease);
+ }
}
}
}
- $aggregate{$symb."\0".$part."\0attempts"} = 1;
}
- if (keys (%aggregate) > 0) {
+ if (keys(%aggregate) > 0) {
&Apache::lonnet::cinc('nohist_resourcetracker',\%aggregate,
$cdomain,$cname);
}
+ if (keys(%anoncounter) > 0) {
+ &Apache::lonnet::cput('nohist_anonsurveys',\%anoncounter,
+ $cdomain,$cname);
+ }
+ if (keys(%randtrycounter) > 0) {
+ &Apache::lonnet::cput('nohist_randomizetry',\%randtrycounter,
+ $cdomain,$cname);
+ }
}
sub checkout_msg {
@@ -594,29 +1355,49 @@ sub checkout_msg {
return (<$lt{'resource'}
$lt{'id_expln'}
-
-
$lt{'warning'}
-
-
ENDCHECKOUT
}
sub firstaccess_msg {
my ($time,$symb)=@_;
- my ($map)=&Apache::lonnet::decode_symb($symb);
- my $foldertitle=&Apache::lonnet::gettitle($map);
- &Apache::lonxml::debug("map is $map title is $foldertitle");
+ my $result;
+ my @interval=&Apache::lonnet::EXT("resource.0.interval");
+ if ($interval[1] eq 'map') {
+ my ($map)=&Apache::lonnet::decode_symb($symb);
+ my $foldertitle=&Apache::lonnet::gettitle($map);
+
+ &Apache::lonxml::debug("map is $map title is $foldertitle");
+ $result .= "
".&mt('The resources in "[_1]" are open for a limited time.',$foldertitle)."
"
+ .'
'.&mt('Once you click the "Show Resource" button below you have [_2] to complete all resources "[_1]".'
+ ,$foldertitle,$time)."