--- loncom/interface/lonnavmaps.pm 2015/04/13 16:46:43 1.506 +++ loncom/interface/lonnavmaps.pm 2018/09/04 19:48:58 1.509.2.7 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.506 2015/04/13 16:46:43 raeburn Exp $ +# $Id: lonnavmaps.pm,v 1.509.2.7 2018/09/04 19:48:58 raeburn Exp $ # # Copyright Michigan State University Board of Trustees @@ -624,44 +624,55 @@ sub getDescription { if ($status == $res->OPEN_LATER) { return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part)); } + my $slotinfo; if ($res->simpleStatus($part) == $res->OPEN) { unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) { my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part); + my $slotmsg; if ($slot_status == $res->UNKNOWN) { - return &mt('Reservation status unknown'); + $slotmsg = &mt('Reservation status unknown'); } elsif ($slot_status == $res->RESERVED) { - return &mt('Reserved - ends [_1]', + $slotmsg = &mt('Reserved - ends [_1]', timeToHumanString($slot_time,'end')); } elsif ($slot_status == $res->RESERVED_LOCATION) { - return &mt('Reserved - specific location(s) - ends [_1]', + $slotmsg = &mt('Reserved - specific location(s) - ends [_1]', timeToHumanString($slot_time,'end')); } elsif ($slot_status == $res->RESERVED_LATER) { - return &mt('Reserved - next open [_1]', + $slotmsg = &mt('Reserved - next open [_1]', timeToHumanString($slot_time,'start')); } elsif ($slot_status == $res->RESERVABLE) { - return &mt('Reservable, reservations close [_1]', + $slotmsg = &mt('Reservable, reservations close [_1]', + timeToHumanString($slot_time,'end')); + } elsif ($slot_status == $res->NEEDS_CHECKIN) { + $slotmsg = &mt('Reserved, check-in needed - ends [_1]', timeToHumanString($slot_time,'end')); } elsif ($slot_status == $res->RESERVABLE_LATER) { - return &mt('Reservable, reservations open [_1]', + $slotmsg = &mt('Reservable, reservations open [_1]', timeToHumanString($slot_time,'start')); } elsif ($slot_status == $res->NOT_IN_A_SLOT) { - return &mt('Reserve a time/place to work'); + $slotmsg = &mt('Reserve a time/place to work'); } elsif ($slot_status == $res->NOTRESERVABLE) { - return &mt('Reservation not available'); + $slotmsg = &mt('Reservation not available'); } elsif ($slot_status == $res->WAITING_FOR_GRADE) { - return &mt('Submission in grading queue'); + $slotmsg = &mt('Submission in grading queue'); + } + if ($slotmsg) { + if ($res->is_task() || !$due) { + return $slotmsg; + } + $slotinfo = (' ' x 2).'('.$slotmsg.')'; } } } 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)); + 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)); + 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); + return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part).$slotinfo; } } if ($status == $res->PAST_DUE_ANSWER_LATER) { @@ -907,6 +918,9 @@ sub render_resource { my $nonLinkedText = ''; # stuff after resource title not in link my $link = $params->{"resourceLink"}; + if ($resource->ext()) { + $link =~ s/\#.+(\?)/$1/g; + } # The URL part is not escaped at this point, but the symb is... @@ -927,7 +941,28 @@ sub render_resource { # links to open and close the folder my $whitespace = $location.'/whitespace_21.gif'; - my $linkopen = "".""; + my $linkopen = ""; + my $nomodal; + if (($params->{'modalLink'}) && (!$resource->is_sequence())) { + if ($link =~m{^(?:|/adm/wrapper)/ext/([^#]+)}) { + my $exturl = $1; + if (($ENV{'SERVER_PORT'} == 443) && ($exturl !~ /^https:/)) { + $nomodal = 1; + } + } elsif (($link eq "/public/$LONCAPA::match_domain/$LONCAPA::match_courseid/syllabus") && + ($env{'request.course.id'}) && ($ENV{'SERVER_PORT'} == 443) && + ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { + $nomodal = 1; + } + my $esclink = &js_escape($link); + if ($nomodal) { + $linkopen .= ""; + } else { + $linkopen .= ""; + } + } else { + $linkopen .= ""; + } my $linkclose = ""; # Default icon: unknown page @@ -985,7 +1020,8 @@ sub render_resource { $linkopen = ""; $linkclose = ""; } - if ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) && + if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) || + (&Apache::lonnet::allowed('cev',$env{'request.course.id'}))) && ($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) { if (!$params->{'map_no_edit_link'}) { my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png'; @@ -995,10 +1031,13 @@ sub render_resource { ''; } } - } - - if ($resource->randomout()) { - $nonLinkedText .= ' ('.&mt('hidden').') '; + if ($params->{'mapHidden'} || $resource->randomout()) { + $nonLinkedText .= ' ('.&mt('hidden').') '; + } + } else { + if ($resource->randomout()) { + $nonLinkedText .= ' ('.&mt('hidden').') '; + } } if (!$resource->condval()) { $nonLinkedText .= ' ('.&mt('conditionally hidden').') '; @@ -1048,10 +1087,19 @@ sub render_resource { } if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) { - $result .= "$curMarkerBegin$title$partLabel$curMarkerEnd$editmapLink$nonLinkedText"; - } else { - $result .= "$curMarkerBegin$linkopen$title$partLabel$curMarkerEnd$editmapLink$nonLinkedText"; + $linkclose = ''; + if ($params->{'modalLink'}) { + my $esclink = &js_escape($link); + if ($nomodal) { + $linkopen = ""; + } else { + $linkopen = ""; + } + } else { + $linkopen = ""; + } } + $result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText"; return $result; } @@ -1527,7 +1575,8 @@ END $result.=''; } if (($args->{'caller'} eq 'navmapsdisplay') && - (&Apache::lonnet::allowed('mdc',$env{'request.course.id'}))) { + ((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) || + (&Apache::lonnet::allowed('cev',$env{'request.course.id'})))) { my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; if ($env{'course.'.$env{'request.course.id'}.'.url'} eq @@ -1573,41 +1622,46 @@ END $args->{'indentString'} = setDefault($args->{'indentString'}, ""); $args->{'displayedHereMarker'} = 0; - # If we're suppressing empty sequences, look for them here. Use DFS for speed, - # since structure actually doesn't matter, except what map has what resources. - if ($args->{'suppressEmptySequences'}) { - my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap, - $it->{FIRST_RESOURCE}, - $it->{FINISH_RESOURCE}, - {}, undef, 1); - my $depth = 0; - $dfsit->next(); - my $curRes = $dfsit->next(); - while ($depth > -1) { - if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; } - if ($curRes == $dfsit->END_MAP()) { $depth--; } - - if (ref($curRes)) { - # Parallel pre-processing: Do sequences have non-filtered-out children? - if ($curRes->is_map()) { - $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; - # Sequences themselves do not count as visible children, - # unless those sequences also have visible children. - # This means if a sequence appears, there's a "promise" - # that there's something under it if you open it, somewhere. - } else { - # Not a sequence: if it's filtered, ignore it, otherwise - # rise up the stack and mark the sequences as having children - if (&$filterFunc($curRes)) { - for my $sequence (@{$dfsit->getStack()}) { - $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1; - } + # If we're suppressing empty sequences, look for them here. + # We also do this even if $args->{'suppressEmptySequences'} + # is not true, so we can hide empty sequences for which the + # hiddenresource parameter is set to yes (at map level), or + # mark as hidden for users who have $userCanSeeHidden. + # Use DFS for speed, since structure actually doesn't matter, + # except what map has what resources. + + my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap, + $it->{FIRST_RESOURCE}, + $it->{FINISH_RESOURCE}, + {}, undef, 1); + + my $depth = 0; + $dfsit->next(); + my $curRes = $dfsit->next(); + while ($depth > -1) { + if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; } + if ($curRes == $dfsit->END_MAP()) { $depth--; } + + if (ref($curRes)) { + # Parallel pre-processing: Do sequences have non-filtered-out children? + if ($curRes->is_map()) { + $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; + # Sequences themselves do not count as visible children, + # unless those sequences also have visible children. + # This means if a sequence appears, there's a "promise" + # that there's something under it if you open it, somewhere. + } 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)) { + for my $sequence (@{$dfsit->getStack()}) { + $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1; } } } - } continue { - $curRes = $dfsit->next(); } + } continue { + $curRes = $dfsit->next(); } my $displayedJumpMarker = 0; @@ -1666,6 +1720,11 @@ END } + my $inhibitmenu; + if ($args->{'modalLink'}) { + $inhibitmenu = '&inhibitmenu=yes'; + } + while (1) { if ($args->{'sort'}) { $curRes = shift(@resources); @@ -1700,9 +1759,21 @@ END } # If this is an empty sequence and we're filtering them, continue on - if ($curRes->is_map() && $args->{'suppressEmptySequences'} && - !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) { - next; + $args->{'mapHidden'} = 0; + if (($curRes->is_map()) && (!$curRes->{DATA}->{HAS_VISIBLE_CHILDREN})) { + if ($args->{'suppressEmptySequences'}) { + next; + } else { + my $mapname = &Apache::lonnet::declutter($curRes->src()); + $mapname = &Apache::lonnet::deversion($mapname); + if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') { + if ($userCanSeeHidden) { + $args->{'mapHidden'} = 1; + } else { + next; + } + } + } } # If we're suppressing navmaps and this is a navmap, continue on @@ -1790,7 +1861,7 @@ END my $srcHasQuestion = $src =~ /\?/; $args->{"resourceLink"} = $src. ($srcHasQuestion?'&':'?') . - 'symb=' . &escape($symb).$anchor; + 'symb=' . &escape($symb).$inhibitmenu.$anchor; } # Now, we've decided what parts to show. Loop through them and # show them. @@ -2701,6 +2772,23 @@ sub parmval_real { if (defined($pack_def)) { return [$pack_def,'resource']; } return ['']; } + +sub recurseup_maps { + my ($self,$mapname) = @_; + my @recurseup; + if ($mapname) { + my $res = $self->getResourceByUrl($mapname); + if (ref($res)) { + my @pcs = split(/,/,$res->map_hierarchy()); + shift(@pcs); + if (@pcs) { + @recurseup = map { &Apache::lonnet::declutter($self->getByMapPc($_)->src()); } reverse(@pcs); + } + } + } + return @recurseup; +} + # # Determines the open/close dates for printing a map that # encloses a resource. @@ -2712,15 +2800,15 @@ sub map_printdates { - my $opendate = $self->get_mapparam($res->symb(), "$part.printstartdate"); - my $closedate= $self->get_mapparam($res->symb(), "$part.printenddate"); + my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate"); + my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate"); return ($opendate, $closedate); } sub get_mapparam { - my ($self, $symb, $what) = @_; + my ($self, $symb, $mapname, $what) = @_; # Ensure the course option hash is populated: @@ -2739,15 +2827,17 @@ sub get_mapparam { my $uname=$self->{USERNAME}; my $udom=$self->{DOMAIN}; - unless ($symb) { return ['']; } + unless ($symb || $mapname) { return; } my $result=''; - + my ($recursed,@recurseup); # Figure out which map we are in. - my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); - $mapname = &Apache::lonnet::deversion($mapname); - + if ($symb && !$mapname) { + my ($id,$fn); + ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); + $mapname = &Apache::lonnet::deversion($mapname); + } my $rwhat=$what; $what=~s/^parameter\_//; @@ -2779,6 +2869,20 @@ sub get_mapparam { if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; } + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + unless ($recursed) { + @recurseup = $self->recurseup_maps($mapname); + $recursed = 1; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; + if (defined($$useropt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$useropt{$norecursechk}; + } + } + } + } } # Check course -- group @@ -2789,6 +2893,20 @@ sub get_mapparam { if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; } + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + unless ($recursed) { + @recurseup = $self->recurseup_maps($mapname); + $recursed = 1; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } + } + } + } } # Check course -- section @@ -2801,12 +2919,29 @@ sub get_mapparam { if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; } + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + unless ($recursed) { + @recurseup = $self->recurseup_maps($mapname); + $recursed = 1; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } + } + } + } } # Check the map parameters themselves: - my $thisparm = $$parmhash{$symbparm}; - if (defined($thisparm)) { - return $thisparm; + if ($symb) { + my $symbparm=$symb.'.'.$what; + my $thisparm = $$parmhash{$symbparm}; + if (defined($thisparm)) { + return $thisparm; + } } @@ -2816,6 +2951,20 @@ sub get_mapparam { if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; } + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + unless ($recursed) { + @recurseup = $self->recurseup_maps($mapname); + $recursed = 1; + } + foreach my $item (@recurseup) { + my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; + if (defined($$courseopt{$norecursechk})) { + if ($what =~ /\.(encrypturl|hiddenresource)$/) { + return $$courseopt{$norecursechk}; + } + } + } + } } return undef; # Unefined if we got here. } @@ -2871,7 +3020,7 @@ sub getcourseparam { # # We want the course level stuff from the way # parmval_real operates - # TODO: Fator some of this stuff out of + # TODO: Factor some of this stuff out of # both parmval_real and here # my $courselevel = $cid . '.' . $what; @@ -2888,7 +3037,7 @@ sub getcourseparam { } # Try for the group's course level option: - if ($uname ne '' and defined($courseopt)) { + if ($cgroup ne '' and defined($courseopt)) { if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; } @@ -2896,12 +3045,12 @@ sub getcourseparam { # Try for section level parameters: - if ($csec and defined($courseopt)) { + if ($csec ne '' and defined($courseopt)) { if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; } } - # Try for 'additional' course parameterse: + # Try for 'additional' course parameters: if (defined($courseopt)) { if (defined($$courseopt{$courselevel})) { @@ -3860,9 +4009,12 @@ sub new { # about this resource in. Not used by the resource object # directly. $self->{DATA} = {}; - + bless($self); + # This is a speed optimization, to avoid calling symb() too often. + $self->{SYMB} = $self->symb(); + return $self; } @@ -3974,8 +4126,8 @@ sub src { } sub shown_symb { my $self=shift; - if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());} - return $self->symb(); + if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});} + return $self->{SYMB}; } sub id { my $self=shift; @@ -3988,6 +4140,7 @@ sub enclosing_map_src { } sub symb { my $self=shift; + if (defined($self->{SYMB})) { return $self->{SYMB}; } (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/; my $symbSrc = &Apache::lonnet::declutter($self->src()); my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) @@ -3996,7 +4149,7 @@ sub symb { } sub wrap_symb { my $self = shift; - return $self->{NAV_MAP}->wrap_symb($self->symb()); + return $self->{NAV_MAP}->wrap_symb($self->{SYMB}); } sub title { my $self=shift; @@ -4219,7 +4372,7 @@ sub parmval { if (!defined($part)) { $part = '0'; } - return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb()); + return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB}); } =pod @@ -4472,25 +4625,25 @@ sub awarded { 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.'.awarded'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.awarded'}; } sub taskversion { 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.'.version'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.version'}; } sub taskstatus { 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.'.$self->taskversion($part).'.'.$part.'.status'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'}; } sub solved { 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.'.solved'}; + return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.solved'}; } sub checkedin { my $self = shift; my $part = shift; @@ -4498,9 +4651,9 @@ sub checkedin { if (!defined($part)) { $part = '0'; } if ($self->is_task()) { my $version = $self->taskversion($part); - return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$version .'.'.$part.'.checkedin.slot'}); } else { - return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.checkedin.slot'}); + return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}->{'resource.'.$part.'.checkedin.slot'}); } } # this should work exactly like the copy in lonhomework.pm @@ -4519,7 +4672,7 @@ sub duedate { my $due_date=$self->parmval("duedate", $part); if ($interval[0] =~ /\d+/) { my $first_access=&Apache::lonnet::get_first_access($interval[1], - $self->symb); + $self->{SYMB}); if (defined($first_access)) { my $interval = $first_access+$interval[0]; $date = (!$due_date || $interval < $due_date) ? $interval @@ -4592,7 +4745,7 @@ sub weight { my $self = shift; my $part = shift; if (!defined($part)) { $part = '0'; } my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight', - $self->symb(), $self->{DOMAIN}, + $self->{SYMB}, $self->{DOMAIN}, $self->{USERNAME}, $env{'request.course.sec'}); return $weight; @@ -4601,7 +4754,7 @@ sub part_display { my $self= shift(); my $partID = shift(); if (! defined($partID)) { $partID = '0'; } my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display', - $self->symb); + $self->{SYMB}); if (! defined($display) || $display eq '') { $display = $partID; } @@ -4621,7 +4774,7 @@ sub getReturnHash { my $self = shift; if (!defined($self->{RETURN_HASH})) { - my %tmpHash = &Apache::lonnet::restore($self->symb(),undef,$self->{DOMAIN},$self->{USERNAME}); + my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME}); $self->{RETURN_HASH} = \%tmpHash; } } @@ -4686,23 +4839,23 @@ and use the link as appropriate. sub hasDiscussion { my $self = shift; - return $self->{NAV_MAP}->hasDiscussion($self->symb()); + return $self->{NAV_MAP}->hasDiscussion($self->{SYMB}); } sub last_post_time { my $self = shift; - return $self->{NAV_MAP}->last_post_time($self->symb()); + return $self->{NAV_MAP}->last_post_time($self->{SYMB}); } sub discussion_info { my ($self,$filter) = @_; - return $self->{NAV_MAP}->discussion_info($self->symb(),$filter); + return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter); } sub getFeedback { my $self = shift; my $source = $self->src(); - my $symb = $self->symb(); + my $symb = $self->{SYMB}; if ($source =~ /^\/res\//) { $source = substr $source, 5; } return $self->{NAV_MAP}->getFeedback($symb,$source); } @@ -4710,7 +4863,7 @@ sub getFeedback { sub getErrors { my $self = shift; my $source = $self->src(); - my $symb = $self->symb(); + my $symb = $self->{SYMB}; if ($source =~ /^\/res\//) { $source = substr $source, 5; } return $self->{NAV_MAP}->getErrors($symb,$source); } @@ -4860,7 +5013,7 @@ sub extractParts { if ($partorder) { my @parts; for my $part (split (/,/,$partorder)) { - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { push @parts, $part; $parts{$part} = 1; } @@ -4878,12 +5031,12 @@ sub extractParts { my $part = $1; # This floods the logs if it blows up if (defined($parts{$part})) { - &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb()); + &Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->{SYMB}); } # check to see if part is turned off. - if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) { + if (!Apache::loncommon::check_if_partid_hidden($part, $self->{SYMB})) { $parts{$part} = 1; } } @@ -5388,7 +5541,7 @@ sub status { sub check_for_slot { my $self = shift; my $part = shift; - my $symb = $self->symb(); + my $symb = $self->{SYMB}; my ($use_slots,$available,$availablestudent) = $self->slot_control($part); if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) { my @slots = (split(/:/,$availablestudent),split(/:/,$available)); @@ -5397,13 +5550,13 @@ sub check_for_slot { my $cnum=$env{'course.'.$cid.'.num'}; my $now = time; my $num_usable_slots = 0; + my ($checkedin,$checkedinslot,%consumed_uniq,%slots); if (@slots > 0) { - my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum); + %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum); if (&Apache::lonnet::error(%slots)) { return (UNKNOWN); } my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime'); - my ($checkedin,$checkedinslot); foreach my $slot_name (@sorted_slots) { next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name})); my $end = $slots{$slot_name}->{'endtime'}; @@ -5427,7 +5580,7 @@ sub check_for_slot { ($checkedin,$checkedinslot) = $self->checkedin(); unless ((grep(/^\Q$checkedin\E/,@proctors)) && ($checkedinslot eq $slot_name)) { - return (NEEDS_CHECKIN,undef,$slot_name); + return (NEEDS_CHECKIN,$end,$slot_name); } } return (RESERVED,$end,$slot_name); @@ -5437,19 +5590,30 @@ sub check_for_slot { $num_usable_slots ++; } } - my ($is_correct,$got_grade); + my ($is_correct,$wait_for_grade); if ($self->is_task()) { my $taskstatus = $self->taskstatus(); $is_correct = (($taskstatus eq 'pass') || ($self->solved() =~ /^correct_/)); - $got_grade = ($taskstatus =~ /^(?:pass|fail)$/); + unless ($taskstatus =~ /^(?:pass|fail)$/) { + $wait_for_grade = 1; + } } else { - $got_grade = 1; - $is_correct = ($self->solved() =~ /^correct_/); + unless ($self->completable()) { + $wait_for_grade = 1; + } + unless (($self->problemstatus($part) eq 'no') || + ($self->problemstatus($part) eq 'no_feedback_ever')) { + $is_correct = ($self->solved($part) =~ /^correct_/); + $wait_for_grade = 0; + } } ($checkedin,$checkedinslot) = $self->checkedin(); if ($checkedin) { - if (!$got_grade) { + if (ref($slots{$checkedinslot}) eq 'HASH') { + $consumed_uniq{$checkedinslot} = $slots{$checkedinslot}{'uniqueperiod'}; + } + if ($wait_for_grade) { return (WAITING_FOR_GRADE); } elsif ($is_correct) { return (CORRECT); @@ -5464,16 +5628,46 @@ sub check_for_slot { if (ref($reservable) eq 'HASH') { if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) { foreach my $slot (reverse (@{$reservable->{'now_order'}})) { + my $canuse; if (($reservable->{'now'}{$slot}{'symb'} eq '') || ($reservable->{'now'}{$slot}{'symb'} eq $symb)) { + $canuse = 1; + } + if ($canuse) { + if ($checkedin) { + if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') { + my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}}; + if ($reservable->{'now'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { + my ($new_uniq_start,$new_uniq_end) = ($1,$2); + next if (! + ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || + ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); + } + } + } return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'}); } } } if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) { foreach my $slot (@{$reservable->{'future_order'}}) { + my $canuse; if (($reservable->{'future'}{$slot}{'symb'} eq '') || ($reservable->{'future'}{$slot}{'symb'} eq $symb)) { + $canuse = 1; + } + if ($canuse) { + if ($checkedin) { + if (ref($consumed_uniq{$checkedinslot}) eq 'ARRAY') { + my ($uniqstart,$uniqend)=@{$consumed_uniq{$checkedinslot}}; + if ($reservable->{'future'}{$slot}{'uniqueperiod'} =~ /^(\d+),(\d+)$/) { + my ($new_uniq_start,$new_uniq_end) = ($1,$2); + next if (! + ($uniqstart < $new_uniq_start && $uniqend < $new_uniq_start) || + ($uniqstart > $new_uniq_end && $uniqend > $new_uniq_end )); + } + } + } return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'}); } } @@ -5624,6 +5818,39 @@ sub completable { =pod +B + +The answerable method differs from the completable method in its handling of problem parts +for which feedback on correctness is suppressed, but the student still has tries left, and +the problem part is not past due, (i.e., the student could submit a different answer if +he/she so chose). For that case completable will return 0, whereas answerable will return 1. + +=cut + +sub answerable { + my $self = shift; + if (!$self->is_problem()) { return 0; } + my $partCount = $self->countParts(); + foreach my $part (@{$self->parts()}) { + if ($part eq '0' && $partCount != 1) { next; } + my $status = $self->status($part); + if ($self->getCompletionStatus($part) == ATTEMPTED() || + $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() || + $status == ANSWER_SUBMITTED() ) { + if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) { + return 1; + } + } + if ($status == OPEN() || $status == TRIES_LEFT() || $status == NETWORK_FAILURE()) { + return 1; + } + } + # None of the parts were answerable, so neither is this problem. + return 0; +} + +=pod + =head2 Resource/Nav Map Navigation =over 4 @@ -5677,7 +5904,7 @@ sub browsePriv { } $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(), - $self->symb(),undef, + $self->{SYMB},undef, undef,$noblockcheck); }