--- loncom/interface/lonnavmaps.pm 2025/05/27 23:31:49 1.575 +++ loncom/interface/lonnavmaps.pm 2025/06/28 17:34:00 1.577 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.575 2025/05/27 23:31:49 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.577 2025/06/28 17:34:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -425,6 +425,11 @@ Convenience function, so others can use Convenience function, so others can use it: Is there only one try remaining for the part, with more than one try to begin with, not due yet and still can be done? +=item graceEndsUnder24Hours() + +Convenience function, so others can use it: Is the problem past-due with a grace period +which ends in less than 24 hours, and still can be done? + =item advancedUser() This puts a human-readable name on the env variable. @@ -637,6 +642,7 @@ sub getDescription { my $open = $res->opendate($part); my $due = $res->duedate($part); + my $overdue = $res->overduedate($part); my $answer = $res->answerdate($part); if ($status == $res->NETWORK_FAILURE) { @@ -690,11 +696,20 @@ sub getDescription { } if ($status == $res->OPEN) { if ($due) { - if ($res->is_practice()) { - return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo; - } else { - return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo; - } + my $now = time; + if (($now >= $due) && ($overdue) && ($now < $overdue)) { + if ($res->is_practice()) { + return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($overdue,'start'),$res->symb(),'duedate',$part)).$slotinfo; + } else { + return &mt("Grace period ends [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($overdue,'end'),$res->symb(),'grace',$part)).$slotinfo; + } + } else { + if ($res->is_practice()) { + return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part)).$slotinfo; + } else { + return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)).$slotinfo; + } + } } else { return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo; } @@ -749,8 +764,14 @@ sub getDescription { } } if ($due) { - return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) . - " $triesString"; + my $now = time; + if (($now >= $due) && ($overdue) && ($now < $overdue)) { + return &mt("Grace period ends [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($overdue,'end'),$res->symb(),'grace',$part)). + " $triesString"; + } else { + return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) . + " $triesString"; + } } else { return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString"; } @@ -784,6 +805,19 @@ sub lastTry { $res->duedate($part) > time(); } +sub graceEndsUnder24Hours { + my $res = shift; + my $part = shift; + my $status = $res->status($part); + my $now = time(); + + return ($status == $res->OPEN() || + $status == $res->TRIES_LEFT()) && + $res->duedate($part) && $res->duedate($part) < $now && + $res->overduedate($part) && $res->overduedate($part) < time()+(24*60*60) && + $res->duedate($part) && $res->duedate($part) < $now+(24*60*60) && + $res->overduedate($part) > $now; +} sub advancedUser { return $env{'request.role.adv'}; @@ -1254,6 +1288,9 @@ sub render_long_status { if (dueInLessThan24Hours($resource, $part)) { $color = $hurryUpColor; $info = ' title="'.&mt('Due in less than 24 hours!').'"'; + } elsif (graceEndsUnder24Hours($resource, $part)) { + $color = $hurryUpColor; + $info = ' title="'.&mt('Grace period ends in less than 24 hours!').'"'; } elsif (lastTry($resource, $part)) { unless (($resource->problemstatus($part) eq 'no') || ($resource->problemstatus($part) eq 'no_feedback_ever')) { @@ -5177,6 +5214,12 @@ sub awarded { if (!defined($part)) { $part = '0'; } return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'}; } +sub latefrac { + my $self = shift; my $part = shift; + $self->{NAV_MAP}->get_user_data(); + if (!defined($part)) { $part = '0'; } + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.latefrac'}; +} sub taskversion { my $self = shift; my $part = shift; $self->{NAV_MAP}->get_user_data(); @@ -5266,6 +5309,71 @@ sub opendate { } return $opendate; } +sub overduedate { + my ($self,$part) = @_; + my $duedate = $self->parmval("duedate", $part); + my $overduedate; + if ($duedate) { + my $grace = $self->parmval("grace", $part); + if ($grace) { + my $grace_end = (split(/,/,$grace))[-1]; + my ($offset) = split(/:/,$grace_end,2); + if ($offset > 0) { + $overduedate = $offset + $duedate; + } + } + } + return $overduedate; +} +sub partial_credit_overdue { + my ($self,$part) = @_; + my $reduction; + my $duedate = $self->parmval("duedate", $part); + if ($duedate) { + my $grace = $self->parmval("grace",$part); + if ($grace) { + my $lateness = time - $duedate; + if ($lateness > 0) { + my ($start,$end,$startfrac,$endfrac,$usegrad); + $start = 0; + $startfrac = 1.0; + $usegrad = 0; + foreach my $item (split(/,/,$grace)) { + my ($offset,$frac,$grad) = split(/:/,$item); + if ($lateness > $offset) { + $start = $offset; + $startfrac = $frac; + next; + } elsif ($lateness <= $offset) { + $end = $offset; + $endfrac = $frac; + $usegrad = $grad; + last; + } + } + if ($end) { + if (($end == $start) || ($startfrac == $endfrac)) { + $reduction = $endfrac; + } elsif ($end - $start > 0) { + if (($endfrac <= 1.0) && ($endfrac >= 0)) { + $reduction = $endfrac; + if ($usegrad) { + my $decline = $startfrac - $endfrac; + my $fraction = ($lateness - $start)/($end - $start); + if (($fraction <= 1) && ($fraction >= 0)) { + my $value = $startfrac - ($decline*$fraction); + $reduction = sprintf("%.2f", $value); + } + } + } + } + } + } + } + } + return $reduction; +} + sub problemstatus { (my $self, my $part) = @_; my $problemstatus = $self->parmval("problemstatus", $part); @@ -5844,6 +5952,7 @@ sub getDateStatus { my $open = $self->opendate($part); my $due = $self->duedate($part); + my $overdue = $self->overduedate($part); my $answer = $self->answerdate($part); if (!$open && !$due && !$answer) { @@ -5853,6 +5962,7 @@ sub getDateStatus { } if (!$open || $now < $open) {return $self->OPEN_LATER} if (!$due || $now < $due) {return $self->OPEN} + if ($overdue && $now < $overdue) {return $self->OPEN} if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER} if ($answer) { return $self->ANSWER_OPEN; } return PAST_DUE_NO_ANSWER; @@ -6121,6 +6231,7 @@ sub status { # If there's an answer date and we're past it, don't # suppress the feedback; student should know if ($self->duedate($part) && $self->duedate($part) < time() && + (!$self->overduedate($part) || $self->overduedate($part) < time()) && $self->answerdate($part) && $self->answerdate($part) < time()) { $suppressFeedback = 0; }