--- loncom/interface/lonnavmaps.pm 2023/01/20 22:45:51 1.509.2.14.2.6 +++ loncom/interface/lonnavmaps.pm 2025/05/28 13:42:14 1.509.2.14.2.12 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.509.2.14.2.6 2023/01/20 22:45:51 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.509.2.14.2.12 2025/05/28 13:42:14 raeburn Exp $ # # Copyright Michigan State University Board of Trustees @@ -534,6 +534,10 @@ my %colormap = $resObj->EXCUSED => '#3333FF', $resObj->PAST_DUE_ANSWER_LATER => '', $resObj->PAST_DUE_NO_ANSWER => '', + $resObj->PAST_DUE_ATMPT_ANS => '', + $resObj->PAST_DUE_ATMPT_NOANS => '', + $resObj->PAST_DUE_NO_ATMT_ANS => '', + $resObj->PAST_DUE_NO_ATMT_NOANS => '', $resObj->ANSWER_OPEN => '#006600', $resObj->OPEN_LATER => '', $resObj->TRIES_LEFT => '', @@ -679,10 +683,10 @@ sub getDescription { return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo; } } - if ($status == $res->PAST_DUE_ANSWER_LATER) { + if (($status == $res->PAST_DUE_ANSWER_LATER) || ($status == $res->PAST_DUE_ATMPT_ANS) || ($status == $res->PAST_DUE_NO_ATMT_ANS)) { return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start'),$res->symb(),'answerdate',$part)); } - if ($status == $res->PAST_DUE_NO_ANSWER) { + if (($status == $res->PAST_DUE_NO_ANSWER) || ($status == $res->PAST_DUE_ATMPT_NOANS) || ($status == $res->PAST_DUE_NO_ATMT_NOANS)) { if ($res->is_practice()) { return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'answerdate,duedate',$part)); } else { @@ -691,7 +695,17 @@ sub getDescription { } if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT) && $res->handgrade($part) ne 'yes') { - return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part); + my $msg = &mt('Answer available'); + my $parmlist = 'answerdate,duedate'; + if (($res->is_tool) && ($res->is_gradable())) { + if (($status == $res->PARTIALLY_CORRECT) && ($res->parmval('retrypartial',$part))) { + $msg = &mt('Grade received'); + $parmlist = 'retrypartial'; + } else { + $msg = &mt('Grade available'); + } + } + return &Apache::lonhtmlcommon::direct_parm_link($msg,$res->symb(),$parmlist,$part); } if ($status == $res->EXCUSED) { return &mt("Excused by instructor"); @@ -1195,7 +1209,7 @@ sub render_quick_status { my $linkclose = ""; $result .= ''; - if ($resource->is_problem() && + if ($resource->is_gradable() && !$firstDisplayed) { my $icon = $statusIconMap{$resource->simpleStatus($part)}; my $alt = $iconAltTags{$icon}; @@ -1220,7 +1234,7 @@ sub render_long_status { my $color; my $info = ''; - if ($resource->is_problem() || $resource->is_practice()) { + if ($resource->is_gradable() || $resource->is_practice()) { $color = $colormap{$resource->status}; if (dueInLessThan24Hours($resource, $part)) { @@ -1235,8 +1249,8 @@ sub render_long_status { } } - if ($resource->kind() eq "res" && - $resource->is_raw_problem() && + if (($resource->kind() eq "res") && + ($resource->is_raw_problem() || $resource->is_gradable()) && !$firstDisplayed) { if ($color) {$result .= ''; } $result .= getDescription($resource, $part); @@ -1283,7 +1297,7 @@ my @statuses = ($resObj->CORRECT, $resOb sub render_parts_summary_status { my ($resource, $part, $params) = @_; - if (!$resource->is_problem() && !$resource->contains_problem) { return ''; } + if (!$resource->is_gradable() && !$resource->contains_problem) { return ''; } if ($params->{showParts}) { return ''; } @@ -1352,13 +1366,17 @@ sub cmp_title { sub render { my $args = shift; &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING}); - my $result = ''; # Configure the renderer. my $cols = $args->{'cols'}; if (!defined($cols)) { # no columns, no nav maps. return ''; } + my $legend = ''; + my $tools = ''; + my $result = ''; + my $tools_printed = 0; + my $tablestarted = 0; my $navmap; if (defined($args->{'navmap'})) { $navmap = $args->{'navmap'}; @@ -1570,24 +1588,24 @@ sub render { # Print key? if ($printKey) { - $result .= ''; - $result.=''; + $legend = '
Key:  
'; + $legend .= ''; my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); if ($navmap->{LAST_CHECK}) { - $result .= + $legend .= ' '.&mt('New discussion since').' '. strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})). ''; } else { - $result .= ''; } - $result .= '
Key:    '. ' '.&mt('New message (click to open)').'

