--- loncom/interface/lonnavmaps.pm 2022/10/06 16:32:04 1.509.2.14.2.4 +++ loncom/interface/lonnavmaps.pm 2021/08/04 19:59:10 1.554 @@ -1,8 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.509.2.14.2.4 2022/10/06 16:32:04 raeburn Exp $ - +# $Id: lonnavmaps.pm,v 1.554 2021/08/04 19:59:10 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -52,9 +51,16 @@ described at http://www.lon-capa.org. 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. (You may need to -tweak the /home/httpd/lonTabs/htpasswd file to view it.) The -content of the hash will be under the heading "Big Hash". +LON-CAPA, in a course, by going to /adm/test. The content of +the hash will be under the heading "Big Hash". + +Access to /adm/test is controlled by a domain configuration, +which a Domain Coordinator will set for a server's default domain +via: Main Menu > Set domain configuration > Display (Access to +server status pages checked), and entering a username:domain +or IP address in the "Show user environment" row. Users with +an unexpired domain coordinator role in the server's domain +automatically receive access to /adm/test. Big Hash contains, among other things, how resources are related to each other (next/previous), what resources are maps, which @@ -65,7 +71,7 @@ processed. Apache::lonnavmaps provides an object model for manipulating this information in a higher-level fashion than directly manipulating -the hash. It also provides access to several auxilary functions +the hash. It also provides access to several auxiliary functions that aren't necessarily stored in the Big Hash, but are a per- resource sort of value, like whether there is any feedback on a given resource. @@ -78,11 +84,18 @@ Apache::lonnavmaps also provides fairly rendering navmaps, and last but not least, provides the navmaps view for when the user clicks the NAV button. -B: Apache::lonnavmaps I works for the "currently -logged in user"; if you want things like "due dates for another -student" lonnavmaps can not directly retrieve information like -that. You need the EXT function. This module can still help, -because many things, such as the course structure, are constant +B: Apache::lonnavmaps by default will show information +for the "currently logged in user". However, if information +about resources is needed for a different user, e.g., a bubblesheet +exam which uses randomorder, or randompick needs to be printed or +graded for named user(s) or specific CODEs, then the username, +domain, or CODE can be passed as arguments when creating a new +navmap object. + +Note if you want things like "due dates for another student", +you would use the EXT function instead of lonnavmaps. +That said, the lonnavmaps module can still help, because many +things, such as the course structure, are usually constant between users, and Apache::lonnavmaps can help by providing symbs for the EXT call. @@ -92,7 +105,9 @@ all, then documents the Apache::lonnavma is the key to accessing the Big Hash information, covers the use of the Iterator (which provides the logic for traversing the somewhat-complicated Big Hash data structure), documents the -Apache::lonnavmaps::Resource objects that are returned by +Apache::lonnavmaps::Resource objects that are returned singularly +by: getBySymb(), getById(), getByMapPc(), and getResourceByUrl() +(can also be as an array), or in an array by retrieveResources(). =head1 Subroutine: render @@ -691,7 +706,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"); @@ -942,8 +967,6 @@ sub render_resource { $newBranchText = ".mt('Branch')."; } - # links to open and close the folder - my $whitespace = $location.'/whitespace_21.gif'; my ($nomodal,$linkopen,$linkclose); unless ($resource->is_map() || $params->{'resource_nolink'}) { @@ -997,12 +1020,8 @@ sub render_resource { if ($it->{CONDITION}) { $nowOpen = !$nowOpen; } - my $folderType; - if (&advancedUser() && $resource->is_missing_map()) { - $folderType = 'none'; - } else { - $folderType = $resource->is_sequence() ? 'folder' : 'page'; - } + + my $folderType = $resource->is_sequence() ? 'folder' : 'page'; my $title=$resource->title; $title=~s/\"/\&qout;/g; if (!$params->{'resource_no_folder_link'}) { @@ -1035,7 +1054,7 @@ sub render_resource { } } if (((&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) || - (&Apache::lonnet::allowed('cev',$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'; @@ -1195,7 +1214,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 +1239,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)) { @@ -1234,9 +1253,9 @@ 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 +1302,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 ''; } @@ -1394,7 +1413,7 @@ sub render { # Without renaming the filterfunc, the server seems to go into # an infinite loop my $oldFilterFunc = $filterFunc; - $filterFunc = sub { my $res = shift; return !$res->randomout() && + $filterFunc = sub { my $res = shift; return !$res->randomout() && ($res->deeplink($args->{'caller'}) ne 'absent') && ($res->deeplink($args->{'caller'}) ne 'grades') && !$res->deeplinkout() && @@ -1404,37 +1423,6 @@ 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'})) { @@ -1458,11 +1446,11 @@ sub render { my $currenturl = $env{'form.postdata'}; #$currenturl=~s/^http\:\/\///; #$currenturl=~s/^[^\/]+//; - unless ($args->{'caller'} eq 'sequence') { + unless ($args->{'caller'} eq 'sequence') { $here = $jump = &Apache::lonnet::symbread($currenturl); } } - if (($here eq '') && ($args->{'caller'} ne 'sequence')) { + if (($here eq '') && ($args->{'caller'} ne 'sequence')) { my $last; if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', &GDBM_READER(),0640)) { @@ -1698,7 +1686,7 @@ END # 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. + # mark as hidden for users who have $userCanSeeHidden. # Use DFS for speed, since structure actually doesn't matter, # except what map has what resources. @@ -1706,7 +1694,6 @@ END $it->{FIRST_RESOURCE}, $it->{FINISH_RESOURCE}, {}, undef, 1); - my $depth = 0; $dfsit->next(); my $curRes = $dfsit->next(); @@ -1714,7 +1701,7 @@ END if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; } if ($curRes == $dfsit->END_MAP()) { $depth--; } - if (ref($curRes)) { + if (ref($curRes)) { # Parallel pre-processing: Do sequences have non-filtered-out children? if ($curRes->is_map()) { $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0; @@ -1856,7 +1843,7 @@ END next; } else { my $mapname = &Apache::lonnet::declutter($curRes->src()); - $mapname = &Apache::lonnet::deversion($mapname); + $mapname = &Apache::lonnet::deversion($mapname); if (lc($navmap->get_mapparam(undef,$mapname,"0.hiddenresource")) eq 'yes') { if ($userCanSeeHidden) { $args->{'mapHidden'} = 1; @@ -1951,7 +1938,7 @@ END } else { $args->{'resource_nolink'} = 1; } - + # If the multipart problem was condensed, "forget" it was multipart if (scalar(@parts) == 1) { $args->{'multipart'} = 0; @@ -1995,13 +1982,13 @@ END } } if (defined($anchor)) { $anchor='#'.$anchor; } - if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) { - $args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1'; - } else { + if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) { + $args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1'; + } else { $args->{"resourceLink"} = $src. ($srcHasQuestion?'&':'?') . 'symb=' . &escape($symb).$inhibitmenu.$anchor; - } + } } # Now, we've decided what parts to show. Loop through them and # show them. @@ -2322,7 +2309,7 @@ sub change_user { - # Now clear the parm cache and reconstruct the parm hash fromt he big_hash + # Now clear the parm cache and reconstruct the parm hash from the big_hash # param.xxxx keys. $self->{PARM_CACHE} = {}; @@ -2787,6 +2774,7 @@ sub parmval { return $self->{PARM_CACHE}->{$hashkey}; } } + my $result = $self->parmval_real($what, $symb, $recurse); $self->{PARM_CACHE}->{$hashkey} = $result; if (wantarray) { @@ -2820,29 +2808,39 @@ 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; + } + my ($recursed,@recurseup); + # ----------------------------------------------------- Cascading lookup scheme my $rwhat=$what; $what=~s/^parameter\_//; $what=~s/\_/\./; my $symbparm=$symb.'.'.$what; + my $recurseparm=$mapname.'___(rec).'.$what; my $mapparm=$mapname.'___(all).'.$what; my $usercourseprefix=$cid; - + my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what; my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm; + my $grpleveli=$usercourseprefix.'.['.$cgroup.'].'.$recurseparm; my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm; my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what; my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm; + my $secleveli=$usercourseprefix.'.['.$csec.'].'.$recurseparm; my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm; my $courselevel= $usercourseprefix.'.'.$what; my $courselevelr=$usercourseprefix.'.'.$symbparm; + my $courseleveli=$usercourseprefix.'.'.$recurseparm; my $courselevelm=$usercourseprefix.'.'.$mapparm; @@ -2854,6 +2852,23 @@ sub parmval_real { if ($uname and defined($useropt)) { if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; } if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; } + if (defined($$useropt{$courseleveli})) { return [$$useropt{$courseleveli},'map']; } + 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},'map']; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$useropt{$recursechk})) { return [$$useropt{$recursechk},'map']; } + } if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; } } @@ -2861,12 +2876,46 @@ sub parmval_real { if ($cgroup ne '' and defined($courseopt)) { if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; } if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; } + if (defined($$courseopt{$grpleveli})) { return [$$courseopt{$grpleveli},'map']; } + 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},'map']; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { return [$$courseopt{$recursechk},'map']; } + } if (defined($$courseopt{$grplevel})) { return [$$courseopt{$grplevel},'course']; } } - if ($csec and defined($courseopt)) { + if ($csec ne '' and defined($courseopt)) { if (defined($$courseopt{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; } if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; } + if (defined($$courseopt{$secleveli})) { return [$$courseopt{$secleveli},'map']; } + 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},'map']; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { return [$$courseopt{$recursechk},'map']; } + } if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; } } @@ -2883,13 +2932,32 @@ 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)) { if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; } + if (defined($$courseopt{$courseleveli})) { return [$$courseopt{$courseleveli},'map']; } + 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},'map']; + } else { + last; + } + } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return [$$courseopt{$recursechk},'map']; + } + } if (defined($$courseopt{$courselevel})) { my $ret = [$$courseopt{$courselevel},'course']; return $ret; @@ -2908,7 +2976,7 @@ 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 ['']; } @@ -2954,12 +3022,8 @@ sub recursed_crumbs { my $pc = $map->map_pc(); next if ((!$pc) || ($pc == 1)); push(@links,$map); - 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); + push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,}); + $totallength += length($map->title()); } my $numlinks = scalar(@links); if ($numlinks) { @@ -2971,11 +3035,7 @@ sub recursed_crumbs { } @revmapinfo = (); foreach my $map (@links) { - my $title = $map->title(); - if ($title eq '') { - $title = '...'; - } - my $showntitle = &truncate_crumb_text($title,$avg); + my $showntitle = &truncate_crumb_text($map->title(),$avg); if ($showntitle ne '') { push(@revmapinfo,{'href' => $env{'request.use_absolute'}.$map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,}); } @@ -3034,7 +3094,7 @@ sub map_printdates { my $opendate = $self->get_mapparam($res->symb(),'',"$part.printstartdate"); - my $closedate= $self->get_mapparam($res->symb(),'', "$part.printenddate"); + my $closedate= $self->get_mapparam($res->symb(),'',"$part.printenddate"); return ($opendate, $closedate); @@ -3064,6 +3124,7 @@ sub get_mapparam { my $result=''; my ($recursed,@recurseup); + # Figure out which map we are in. if ($symb && !$mapname) { @@ -3072,21 +3133,25 @@ sub get_mapparam { $mapname = &Apache::lonnet::deversion($mapname); } + my $rwhat=$what; $what=~s/^parameter\_//; $what=~s/\_/\./; # Build the hash keys for the lookup: - my $symbparm=$symb.'.'.$what; my $mapparm=$mapname.'___(all).'.$what; + my $recurseparm=$mapname.'___(rec).'.$what; my $usercourseprefix=$cid; - my $grplevel = "$usercourseprefix.[$cgroup].$mapparm"; - my $seclevel = "$usercourseprefix.[$csec].$mapparm"; - my $courselevel = "$usercourseprefix.$mapparm"; - + my $grplevelm = "$usercourseprefix.[$cgroup].$mapparm"; + my $seclevelm = "$usercourseprefix.[$csec].$mapparm"; + my $courselevelm = "$usercourseprefix.$mapparm"; + + my $grpleveli = "$usercourseprefix.[$cgroup].$recurseparm"; + my $secleveli = "$usercourseprefix.[$csec].$recurseparm"; + my $courseleveli = "$usercourseprefix.$recurseparm"; # Get handy references to the hashes we need in $self: @@ -3099,22 +3164,29 @@ sub get_mapparam { if ($uname and defined($useropt)) { - if (defined($$useropt{$courselevel})) { - return $$useropt{$courselevel}; + if (defined($$useropt{$courselevelm})) { + return $$useropt{$courselevelm}; } - 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}; - } + if (defined($$useropt{$courseleveli})) { + return $$useropt{$courseleveli}; + } + 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}; + } else { + last; } } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$useropt{$recursechk})) { + return $$useropt{$recursechk}; + } } } @@ -3123,48 +3195,59 @@ sub get_mapparam { if ($cgroup ne '' and defined ($courseopt)) { - if (defined($$courseopt{$grplevel})) { - return $$courseopt{$grplevel}; + if (defined($$courseopt{$grplevelm})) { + return $$courseopt{$grplevelm}; } - 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}; - } + if (defined($$courseopt{$grpleveli})) { + return $$courseopt{$grpleveli}; + } + 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}; + } else { + last; } } + my $recursechk=$usercourseprefix.'.['.$cgroup.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } } } # Check course -- section - - - - if ($csec and defined($courseopt)) { - if (defined($$courseopt{$seclevel})) { - return $$courseopt{$seclevel}; + if ($csec ne '' and defined($courseopt)) { + if (defined($$courseopt{$seclevelm})) { + return $$courseopt{$seclevelm}; } - 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}; - } + if (defined($$courseopt{$secleveli})) { + return $$courseopt{$secleveli}; + } + 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}; + } else { + last; } } + my $recursechk=$usercourseprefix.'.['.$csec.'].'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } } } # Check the map parameters themselves: @@ -3173,7 +3256,7 @@ sub get_mapparam { my $symbparm=$symb.'.'.$what; my $thisparm = $$parmhash{$symbparm}; if (defined($thisparm)) { - return $thisparm; + return $thisparm; } } @@ -3181,25 +3264,34 @@ sub get_mapparam { # Additional course parameters: if (defined($courseopt)) { - if (defined($$courseopt{$courselevel})) { - return $$courseopt{$courselevel}; + if (defined($$courseopt{$courselevelm})) { + return $$courseopt{$courselevelm}; } - if ($what =~ /\.(encrypturl|hiddenresource)$/) { - unless ($recursed) { - @recurseup = $self->recurseup_maps($mapname); - $recursed = 1; - } + if (defined($$courseopt{$courseleveli})) { + return $$courseopt{$courseleveli}; + } + unless ($recursed) { + @recurseup = $self->recurseup_maps($mapname); + $recursed = 1; + } + if (@recurseup) { foreach my $item (@recurseup) { my $norecursechk=$usercourseprefix.'.'.$item.'___(all).'.$what; if (defined($$courseopt{$norecursechk})) { if ($what =~ /\.(encrypturl|hiddenresource)$/) { return $$courseopt{$norecursechk}; + } else { + last; } } + my $recursechk=$usercourseprefix.'.'.$item.'___(rec).'.$what; + if (defined($$courseopt{$recursechk})) { + return $$courseopt{$recursechk}; + } } } } - return undef; # Unefined if we got here. + return undef; # Undefined if we got here. } sub course_printdates { @@ -3241,10 +3333,6 @@ sub getcourseparam { $what=~s/^parameter\_//; $what=~s/\_/\./; - - my $symbparm = $symb . '.' . $what; - my $mapparm=$mapname.'___(all).'.$what; - # Local refs to the hashes we're going to look at: my $useropt = $self->{USER_OPT}; @@ -3460,6 +3548,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; @@ -4257,7 +4410,7 @@ sub new { # This is a speed optimization, to avoid calling symb() too often. $self->{SYMB} = $self->symb(); - + return $self; } @@ -4384,7 +4537,7 @@ sub enclosing_map_src { } sub symb { my $self=shift; - if (defined($self->{SYMB})) { return $self->{SYMB}; } + 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)) @@ -4517,6 +4670,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' # @@ -4575,11 +4741,6 @@ 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(); @@ -4929,10 +5090,10 @@ sub duedate { my $date; my @interval=$self->parmval("interval", $part); my $due_date=$self->parmval("duedate", $part); - if ($interval[0] =~ /(\d+)/) { + if ($interval[0] =~ /^(\d+)/) { my $timelimit = $1; my $first_access=&Apache::lonnet::get_first_access($interval[1], - $self->{SYMB}); + $self->{SYMB}); if (defined($first_access)) { my $interval = $first_access+$timelimit; $date = (!$due_date || $interval < $due_date) ? $interval @@ -5074,8 +5235,14 @@ sub getReturnHash { my $self = shift; if (!defined($self->{RETURN_HASH})) { - my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME}); - $self->{RETURN_HASH} = \%tmpHash; + #my %tmpHash = &Apache::lonnet::restore($self->{SYMB},undef,$self->{DOMAIN},$self->{USERNAME}); + #$self->{RETURN_HASH} = \%tmpHash; + # When info is retrieved for several resources (as when rendering a directory), + # it is much faster to use the user profile dump and avoid repeated lonnet requests + # (especially since lonnet::currentdump is using Lond directly whenever possible, + # and lonnet::restore is not at this point). + $self->{NAV_MAP}->get_user_data(); + $self->{RETURN_HASH} = $self->{NAV_MAP}->{STUDENT_DATA}->{$self->{SYMB}}; } } @@ -5216,6 +5383,8 @@ sub parts { my $self = shift; if ($self->ext) { return []; } + if (($self->is_tool()) && + ($self->is_gradable())) { return ['0']; } $self->extractParts(); return $self->{PARTS}; @@ -5306,7 +5475,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'); @@ -5590,6 +5759,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 @@ -5602,6 +5779,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; @@ -5616,8 +5795,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') { @@ -5771,7 +5954,8 @@ sub status { # There are a few whole rows we can dispose of: if ($completionStatus == CORRECT || - $completionStatus == CORRECT_BY_OVERRIDE ) { + $completionStatus == CORRECT_BY_OVERRIDE || + $completionStatus == CORRECT_BY_PASSBACK ) { if ( $suppressFeedback ) { return ANSWER_SUBMITTED } my $awarded=$self->awarded($part); if ($awarded < 1 && $awarded > 0) { @@ -5784,7 +5968,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; } @@ -5826,7 +6011,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; @@ -5926,12 +6112,29 @@ sub check_for_slot { my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'}, $env{'user.domain'}); if (ref($reservable) eq 'HASH') { + my ($map) = &Apache::lonnet::decode_symb($symb); 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)) { + if ($reservable->{'now'}{$slot}{'symb'} eq '') { $canuse = 1; + } else { + my %oksymbs; + my @slotsymbs = split(/\s*,\s*/,$reservable->{'now'}{$slot}{'symb'}); + map { $oksymbs{$_} = 1; } @slotsymbs; + if ($oksymbs{$symb}) { + $canuse = 1; + } else { + foreach my $item (@slotsymbs) { + if ($item =~ /\.(page|sequence)$/) { + (undef,undef, my $sloturl) = &Apache::lonnet::decode_symb($item); + if (($map ne '') && ($map eq $sloturl)) { + $canuse = 1; + last; + } + } + } + } } if ($canuse) { if ($checkedin) { @@ -5952,8 +6155,26 @@ sub check_for_slot { 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)) { + if ($reservable->{'future'}{$slot}{'symb'} eq '') { + $canuse = 1; + } elsif ($reservable->{'future'}{$slot}{'symb'} =~ /,/) { + my %oksymbs; + my @slotsymbs = split(/\s*,\s*/,$reservable->{'future'}{$slot}{'symb'}); + map { $oksymbs{$_} = 1; } @slotsymbs; + if ($oksymbs{$symb}) { + $canuse = 1; + } else { + foreach my $item (@slotsymbs) { + if ($item =~ /\.(page|sequence)$/) { + (undef,undef, my $sloturl) = &Apache::lonnet::decode_symb($item); + if (($map ne '') && ($map eq $sloturl)) { + $canuse = 1; + last; + } + } + } + } + } elsif ($reservable->{'future'}{$slot}{'symb'} eq $symb) { $canuse = 1; } if ($canuse) {