--- loncom/interface/lonnavmaps.pm 2022/06/11 03:58:58 1.556 +++ loncom/interface/lonnavmaps.pm 2025/02/26 19:50:21 1.572 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.556 2022/06/11 03:58:58 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.572 2025/02/26 19:50:21 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,7 +48,8 @@ described at http://www.lon-capa.org. =head1 OVERVIEW -X When a user enters a course, LON-CAPA examines the +X +When a user enters a course, LON-CAPA examines the course structure and caches it in what is often referred to as the "big hash" X. You can see it if you are logged into LON-CAPA, in a course, by going to /adm/test. The content of @@ -549,6 +550,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 => '', @@ -694,10 +699,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 { @@ -1020,8 +1025,12 @@ sub render_resource { if ($it->{CONDITION}) { $nowOpen = !$nowOpen; } - - my $folderType = $resource->is_sequence() ? 'folder' : 'page'; + my $folderType; + if (&advancedUser() && $resource->is_missing_map()) { + $folderType = 'none'; + } else { + $folderType = $resource->is_sequence() ? 'folder' : 'page'; + } my $title=$resource->title; $title=~s/\"/\&qout;/g; if (!$params->{'resource_no_folder_link'}) { @@ -1112,7 +1121,7 @@ sub render_resource { } # Decide what to display - $result .= "$newBranchText$linkopen$icon$linkclose"; + $result .= "$newBranchText$linkopen$icon"; my $curMarkerBegin = ''; my $curMarkerEnd = ''; @@ -1152,7 +1161,7 @@ sub render_resource { $linkopen = ""; } } - $result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText"; + $result .= "$curMarkerBegin$title$partLabel$curMarkerEnd$linkclose$editmapLink$nonLinkedText"; return $result; } @@ -1423,9 +1432,40 @@ sub render { my $condition = 0; if ($env{'form.condition'}) { $condition = 1; + } elsif (($env{'request.deeplink.login'}) && ($env{'request.course.id'}) && (!$userCanSeeHidden)) { + if (!defined($navmap)) { + $navmap = Apache::lonnavmaps::navmap->new(); + } + if (defined($navmap)) { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $symb = &Apache::loncommon::symb_from_tinyurl($env{'request.deeplink.login'},$cnum,$cdom); + if ($symb) { + my $deeplink; + my $res = $navmap->getBySymb($symb); + if ($res->is_map()) { + my $mapname = &Apache::lonnet::declutter($res->src()); + $mapname = &Apache::lonnet::deversion($mapname); + $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink"); + } else { + $deeplink = $res->deeplink(); + } + if ($deeplink ne '') { + if ((split(/,/,$deeplink))[1] eq 'hide') { + if ($res->is_map()) { + map { $filterHash->{$_} = 1 if $_ } split(/,/,$res->map_hierarchy()); + } else { + my $mapurl = (&Apache::lonnet::decode_symb($symb))[0]; + my $map = $navmap->getResourceByUrl($mapurl); + map { $filterHash->{$_} = 1 if $_ } split(/,/,$map->map_hierarchy()); + } + } + } + } + } } - if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) { + if (!$env{'form.folderManip'} && !defined($args->{'iterator'}) && !$args->{'nocurrloc'}) { # Step 1: Check to see if we have a navmap if (!defined($navmap)) { $navmap = Apache::lonnavmaps::navmap->new(); @@ -1533,10 +1573,15 @@ sub render { my $curRes; my $foundJump = 0; my $counter = 0; - + while (($curRes = $mapIterator->next()) && !$foundJump) { if (ref($curRes)) { $counter++; } - + # Speed up display after course initialization + # when $jump is empty. Note: we still need + # $counter to be 1 in that case if there is at + # least one resource. + last if (($jump eq '') && ($counter)); + if (ref($curRes) && $jump eq $curRes->symb()) { # This is why we have to use the main iterator instead of the @@ -1555,27 +1600,19 @@ sub render { my $printKey = $args->{'printKey'}; my $printCloseAll = $args->{'printCloseAll'}; if (!defined($printCloseAll)) { $printCloseAll = 1; } - + # Print key? if ($printKey) { - $result .= ''; - $result.=''; - my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc"); - if ($navmap->{LAST_CHECK}) { - $result .= - ' '.&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)').'