'. '

  '. + $legend .= '  '. ' '.&mt('Discussions').''. '   '.&mt('New message (click to open)'). '
'; + $legend .= ''; } if ($printCloseAll && !$args->{'resource_no_folder_link'}) { @@ -1607,9 +1625,9 @@ sub render { "location.href='$link'",$text); } } else { - $result.= ''.&mt($text).''; + $tools = ''.&mt($text).''; } - $result .= "\n"; + $tools .= "\n"; } # Check for any unread discussions in all resources. @@ -1619,7 +1637,7 @@ sub render { 'Mark all posts read'); my $time=time; my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"'); - $result .= (< @@ -1636,13 +1654,13 @@ END } if ($totdisc > 0) { $haveDisc =~ s/:$//; - $result .= (< END } } - $result.=''; + $tools .= ''; } if (($args->{'caller'} eq 'navmapsdisplay') && ($env{'request.course.id'})) { my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; @@ -1664,23 +1682,15 @@ END } if ($args->{'caller'} eq 'navmapsdisplay') { - $result .= &show_linkitems_toolbar($args,$condition); + $tools .= &show_linkitems_toolbar($args,$condition); } elsif ($args->{'sort_html'}) { - $result.=$args->{'sort_html'}; + $tools .= $args->{'sort_html'}; } - #$result .= "
\n"; - if ($r) { - $r->print($result); - $r->rflush(); - $result = ""; - } + #$tools .= "
\n"; # End parameter setting - - $result .= "
\n"; # Data - $result.=&Apache::loncommon::start_data_table("LC_tableOfContent"); my $res = "Apache::lonnavmaps::resource"; my %condenseStatuses = @@ -1692,7 +1702,8 @@ END $args->{'counter'} = 0; # counts the rows $args->{'indentLevel'} = 0; $args->{'isNewBranch'} = 0; - $args->{'condensed'} = 0; + $args->{'condensed'} = 0; + $args->{'deeplinknolist'} = 0; my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif"); $args->{'indentString'} = setDefault($args->{'indentString'}, ""); @@ -1705,12 +1716,32 @@ END # mark as hidden for users who have $userCanSeeHidden. # Use DFS for speed, since structure actually doesn't matter, # except what map has what resources. + # + # To ensure the "Selected Resources from selected folder in course" + # printout generation option will work in sessions launched via a + # deep link, the value of $args->{'filterFunc'} included in the + # call to lonnavmaps::render() is omitted from the filter function + # used with the DFS Iterator when $args->{'caller'} is 'printout'. + # + # As a result $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} can be + # set to 1 for folder(s) which include resources only accessible + # for sessions launched via a deep link, when the current session + # is of that type. my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap, $it->{FIRST_RESOURCE}, $it->{FINISH_RESOURCE}, {}, undef, 1); + my $dfsFilterFunc; + if ($args->{'caller'} eq 'printout') { + $dfsFilterFunc = sub { my $res = shift; return !$res->randomout() && + ($res->deeplink($args->{'caller'}) ne 'absent') && + ($res->deeplink($args->{'caller'}) ne 'grades') && + !$res->deeplinkout();}; + } else { + $dfsFilterFunc = $filterFunc; + } my $depth = 0; $dfsit->next(); my $curRes = $dfsit->next(); @@ -1729,8 +1760,9 @@ END } elsif ($curRes->src()) { # Not a sequence: if it's filtered, ignore it, otherwise # rise up the stack and mark the sequences as having children - if (&$filterFunc($curRes)) { + if (&$dfsFilterFunc($curRes)) { for my $sequence (@{$dfsit->getStack()}) { + next unless ($sequence->is_map()); $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1; } } @@ -1847,6 +1879,9 @@ END # If this has been filtered out, continue on if (!(&$filterFunc($curRes))) { + if (!$userCanSeeHidden && !$curRes->randomout && $curRes->deeplinkout) { + $args->{'deeplinknolist'} ++; + } $args->{'isNewBranch'} = 0; # Don't falsely remember this next; } @@ -1871,6 +1906,7 @@ END if ($userCanSeeHidden) { $args->{'mapHiddenDeepLink'} = 1; } else { + $args->{'deeplinknolist'} ++; next; } } else { @@ -1880,6 +1916,7 @@ END if ($userCanSeeHidden) { $args->{'mapUnlisted'} = 1; } else { + $args->{'deeplinknolist'} ++; next; } } @@ -1893,6 +1930,10 @@ END } $args->{'counter'}++; + unless ($tablestarted) { + $result .= "
\n".&Apache::loncommon::start_data_table("LC_tableOfContent"); + $tablestarted = 1; + } # Does it have multiple parts? $args->{'multipart'} = 0; @@ -2043,6 +2084,12 @@ END } if ($r && $rownum % 20 == 0) { + unless ($tools_printed) { + $r->print($legend.$tools); + $legend = ""; + $tools = ""; + $tools_printed = 1; + } $r->print($result); $result = ""; $r->rflush(); @@ -2058,8 +2105,10 @@ END } } - $result.=&Apache::loncommon::end_data_table(); - + if ($tablestarted) { + $result.=&Apache::loncommon::end_data_table(); + } + # Print out the part that jumps to #curloc if it exists # delay needed because the browser is processing the jump before # it finishes rendering, so it goes to the wrong place! @@ -2076,12 +2125,23 @@ if (location.href.indexOf('#curloc')==-1 } if ($r) { + unless ($tools_printed) { + if (($args->{'counter'}) || ($userCanSeeHidden) || + (($args->{'caller'} eq 'navmapsdisplay') && + ($env{'form.showOnlyHomework'} || + $ENV{QUERY_STRING} =~ /^jumpToFirstHomework/))) { + $r->print($legend.$tools); + } + $legend = ""; + $tools = ""; + $tools_printed = 1; + } $r->print($result); $result = ""; $r->rflush(); } - return $result; + return $legend.$tools.$result; } sub add_linkitem { @@ -2239,10 +2299,17 @@ sub new { $self->{USERNAME} = shift || $env{'user.name'}; $self->{DOMAIN} = shift || $env{'user.domain'}; + $self->{SECTION} = shift; $self->{CODE} = shift; - $self->{NOHIDE} = shift; + $self->{NOHIDE} = shift; + if (($self->{SECTION} eq '') && ($env{'request.course.sec'} ne '')) { + if (($self->{USERNAME} eq $env{'user.name'}) && + ($self->{USERNAME} eq $env{'user.domain'})) { + $self->{SECTION} = $env{'request.course.sec'}; + } + } # Resource cache stores navmap resources as we reference them. We generate # them on-demand so we don't pay for creating resources unless we use them. @@ -2284,7 +2351,7 @@ sub new { $self->{PARM_HASH} = \%parmhash; $self->{PARM_CACHE} = {}; } else { - $self->change_user($self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}); + $self->change_user($self->{USERNAME}, $self->{DOMAIN}, $self->{SECTION}, $self->{CODE}, $self->{NOHIDE}); } return $self; @@ -2295,15 +2362,17 @@ sub new { # username/domain associated with a navmap (e.g. to navigate for someone # else besides the current user...if sufficiently privileged. # Parameters: -# user - New user. -# domain- Domain the user belongs to. -# code - Anonymous CODE in use. +# user - New user. +# domain - Domain to which the user belongs. +# section - Section to which the user belongs. +# code - Anonymous CODE in use. # Implicit inputs: # sub change_user { my $self = shift; $self->{USERNAME} = shift; $self->{DOMAIN} = shift; + $self->{SECTION} = shift; $self->{CODE} = shift; $self->{NOHIDE} = shift; @@ -2809,7 +2878,7 @@ sub parmval_real { $self->generate_course_user_opt(); my $cid=$env{'request.course.id'}; - my $csec=$env{'request.course.sec'}; + my $csec=$self->{SECTION}; my $cgroup=''; my @cgrps=split(/:/,$env{'request.course.groups'}); if (@cgrps > 0) { @@ -2824,6 +2893,10 @@ sub parmval_real { my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); $mapname = &Apache::lonnet::deversion($mapname); + my $toolsymb = ''; + if ($fn =~ /ext\.tool$/) { + $toolsymb = $symb; + } # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; $what=~s/^parameter\_//; @@ -2887,9 +2960,9 @@ sub parmval_real { my $meta_rwhat=$rwhat; $meta_rwhat=~s/\./_/g; - my $default=&Apache::lonnet::metadata($fn,$meta_rwhat); + my $default=&Apache::lonnet::metadata($fn,$meta_rwhat,$toolsymb); if (defined($default)) { return [$default,'resource']} - $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat); + $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat,$toolsymb); if (defined($default)) { return [$default,'resource']} # --------------------------------------------------- fifth, check more course if (defined($courseopt)) { @@ -2912,13 +2985,13 @@ sub parmval_real { if (defined($partgeneral[0])) { return \@partgeneral; } } if ($recurse) { return []; } - my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat); + my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$rwhat,$toolsymb); if (defined($pack_def)) { return [$pack_def,'resource']; } return ['']; } sub recurseup_maps { - my ($self,$mapname) = @_; + my ($self,$mapname,$getsymb) = @_; my @recurseup; if ($mapname) { my $res = $self->getResourceByUrl($mapname); @@ -2926,7 +2999,11 @@ sub recurseup_maps { my @pcs = split(/,/,$res->map_hierarchy()); shift(@pcs); if (@pcs) { - @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs); + if ($getsymb) { + @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->symb()); } reverse(@pcs); + } else { + @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs); + } } } } @@ -3054,7 +3131,7 @@ sub get_mapparam { # Get the course id and section if there is one. my $cid=$env{'request.course.id'}; - my $csec=$env{'request.course.sec'}; + my $csec=$self->{SECTION}; my $cgroup=''; my @cgrps=split(/:/,$env{'request.course.groups'}); if (@cgrps > 0) { @@ -3223,11 +3300,11 @@ sub getcourseparam { my $uname = $self->{USERNAME}; my $udom = $self->{DOMAIN}; + my $csec = $self->{SECTION}; - # Course, section, group ids come from the env: + # Course and group ids come from the env: my $cid = $env{'request.course.id'}; - my $csec = $env{'request.course.sec'}; my $cgroup = ''; # Assume no group my @cgroups = split(/:/, $env{'request.course.groups'}); @@ -3464,6 +3541,71 @@ sub usedVersion { return $self->navhash("version_$linkurl"); } +sub isFirstResource { + my $self = shift; + my $map = shift; + my $symb = shift; + return unless (ref($map)); + my $isfirst; + my $firstResource = $map->map_start(); + if (ref($firstResource)) { + if ((!$firstResource->is_map()) && ($firstResource->src() ne '')) { + if ($firstResource->symb() eq $symb) { + $isfirst = 1; + } else { + $isfirst = 0; + } + } else { + my $it = $self->getIterator($firstResource,undef,undef,1); + while ( my $res=$it->next()) { + if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) { + if ($res->symb() eq $symb) { + $isfirst = 1; + } else { + $isfirst = 0; + } + last; + } + } + } + } + return $isfirst; +} + +sub isLastResource { + my $self = shift; + my $map = shift; + my $symb = shift; + return unless (ref($map)); + my $islast; + my $lastResource = $map->map_finish(); + if (ref($lastResource)) { + if ((!$lastResource->is_map()) && ($lastResource->src() ne '')) { + if ($lastResource->symb() eq $symb) { + $islast = 1; + } else { + $islast = 0; + } + } else { + my $currRes = $self->getBySymb($symb); + if (ref($currRes)) { + my $it = $self->getIterator($currRes,undef,undef,1); + while ( my $res=$it->next()) { + if ((ref($res)) && ($res->src() ne '') && (!$res->is_map())) { + if ($res->symb() eq $symb) { + $islast = 1; + } else { + $islast = 0; + } + last; + } + } + } + } + } + return $islast; +} + 1; package Apache::lonnavmaps::iterator; @@ -4521,6 +4663,19 @@ sub is_problem { } return 0; } +sub is_tool { + my $self=shift; + my $src = $self->src(); + return ($src =~ /ext\.tool$/); +} +sub is_gradable { + my $self=shift; + my $src = $self->src(); + if (($src =~ /$LONCAPA::assess_re/) || + (($self->is_tool()) && ($self->parmval('gradable',0) =~ /^yes$/i))) { + return !($self->is_practice()); + } +} # # The has below is the set of status that are considered 'incomplete' # @@ -5011,7 +5166,7 @@ sub weight { my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', $self->{SYMB}, $self->{DOMAIN}, $self->{USERNAME}, - $env{'request.course.sec'}); + $self->{SECTION}); return $weight; } sub part_display { @@ -5220,6 +5375,8 @@ sub parts { my $self = shift; if ($self->ext) { return []; } + if (($self->is_tool()) && + ($self->is_gradable())) { return ['0']; } $self->extractParts(); return $self->{PARTS}; @@ -5310,7 +5467,7 @@ sub extractParts { my %parts; # Retrieve part count, if this is a problem - if ($self->is_problem()) { + if ($self->is_raw_problem()) { my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder'); my $metadata = &Apache::lonnet::metadata($self->src(), 'packages'); @@ -5477,7 +5634,6 @@ The problem will be opened later. Open and not yet due. - =item * B: The due date has passed, but the answer date has not yet arrived. @@ -5490,6 +5646,26 @@ The due date has passed and there is no The answer date is here. +=item * B: + +No dates have been set for this problem at all. + +=item * B: + +The due date has passed, feedback is suppressed, the problem was attempted, and the answer date has not yet arrived. + +=item * B: + +The due date has passed, feedback is suppressed, the problem was attempted, and there is no answer opening date set. + +=item * B: + +The due date has passed, feedback is suppressed, the problem was not attempted, and the answer date has not yet arrived. + +=item * B: + +The due date has passed, feedback is suppressed, the problem was not attempted, and there is no answer opening date set. + =item * B: The information is unknown due to network failure. @@ -5505,6 +5681,10 @@ sub PAST_DUE_NO_ANSWER { return 2; } sub PAST_DUE_ANSWER_LATER { return 3; } sub ANSWER_OPEN { return 4; } sub NOTHING_SET { return 5; } +sub PAST_DUE_ATMPT_ANS { return 6; } +sub PAST_DUE_ATMPT_NOANS { return 7; } +sub PAST_DUE_NO_ATMT_ANS { return 8; } +sub PAST_DUE_NO_ATMT_NOANS { return 9; } sub NETWORK_FAILURE { return 100; } # getDateStatus gets the date status for a given problem part. @@ -5594,6 +5774,14 @@ Attempted, and not yet graded. Attempted, and credit received for attempt (survey and anonymous survey only). +=item * B: + +Attempted, but wrong for LTI Tool Provider by passback of grade + +=item * B: + +Correct for LTI Tool Provider by passback of grade + =back =cut @@ -5606,6 +5794,8 @@ sub CORRECT_BY_OVERRIDE { return 14; } sub EXCUSED { return 15; } sub ATTEMPTED { return 16; } sub CREDIT_ATTEMPTED { return 17; } +sub INCORRECT_BY_PASSBACK { return 18; } +sub CORRECT_BY_PASSBACK { return 19; } sub getCompletionStatus { my $self = shift; @@ -5620,8 +5810,12 @@ sub getCompletionStatus { if ($status eq 'correct_by_override') { return $self->CORRECT_BY_OVERRIDE; } + if ($status eq 'correct_by_passback') { + return $self->CORRECT_BY_PASSBACK; + } if ($status eq 'incorrect_attempted') {return $self->INCORRECT; } if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; } + if ($status eq 'incorrect_by_passback') {return $self->INCORRECT_BY_PASSBACK; } if ($status eq 'excused') {return $self->EXCUSED; } if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; } if ($status eq 'credit_attempted') { @@ -5694,6 +5888,26 @@ set. The problem is past due, not considered correct, and an answer date in the future is set. +=item * B: + +The problem is past due, feedback is suppressed, the problem was +attempted and an answer date in the future is set. + +=item * B: + +The problem is past due, feedback is suppressed, the problem was +attempted and no answer date is set. + +=item * B: + +The problem is past due, feedback is suppressed, the problem was +not attempted and an answer date in the future is set. + +=item * B: + +The problem is past due, feedback is suppressed, the problem was +not attempted and no answer date is set. + =item * B: The problem is past due, not correct, and the answer is now available. @@ -5775,8 +5989,20 @@ sub status { # There are a few whole rows we can dispose of: if ($completionStatus == CORRECT || - $completionStatus == CORRECT_BY_OVERRIDE ) { - if ( $suppressFeedback ) { return ANSWER_SUBMITTED } + $completionStatus == CORRECT_BY_OVERRIDE || + $completionStatus == CORRECT_BY_PASSBACK ) { + if ( $suppressFeedback ) { + if ($dateStatus == PAST_DUE_ANSWER_LATER || + $dateStatus == PAST_DUE_NO_ANSWER ) { + if ($dateStatus == PAST_DUE_ANSWER_LATER) { + return PAST_DUE_ATMPT_ANS; + } else { + return PAST_DUE_ATMPT_NOANS; + } + } else { + return ANSWER_SUBMITTED; + } + } my $awarded=$self->awarded($part); if ($awarded < 1 && $awarded > 0) { return PARTIALLY_CORRECT; @@ -5788,7 +6014,8 @@ sub status { # If it's WRONG... and not open if ( ($completionStatus == INCORRECT || - $completionStatus == INCORRECT_BY_OVERRIDE) + $completionStatus == INCORRECT_BY_OVERRIDE || + $completionStatus == INCORRECT_BY_PASSBACK) && (!$self->opendate($part) || $self->opendate($part) > time()) ) { return INCORRECT; } @@ -5815,7 +6042,23 @@ sub status { if ($dateStatus == PAST_DUE_ANSWER_LATER || $dateStatus == PAST_DUE_NO_ANSWER ) { - return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; + if ($suppressFeedback) { + if ($completionStatus == NOT_ATTEMPTED) { + if ($dateStatus == PAST_DUE_ANSWER_LATER) { + return PAST_DUE_NO_ATMT_ANS; + } else { + return PAST_DUE_NO_ATMT_NOANS; + } + } else { + if ($dateStatus == PAST_DUE_ANSWER_LATER) { + return PAST_DUE_ATMPT_ANS; + } else { + return PAST_DUE_ATMPT_NOANS; + } + } + } else { + return $dateStatus; + } } if ($dateStatus == ANSWER_OPEN) { @@ -5830,7 +6073,8 @@ sub status { } # If it's WRONG... - if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) { + if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE || + $completionStatus == INCORRECT_BY_PASSBACK) { # and there are TRIES LEFT: if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) { return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT; @@ -6025,6 +6269,10 @@ my %compositeToSimple = EXCUSED() => CORRECT, PAST_DUE_NO_ANSWER() => INCORRECT, PAST_DUE_ANSWER_LATER() => INCORRECT, + PAST_DUE_ATMPT_ANS() => ATTEMPTED, + PAST_DUE_ATMPT_NOANS() => ATTEMPTED, + PAST_DUE_NO_ATMT_ANS() => CLOSED, + PAST_DUE_NO_ATMT_NOANS() => CLOSED, ANSWER_OPEN() => INCORRECT, OPEN_LATER() => CLOSED, TRIES_LEFT() => OPEN,