--- loncom/interface/lonnavmaps.pm 2015/06/19 15:36:53 1.508 +++ loncom/interface/lonnavmaps.pm 2017/09/10 00:11:27 1.535 @@ -1,8 +1,7 @@ # The LearningOnline Network with CAPA # Navigate Maps Handler # -# $Id: lonnavmaps.pm,v 1.508 2015/06/19 15:36:53 damieng Exp $ - +# $Id: lonnavmaps.pm,v 1.535 2017/09/10 00:11:27 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 @@ -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 @@ -486,7 +501,7 @@ use Apache::lonlocal; use Apache::lonnet; use Apache::lonmap; -use POSIX qw (floor strftime); +use POSIX qw (ceil floor strftime); use Time::HiRes qw( gettimeofday tv_interval ); use LONCAPA; use DateTime(); @@ -624,44 +639,52 @@ 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->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 +930,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... @@ -981,11 +1007,15 @@ sub render_resource { # Don't allow users to manipulate folder $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif'; $icon = ""."\"".($nowOpen"; - - $linkopen = ""; - $linkclose = ""; + if ($params->{'caller'} eq 'sequence') { + $linkopen = ""; + } else { + $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 +1025,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').') '; @@ -1342,10 +1375,11 @@ sub render { my $currenturl = $env{'form.postdata'}; #$currenturl=~s/^http\:\/\///; #$currenturl=~s/^[^\/]+//; - - $here = $jump = &Apache::lonnet::symbread($currenturl); + unless ($args->{'caller'} eq 'sequence') { + $here = $jump = &Apache::lonnet::symbread($currenturl); + } } - if ($here eq '') { + if (($here eq '') && ($args->{'caller'} ne 'sequence')) { my $last; if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', &GDBM_READER(),0640)) { @@ -1527,7 +1561,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 +1608,45 @@ 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; @@ -1665,6 +1704,23 @@ END undef($args->{'sort'}); } + # Determine if page will be served with https in case + # it contains a syllabus which uses an external URL + # which points at an http site. + + my ($is_ssl,$cdom,$cnum,$hostname); + if ($ENV{'SERVER_PORT'} == 443) { + $is_ssl = 1; + if ($r) { + $hostname = $r->hostname(); + } else { + $hostname = $ENV{'SERVER_NAME'}; + } + } + if ($env{'request.course.id'}) { + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } while (1) { if ($args->{'sort'}) { @@ -1700,9 +1756,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 @@ -1786,11 +1854,29 @@ END $stack=$it->getStack(); } ($src,$symb,$anchor)=getLinkForResource($stack); + my $srcHasQuestion = $src =~ /\?/; + if ($env{'request.course.id'}) { + if (($is_ssl) && ($src =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) && + ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { + if ($hostname ne '') { + $src = 'http://'.$hostname.$src; + } + $src .= ($srcHasQuestion? '&' : '?') . 'usehttp=1'; + $srcHasQuestion = 1; + } elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) { + if ($hostname ne '') { + $src = 'http://'.$hostname.$src; + } + } + } if (defined($anchor)) { $anchor='#'.$anchor; } - my $srcHasQuestion = $src =~ /\?/; - $args->{"resourceLink"} = $src. - ($srcHasQuestion?'&':'?') . - 'symb=' . &escape($symb).$anchor; + if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) { + $args->{"resourceLink"} = $src.($srcHasQuestion?'&':'?') .'navmap=1'; + } else { + $args->{"resourceLink"} = $src. + ($srcHasQuestion?'&':'?') . + 'symb=' . &escape($symb).$anchor; + } } # Now, we've decided what parts to show. Loop through them and # show them. @@ -2111,7 +2197,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} = {}; @@ -2576,6 +2662,7 @@ sub parmval { return $self->{PARM_CACHE}->{$hashkey}; } } + my $result = $self->parmval_real($what, $symb, $recurse); $self->{PARM_CACHE}->{$hashkey} = $result; if (wantarray) { @@ -2609,29 +2696,35 @@ sub parmval_real { my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb); $mapname = &Apache::lonnet::deversion($mapname); + 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; @@ -2643,6 +2736,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']; } } @@ -2650,12 +2760,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']; } } @@ -2679,6 +2823,25 @@ sub parmval_real { # --------------------------------------------------- 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; @@ -2701,6 +2864,108 @@ 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; +} + +sub recursed_crumbs { + my ($self,$mapurl,$restitle) = @_; + my (@revmapinfo,@revmapres); + my $mapres = $self->getResourceByUrl($mapurl); + if (ref($mapres)) { + @revmapres = map { $self->getByMapPc($_); } split(/,/,$mapres->map_breadcrumbs()); + shift(@revmapres); + } + my $allowedlength = 60; + my $minlength = 5; + my $allowedtitle = 30; + if (($env{'environment.icons'} eq 'iconsonly') && (!$env{'browser.mobile'})) { + $allowedlength = 100; + $allowedtitle = 70; + } + if (length($restitle) > $allowedtitle) { + $restitle = &truncate_crumb_text($restitle,$allowedtitle); + } + my $totallength = length($restitle); + my @links; + + foreach my $map (@revmapres) { + my $pc = $map->map_pc(); + next if ((!$pc) || ($pc == 1)); + push(@links,$map); + push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $map->title(),'no_mt' => 1,}); + $totallength += length($map->title()); + } + my $numlinks = scalar(@links); + if ($numlinks) { + if ($totallength - $allowedlength > 0) { + my $available = $allowedlength - length($restitle); + my $avg = POSIX::ceil($available/$numlinks); + if ($avg < $minlength) { + $avg = $minlength; + } + @revmapinfo = (); + foreach my $map (@links) { + my $showntitle = &truncate_crumb_text($map->title(),$avg); + if ($showntitle ne '') { + push(@revmapinfo,{'href' => $map->link().'?navmap=1','text' => $showntitle,'no_mt' => 1,}); + } + } + } + } + if ($restitle ne '') { + push(@revmapinfo,{'text' => $restitle, 'no_mt' => 1}); + } + return @revmapinfo; +} + +sub truncate_crumb_text { + my ($title,$limit) = @_; + my $showntitle = ''; + if (length($title) > $limit) { + my @words = split(/\b\s*/,$title); + if (@words == 1) { + $showntitle = substr($title,0,$limit).' ...'; + } else { + my $linklength = 0; + my $num = 0; + foreach my $word (@words) { + $linklength += 1+length($word); + if ($word eq '-') { + $showntitle =~ s/ $//; + $showntitle .= $word; + } elsif ($linklength > $limit) { + if ($num < @words) { + $showntitle .= $word.' ...'; + last; + } else { + $showntitle .= $word; + } + } else { + $showntitle .= $word.' '; + } + } + $showntitle =~ s/ $//; + } + return $showntitle; + } else { + return $title; + } +} + # # Determines the open/close dates for printing a map that # encloses a resource. @@ -2712,15 +2977,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,14 +3004,18 @@ 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; @@ -2755,15 +3024,18 @@ sub get_mapparam { # 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: @@ -2776,9 +3048,30 @@ sub get_mapparam { if ($uname and defined($useropt)) { - if (defined($$useropt{$courselevel})) { - return $$useropt{$courselevel}; + if (defined($$useropt{$courselevelm})) { + return $$useropt{$courselevelm}; } + 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}; + } + } } # Check course -- group @@ -2786,38 +3079,100 @@ sub get_mapparam { if ($cgroup ne '' and defined ($courseopt)) { - if (defined($$courseopt{$grplevel})) { - return $$courseopt{$grplevel}; + if (defined($$courseopt{$grplevelm})) { + return $$courseopt{$grplevelm}; } + 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 (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: - my $thisparm = $$parmhash{$symbparm}; - if (defined($thisparm)) { - return $thisparm; + if ($symb) { + my $symbparm=$symb.'.'.$what; + my $thisparm = $$parmhash{$symbparm}; + if (defined($thisparm)) { + return $thisparm; + } } # Additional course parameters: if (defined($courseopt)) { - if (defined($$courseopt{$courselevel})) { - return $$courseopt{$courselevel}; + if (defined($$courseopt{$courselevelm})) { + return $$courseopt{$courselevelm}; } + 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 { @@ -2859,10 +3214,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}; @@ -2871,7 +3222,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 +3239,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 +3247,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})) { @@ -3865,7 +4216,7 @@ sub new { # This is a speed optimization, to avoid calling symb() too often. $self->{SYMB} = $self->symb(); - + return $self; } @@ -3991,6 +4342,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)) @@ -4261,6 +4613,12 @@ Returns a string with a comma-separated for the hierarchy of maps containing a map, with the top level map first, then descending to deeper levels, with the enclosing map last. +=item * B: + +Same as map_hierarchy, except maps containing only a single itemm if +it's a map, or containing no items are omitted, unless it's the top +level map (map_pc = 1), which is always included. + =back =cut @@ -4296,6 +4654,11 @@ sub map_hierarchy { my $pc = $self->map_pc(); return $self->navHash("map_hierarchy_$pc", 0); } +sub map_breadcrumbs { + my $self = shift; + my $pc = $self->map_pc(); + return $self->navHash("map_breadcrumbs_$pc", 0); +} ##### # Property queries @@ -4520,11 +4883,12 @@ sub duedate { my $date; my @interval=$self->parmval("interval", $part); my $due_date=$self->parmval("duedate", $part); - if ($interval[0] =~ /\d+/) { - my $first_access=&Apache::lonnet::get_first_access($interval[1], - $self->symb); + if ($interval[0] =~ /^(\d+)/) { + my $timelimit = $1; + my $first_access=&Apache::lonnet::get_first_access($interval[1], + $self->{SYMB}); if (defined($first_access)) { - my $interval = $first_access+$interval[0]; + my $interval = $first_access+$timelimit; $date = (!$due_date || $interval < $due_date) ? $interval : $due_date; } else { @@ -4604,7 +4968,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; } @@ -4624,8 +4988,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}}; } } @@ -4856,7 +5226,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'); @@ -5400,13 +5770,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'}; @@ -5440,19 +5810,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); @@ -5465,18 +5846,83 @@ 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'}})) { - if (($reservable->{'now'}{$slot}{'symb'} eq '') || - ($reservable->{'now'}{$slot}{'symb'} eq $symb)) { + my $canuse; + 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) { + 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'}}) { - if (($reservable->{'future'}{$slot}{'symb'} eq '') || - ($reservable->{'future'}{$slot}{'symb'} eq $symb)) { + my $canuse; + 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) { + 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'}); } } 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.