'. - '

  '. - ' '.&mt('Discussions').''. - '   '.&mt('New message (click to open)'). - '
'; + my $location = &Apache::loncommon::lonhttpdurl("/adm/lonMisc"); + $result .= '
'."\n". + ''.&mt('Key').':'. + '  '. + ' '.&mt('Unread Discussion'). + '  '. + ' '. + &mt('New message (click to open)'). + '
'. + '
'."\n"; } if ($printCloseAll && !$args->{'resource_no_folder_link'}) { @@ -1589,9 +1626,6 @@ sub render { '&here='.&escape($here); $text='Open all folders'; } - if ($env{'form.register'}) { - $link .= '&register='.$env{'form.register'}; - } if ($args->{'caller'} eq 'navmapsdisplay') { unless ($args->{'notools'}) { &add_linkitem($args->{'linkitems'},'changefolder', @@ -1605,20 +1639,19 @@ sub render { # Check for any unread discussions in all resources. if (($args->{'caller'} eq 'navmapsdisplay') && (!$args->{'notools'})) { + my $markread = 'Mark all posts read'; &add_linkitem($args->{'linkitems'},'clearbubbles', 'document.clearbubbles.submit()', - 'Mark all posts read'); + $markread); my $time=time; + my $submit = &mt($markread); my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"'); $result .= (< +
'. - &mt('Content Editor').''; + $result .= ''; } } if ($args->{'sort_html'}) { - $result .= '   '. - ''.$args->{'sort_html'}.''; + $result .= '
   '. + $args->{'sort_html'}.'
'; } } if ($result) { - $result = "$result
"; + $result = '
'.$result.'
'."\n". + '
'."\n"; } return $result; } @@ -2226,10 +2298,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. @@ -2271,7 +2350,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; @@ -2282,15 +2361,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; @@ -2797,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) { @@ -2986,7 +3067,7 @@ sub parmval_real { } sub recurseup_maps { - my ($self,$mapname) = @_; + my ($self,$mapname,$getsymb) = @_; my @recurseup; if ($mapname) { my $res = $self->getResourceByUrl($mapname); @@ -2994,7 +3075,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); + } } } } @@ -3026,8 +3111,12 @@ sub recursed_crumbs { my $pc = $map->map_pc(); next if ((!$pc) || ($pc == 1)); push(@links,$map); - push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,}); - $totallength += length($map->title()); + my $text = $map->title(); + if ($text eq '') { + $text = '...'; + } + push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $text,'no_mt' => 1,}); + $totallength += length($text); } my $numlinks = scalar(@links); if ($numlinks) { @@ -3039,7 +3128,11 @@ sub recursed_crumbs { } @revmapinfo = (); foreach my $map (@links) { - my $showntitle = &truncate_crumb_text($map->title(),$avg); + my $title = $map->title(); + if ($title eq '') { + $title = '...'; + } + my $showntitle = &truncate_crumb_text($title,$avg); if ($showntitle ne '') { push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,}); } @@ -3114,7 +3207,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) { @@ -3315,11 +3408,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'}); @@ -4745,6 +4838,11 @@ sub is_sequence { return $self->navHash("is_map_", 1) && $self->navHash("map_type_" . $self->map_pc()) eq 'sequence'; } +sub is_missing_map { + my $self=shift; + return $self->navHash("is_map_", 1) && + $self->navHash("map_type_" . $self->map_pc()) eq 'none'; +} sub is_survey { my $self = shift(); my $part = shift(); @@ -5172,7 +5270,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 { @@ -5646,7 +5744,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. @@ -5659,6 +5756,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. @@ -5674,6 +5791,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. @@ -5877,6 +5998,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. @@ -5960,7 +6101,18 @@ sub status { if ($completionStatus == CORRECT || $completionStatus == CORRECT_BY_OVERRIDE || $completionStatus == CORRECT_BY_PASSBACK ) { - if ( $suppressFeedback ) { return ANSWER_SUBMITTED } + 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; @@ -6000,7 +6152,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) { @@ -6246,6 +6414,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,