Diff for /loncom/interface/lonnavmaps.pm between versions 1.484 and 1.554

version 1.484, 2012/05/07 19:43:44 version 1.554, 2021/08/04 19:59:10
Line 2 Line 2
 # Navigate Maps Handler  # Navigate Maps Handler
 #  #
 # $Id$  # $Id$
   
 #  #
 # Copyright Michigan State University Board of Trustees  # Copyright Michigan State University Board of Trustees
 #  #
Line 52  described at http://www.lon-capa.org. Line 51  described at http://www.lon-capa.org.
 X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the  X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
 course structure and caches it in what is often referred to as the  course structure and caches it in what is often referred to as the
 "big hash" X<big hash>. You can see it if you are logged into  "big hash" X<big hash>. You can see it if you are logged into
 LON-CAPA, in a course, by going to /adm/test. (You may need to  LON-CAPA, in a course, by going to /adm/test. The content of 
 tweak the /home/httpd/lonTabs/htpasswd file to view it.) The  the hash will be under the heading "Big Hash".
 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  Big Hash contains, among other things, how resources are related
 to each other (next/previous), what resources are maps, which   to each other (next/previous), what resources are maps, which 
Line 65  processed. Line 71  processed.
   
 Apache::lonnavmaps provides an object model for manipulating this  Apache::lonnavmaps provides an object model for manipulating this
 information in a higher-level fashion than directly manipulating   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-  that aren't necessarily stored in the Big Hash, but are a per-
 resource sort of value, like whether there is any feedback on   resource sort of value, like whether there is any feedback on 
 a given resource.  a given resource.
Line 78  Apache::lonnavmaps also provides fairly Line 84  Apache::lonnavmaps also provides fairly
 rendering navmaps, and last but not least, provides the navmaps  rendering navmaps, and last but not least, provides the navmaps
 view for when the user clicks the NAV button.  view for when the user clicks the NAV button.
   
 B<Note>: Apache::lonnavmaps I<only> works for the "currently  B<Note>: Apache::lonnavmaps by default will show information 
 logged in user"; if you want things like "due dates for another  for the "currently logged in user".  However, if information
 student" lonnavmaps can not directly retrieve information like  about resources is needed for a different user, e.g., a bubblesheet
 that. You need the EXT function. This module can still help,  exam which uses randomorder, or randompick needs to be printed or 
 because many things, such as the course structure, are constant  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  between users, and Apache::lonnavmaps can help by providing
 symbs for the EXT call.  symbs for the EXT call.
   
Line 92  all, then documents the Apache::lonnavma Line 105  all, then documents the Apache::lonnavma
 is the key to accessing the Big Hash information, covers the use  is the key to accessing the Big Hash information, covers the use
 of the Iterator (which provides the logic for traversing the   of the Iterator (which provides the logic for traversing the 
 somewhat-complicated Big Hash data structure), documents 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  =head1 Subroutine: render
   
Line 164  If true, the resource's folder will not Line 179  If true, the resource's folder will not
 it. Default is false. True implies printCloseAll is false, since you  it. Default is false. True implies printCloseAll is false, since you
 can't close or open folders when this is on anyhow.  can't close or open folders when this is on anyhow.
   
   =item * B<map_no_edit_link>:
   
   If true, the title of the folder or page will not be followed by an
   icon/link to direct editing of a folder or composite page, originally
   added via the Course Editor.
   
 =back  =back
   
 =item * B<Apache::lonnavmaps::communication_status>:  =item * B<Apache::lonnavmaps::communication_status>:
Line 480  use Apache::lonlocal; Line 501  use Apache::lonlocal;
 use Apache::lonnet;  use Apache::lonnet;
 use Apache::lonmap;  use Apache::lonmap;
   
 use POSIX qw (floor strftime);  use POSIX qw (ceil floor strftime);
 use Time::HiRes qw( gettimeofday tv_interval );  use Time::HiRes qw( gettimeofday tv_interval );
 use LONCAPA;  use LONCAPA;
 use DateTime();  use DateTime();
   use HTML::Entities;
   
 # For debugging  # For debugging
   
 # use Data::Dumper;  #use Data::Dumper;
   
   
 # symbolic constants  # symbolic constants
Line 570  sub getLinkForResource { Line 592  sub getLinkForResource {
     my $anchor;      my $anchor;
     if ($res->is_page()) {      if ($res->is_page()) {
  foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }   foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
  $anchor=&escape($anchor->shown_symb());   if ($anchor->encrypted() && !&advancedUser()) {
       $anchor='LC_'.$anchor->id();
    } else {
       $anchor=&escape($anchor->shown_symb());
    }
  return ($res->link(),$res->shown_symb(),$anchor);   return ($res->link(),$res->shown_symb(),$anchor);
     }      }
             # in case folder was skipped over as "only sequence"              # in case folder was skipped over as "only sequence"
Line 612  sub getDescription { Line 638  sub getDescription {
         return &mt("Having technical difficulties; please check status later");           return &mt("Having technical difficulties; please check status later"); 
     }      }
     if ($status == $res->NOTHING_SET) {      if ($status == $res->NOTHING_SET) {
         return &Apache::lonhtmlcommon::direct_parm_link(&mt("Not currently assigned.",$res->symb(),'opendate'),$part);          return &Apache::lonhtmlcommon::direct_parm_link(&mt('Not currently assigned'),$res->symb(),'opendate',$part);
     }      }
     if ($status == $res->OPEN_LATER) {      if ($status == $res->OPEN_LATER) {
         return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part));          return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part));
     }      }
       my $slotinfo;
     if ($res->simpleStatus($part) == $res->OPEN) {      if ($res->simpleStatus($part) == $res->OPEN) {
         unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {          unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {
             my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);              my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);
               my $slotmsg;
             if ($slot_status == $res->UNKNOWN) {              if ($slot_status == $res->UNKNOWN) {
                 return &mt('Reservation status unknown');                  $slotmsg = &mt('Reservation status unknown');
             } elsif ($slot_status == $res->RESERVED) {              } elsif ($slot_status == $res->RESERVED) {
                 return &mt('Reserved - ends [_1]',                  $slotmsg = &mt('Reserved - ends [_1]',
                            timeToHumanString($slot_time,'end'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVED_LOCATION) {              } 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'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVED_LATER) {              } elsif ($slot_status == $res->RESERVED_LATER) {
                 return &mt('Reserved - next open [_1]',                  $slotmsg = &mt('Reserved - next open [_1]',
                            timeToHumanString($slot_time,'start'));                             timeToHumanString($slot_time,'start'));
             } elsif ($slot_status == $res->RESERVABLE) {              } 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'));                             timeToHumanString($slot_time,'end'));
             } elsif ($slot_status == $res->RESERVABLE_LATER) {              } elsif ($slot_status == $res->RESERVABLE_LATER) {
                 return &mt('Reservable, reservations open [_1]',                  $slotmsg = &mt('Reservable, reservations open [_1]',
                            timeToHumanString($slot_time,'start'));                             timeToHumanString($slot_time,'start'));
             } elsif ($slot_status == $res->NOT_IN_A_SLOT) {              } 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) {              } elsif ($slot_status == $res->NOTRESERVABLE) {
                 return &mt('Reservation not available');                  $slotmsg = &mt('Reservation not available');
             } elsif ($slot_status == $res->WAITING_FOR_GRADE) {              } 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 = ('&nbsp;' x 2).'('.$slotmsg.')';
             }              }
         }          }
     }      }
     if ($status == $res->OPEN) {      if ($status == $res->OPEN) {
         if ($due) {          if ($due) {
     if ($res->is_practice()) {      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 {      } 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 {          } 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) {      if ($status == $res->PAST_DUE_ANSWER_LATER) {
Line 669  sub getDescription { Line 706  sub getDescription {
     }      }
     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)      if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
  && $res->handgrade($part) ne 'yes') {   && $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) {      if ($status == $res->EXCUSED) {
         return &mt("Excused by instructor");          return &mt("Excused by instructor");
Line 691  sub getDescription { Line 738  sub getDescription {
         my $maxtries = $res->maxtries($part);          my $maxtries = $res->maxtries($part);
         my $triesString = "";          my $triesString = "";
         if ($tries && $maxtries) {          if ($tries && $maxtries) {
             $triesString = '<font size="-1"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></font>';              $triesString = '<span class="LC_fontsize_medium"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></span>';
             if ($maxtries > 1 && $maxtries - $tries == 1) {              if ($maxtries > 1 && $maxtries - $tries == 1) {
                 $triesString = "<b>$triesString</b>";                  $triesString = "<b>$triesString</b>";
             }              }
Line 900  sub render_resource { Line 947  sub render_resource {
     my $nonLinkedText = ''; # stuff after resource title not in link      my $nonLinkedText = ''; # stuff after resource title not in link
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
       if ($resource->ext()) {
           $link =~ s/\#.+(\?)/$1/g;
       }
   
     #  The URL part is not escaped at this point, but the symb is...       #  The URL part is not escaped at this point, but the symb is... 
   
Line 917  sub render_resource { Line 967  sub render_resource {
         $newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />";          $newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />";
     }      }
   
     # links to open and close the folder  
   
     my $whitespace = $location.'/whitespace_21.gif';      my $whitespace = $location.'/whitespace_21.gif';
     my $linkopen = "<img src='$whitespace' alt='' />"."<a href=\"$link\">";      my ($nomodal,$linkopen,$linkclose);
     my $linkclose = "</a>";      unless ($resource->is_map() || $params->{'resource_nolink'}) {
           $linkopen = "<img src='$whitespace' alt='' />";
           $linkclose = "</a>";
           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 .= "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />";
               } else {
                   $linkopen .= "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">";
               }
           } else {
               $linkopen .= "<a href=\"$link\">";
           }
       }
   
     # Default icon: unknown page      # Default icon: unknown page
     my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />";      my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />";
Line 969  sub render_resource { Line 1040  sub render_resource {
                 '&amp;jump=' .                  '&amp;jump=' .
                 &escape($resource->symb()) .                   &escape($resource->symb()) . 
                 "&amp;folderManip=1\">";                  "&amp;folderManip=1\">";
               $linkclose = '</a>';
         } else {          } else {
             # Don't allow users to manipulate folder              # Don't allow users to manipulate folder
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
             $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";              $icon = "<img class=\"LC_space\" src='$whitespace' alt='' />"."<img class=\"LC_contentImage\" src='$location/$icon' alt=\"".($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
               if ($params->{'caller'} eq 'sequence') {
             $linkopen = "";                  $linkopen = "<a href=\"$link\">";
             $linkclose = "";                  $linkclose = '</a>';
               } 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/)) {              ($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/)) {
             my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png';              if (!$params->{'map_no_edit_link'}) {
             $editmapLink='&nbsp;'.                  my $icon = &Apache::loncommon::lonhttpdurl('/res/adm/pages').'/editmap.png';
                          '<a href="/adm/coursedocs?command=direct&symb='.&escape($resource->symb()).'">'.                  $editmapLink='&nbsp;'.
                            '<a href="/adm/coursedocs?command=directnav&amp;symb='.&escape($resource->symb()).'">'.
                          '<img src="'.$icon.'" alt="'.&mt('Edit Content').'" title="'.&mt('Edit Content').'" />'.                           '<img src="'.$icon.'" alt="'.&mt('Edit Content').'" title="'.&mt('Edit Content').'" />'.
                          '</a>';                           '</a>';
               }
           }
           if ($params->{'mapHidden'} || $resource->randomout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
           } elsif ($params->{'mapUnlisted'}) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> ';
           } elsif ($params->{'mapHiddenDeepLink'} || $resource->deeplinkout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> ';
           }
       } else {
           if ($resource->randomout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
           } elsif ($resource->deeplinkout()) {
               $nonLinkedText .= ' <span class="LC_warning">('.&mt('not shown').')</span> ';
           } else {
               my $deeplink = $resource->deeplink($params->{caller});
               if ((($deeplink eq 'absent') || ($deeplink eq 'grades')) &&
                     &advancedUser()) {
                   $nonLinkedText .= ' <span class="LC_warning">('.&mt('unlisted').')</span> ';
               } elsif (($deeplink) && ($deeplink) ne 'full') {
                   if (&advancedUser()) {
                       $nonLinkedText .= ' <span class="LC_warning">('.&mt('deep-link access').
                                         ')</span> ';
                   } else {
                       $nonLinkedText .= ' <span class="LC_warning">('.&mt('access via external site').
                                         ')</span> ';
                   }
               }
         }          }
     }  
   
     if ($resource->randomout()) {  
         $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';  
     }      }
     if (!$resource->condval()) {      if (!$resource->condval()) {
         $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';          $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';
     }      }
     if (($resource->is_practice()) && ($resource->is_raw_problem())) {      if (($resource->is_practice()) && ($resource->is_raw_problem())) {
         $nonLinkedText .=' <font color="green"><b>'.&mt('not graded').'</b></font>';          $nonLinkedText .=' <span class="LC_info"><b>'.&mt('not graded').'</b></span>';
     }      }
   
     # We're done preparing and finally ready to start the rendering      # We're done preparing and finally ready to start the rendering
Line 1039  sub render_resource { Line 1140  sub render_resource {
     }      }
   
     if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {      if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
         $result .= "$curMarkerBegin<a href=\"$link\">$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";          $linkclose = '</a>';
     } else {          if ($params->{'modalLink'}) {
         $result .= "$curMarkerBegin$linkopen$title$partLabel</a>$curMarkerEnd$editmapLink$nonLinkedText</td>";              my $esclink = &js_escape($link);
               if ($nomodal) {
                   $linkopen = "<a href=\"#\" onclick=\"javascript:window.open('$esclink','resourcepreview','height=400,width=500,scrollbars=1,resizable=1,menubar=0,location=1'); return false;\" />";
               } else {
                   $linkopen = "<a href=\"$link\" onclick=\"javascript:openMyModal('$esclink',600,500,'yes','true'); return false;\">";
               }
           } else {
               $linkopen = "<a href=\"$link\">";
           }
     }      }
       $result .= "$curMarkerBegin$linkopen$title$partLabel$linkclose$curMarkerEnd$editmapLink$nonLinkedText</td>";
   
     return $result;      return $result;
 }  }
Line 1104  sub render_quick_status { Line 1214  sub render_quick_status {
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
  $result .= '<td class="LC_middle">';   $result .= '<td class="LC_middle">';
     if ($resource->is_problem() &&      if ($resource->is_gradable() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         my $icon = $statusIconMap{$resource->simpleStatus($part)};          my $icon = $statusIconMap{$resource->simpleStatus($part)};
         my $alt = $iconAltTags{$icon};          my $alt = $iconAltTags{$icon};
Line 1128  sub render_long_status { Line 1238  sub render_long_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
     my $color;      my $color;
     if ($resource->is_problem() || $resource->is_practice()) {      my $info = '';
       if ($resource->is_gradable() || $resource->is_practice()) {
         $color = $colormap{$resource->status};          $color = $colormap{$resource->status};
           
         if (dueInLessThan24Hours($resource, $part) ||          if (dueInLessThan24Hours($resource, $part)) {
             lastTry($resource, $part)) {  
             $color = $hurryUpColor;              $color = $hurryUpColor;
         }              $info = ' title="'.&mt('Due in less than 24 hours!').'"';
           } elsif (lastTry($resource, $part)) {
               unless (($resource->problemstatus($part) eq 'no') ||
                       ($resource->problemstatus($part) eq 'no_feedback_ever')) {
                   $color = $hurryUpColor;
                   $info = ' title="'.&mt('One try remaining!').'"';
               }
            }
     }      }
       
     if ($resource->kind() eq "res" &&      if (($resource->kind() eq "res") &&
         $resource->is_raw_problem() &&          ($resource->is_raw_problem() || $resource->is_gradable()) &&
         !$firstDisplayed) {          !$firstDisplayed) {
         if ($color) {$result .= "<font color=\"$color\"><b>"; }          if ($color) {$result .= '<span style="color:'.$color.'"'.$info.'><b>'; }
         $result .= getDescription($resource, $part);          $result .= getDescription($resource, $part);
         if ($color) {$result .= "</b></font>"; }          if ($color) {$result .= "</b></span>"; }
     }      }
     if ($resource->is_map() && &advancedUser() && $resource->randompick()) {      if ($resource->is_map() && &advancedUser() && $resource->randompick()) {
         $result .= &mt('(randomly select [_1])', $resource->randompick());          $result .= &mt('(randomly select [_1])', $resource->randompick());
Line 1185  my @statuses = ($resObj->CORRECT, $resOb Line 1302  my @statuses = ($resObj->CORRECT, $resOb
   
 sub render_parts_summary_status {  sub render_parts_summary_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }      if (!$resource->is_gradable() && !$resource->contains_problem) { return '<td></td>'; }
     if ($params->{showParts}) {       if ($params->{showParts}) { 
  return '<td></td>';   return '<td></td>';
     }      }
Line 1224  sub render_parts_summary_status { Line 1341  sub render_parts_summary_status {
     }      }
     $return.= $td . $totalParts . ' parts: ';      $return.= $td . $totalParts . ' parts: ';
     foreach my $status (@statuses) {      foreach my $status (@statuses) {
  if ($overallstatus{$status}) {          if ($overallstatus{$status}) {
     $return.="<font color='" . $statusColors{$status} .              $return.='<span style="color:' . $statusColors{$status}
  "'>" . $overallstatus{$status} . ' '                     . '">' . $overallstatus{$status} . ' '
  . $statusStrings{$status} . "</font>";                     . $statusStrings{$status} . '</span>';
  }          }
     }      }
     $return.= $endtd;      $return.= $endtd;
     return $return;      return $return;
Line 1296  sub render { Line 1413  sub render {
         # Without renaming the filterfunc, the server seems to go into          # Without renaming the filterfunc, the server seems to go into
         # an infinite loop          # an infinite loop
         my $oldFilterFunc = $filterFunc;          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() &&
                                 &$oldFilterFunc($res);};                                  &$oldFilterFunc($res);};
     }      }
   
Line 1326  sub render { Line 1446  sub render {
             my $currenturl = $env{'form.postdata'};              my $currenturl = $env{'form.postdata'};
             #$currenturl=~s/^http\:\/\///;              #$currenturl=~s/^http\:\/\///;
             #$currenturl=~s/^[^\/]+//;              #$currenturl=~s/^[^\/]+//;
                           unless ($args->{'caller'} eq 'sequence') {
             $here = $jump = &Apache::lonnet::symbread($currenturl);                  $here = $jump = &Apache::lonnet::symbread($currenturl);
               }
  }   }
  if ($here eq '') {   if (($here eq '') && ($args->{'caller'} ne 'sequence')) {
     my $last;      my $last;
     if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',      if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
                     &GDBM_READER(),0640)) {                      &GDBM_READER(),0640)) {
Line 1389  sub render { Line 1510  sub render {
         if ($args->{'iterator_map'}) {          if ($args->{'iterator_map'}) {
             my $map = $args->{'iterator_map'};              my $map = $args->{'iterator_map'};
             $map = $navmap->getResourceByUrl($map);              $map = $navmap->getResourceByUrl($map);
             my $firstResource = $map->map_start();              if (ref($map)) {
             my $finishResource = $map->map_finish();                  my $firstResource = $map->map_start();
                   my $finishResource = $map->map_finish();
             $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);                  $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
               } else {
                   return;
               }
         } else {          } else {
             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'});              $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition,undef,$args->{'include_top_level_map'});
         }          }
Line 1481  sub render { Line 1605  sub render {
       'document.clearbubbles.submit()',        'document.clearbubbles.submit()',
       'Mark all posts read');        'Mark all posts read');
  my $time=time;   my $time=time;
           my $querystr = &HTML::Entities::encode($ENV{'QUERY_STRING'},'<>&"');
  $result .= (<<END);   $result .= (<<END);
     <form name="clearbubbles" method="post" action="/adm/feedback">      <form name="clearbubbles" method="post" action="/adm/feedback">
  <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />   <input type="hidden" name="navurl" value="$querystr" />
  <input type="hidden" name="navtime" value="$time" />   <input type="hidden" name="navtime" value="$time" />
 END  END
         if ($env{'form.register'}) {          if ($env{'form.register'}) {
Line 1510  END Line 1635  END
  $result.='</form>';   $result.='</form>';
     }      }
     if (($args->{'caller'} eq 'navmapsdisplay') &&      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 $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
         my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};          my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
         if ($env{'course.'.$env{'request.course.id'}.'.url'} eq           if ($env{'course.'.$env{'request.course.id'}.'.url'} eq 
Line 1556  END Line 1682  END
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");      $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' alt='' />");
     $args->{'displayedHereMarker'} = 0;      $args->{'displayedHereMarker'} = 0;
   
     # If we're suppressing empty sequences, look for them here. Use DFS for speed,      # If we're suppressing empty sequences, look for them here.
     # since structure actually doesn't matter, except what map has what resources.      # We also do this even if $args->{'suppressEmptySequences'}
     if ($args->{'suppressEmptySequences'}) {      # is not true, so we can hide empty sequences for which the
         my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,      # hiddenresource parameter is set to yes (at map level), or
                                                          $it->{FIRST_RESOURCE},      # mark as hidden for users who have $userCanSeeHidden.  
                                                          $it->{FINISH_RESOURCE},      # Use DFS for speed, since structure actually doesn't matter,
                                                          {}, undef, 1);      # except what map has what resources.
         my $depth = 0;  
         $dfsit->next();      my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
         my $curRes = $dfsit->next();                                                       $it->{FIRST_RESOURCE},
         while ($depth > -1) {                                                       $it->{FINISH_RESOURCE},
             if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }                                                       {}, undef, 1);
             if ($curRes == $dfsit->END_MAP()) { $depth--; }      my $depth = 0;
       $dfsit->next();
             if (ref($curRes)) {       my $curRes = $dfsit->next();
                 # Parallel pre-processing: Do sequences have non-filtered-out children?      while ($depth > -1) {
                 if ($curRes->is_map()) {          if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
                     $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;          if ($curRes == $dfsit->END_MAP()) { $depth--; }
                     # Sequences themselves do not count as visible children,  
                     # unless those sequences also have visible children.          if (ref($curRes)) { 
                     # This means if a sequence appears, there's a "promise"              # Parallel pre-processing: Do sequences have non-filtered-out children?
                     # that there's something under it if you open it, somewhere.              if ($curRes->is_map()) {
                 } else {                  $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
                     # Not a sequence: if it's filtered, ignore it, otherwise                  # Sequences themselves do not count as visible children,
                     # rise up the stack and mark the sequences as having children                  # unless those sequences also have visible children.
                     if (&$filterFunc($curRes)) {                  # This means if a sequence appears, there's a "promise"
                         for my $sequence (@{$dfsit->getStack()}) {                  # that there's something under it if you open it, somewhere.
                             $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;              } 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;      my $displayedJumpMarker = 0;
Line 1648  END Line 1778  END
  undef($args->{'sort'});   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'};
       }
   
       my $inhibitmenu;
       if ($args->{'modalLink'}) {
           $inhibitmenu = '&amp;inhibitmenu=yes';
       }
   
     while (1) {      while (1) {
  if ($args->{'sort'}) {   if ($args->{'sort'}) {
Line 1683  END Line 1835  END
         }           } 
   
         # If this is an empty sequence and we're filtering them, continue on          # If this is an empty sequence and we're filtering them, continue on
         if ($curRes->is_map() && $args->{'suppressEmptySequences'} &&          $args->{'mapHidden'} = 0;
             !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {          $args->{'mapUnlisted'} = 0;
             next;          $args->{'mapHiddenDeepLink'} = 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;
                       }
                   } elsif ($curRes->deeplinkout) {
                       if ($userCanSeeHidden) {
                           $args->{'mapHiddenDeepLink'} = 1;
                       } else {
                           next;
                       }
                   } else {
                       my $deeplink = $navmap->get_mapparam(undef,$mapname,"0.deeplink");
                       my ($state,$others,$listed) = split(/,/,$deeplink);
                       if (($listed eq 'absent') || ($listed eq 'grades')) {
                           if ($userCanSeeHidden) {
                               $args->{'mapUnlisted'} = 1;
                           } else {
                               next;
                           }
                       }
                   }
               }
         }          }
   
         # If we're suppressing navmaps and this is a navmap, continue on          # If we're suppressing navmaps and this is a navmap, continue on
Line 1746  END Line 1928  END
     $args->{'condensed'} = 1;      $args->{'condensed'} = 1;
  }   }
             }              }
         }           }
                       # If deep-link parameter is set (and is not set to full) suppress link
           # unless privileged user, tinyurl used for login resolved to a map, and
           # the resource is within the map.
           if ((!$curRes->deeplink($args->{'caller'})) ||
               ($curRes->deeplink($args->{'caller'}) eq 'full') || &advancedUser()) {
               $args->{'resource_nolink'} = 0;
           } else {
               $args->{'resource_nolink'} = 1;
           }
   
         # If the multipart problem was condensed, "forget" it was multipart          # If the multipart problem was condensed, "forget" it was multipart
         if (scalar(@parts) == 1) {          if (scalar(@parts) == 1) {
             $args->{'multipart'} = 0;              $args->{'multipart'} = 0;
Line 1769  END Line 1960  END
  $stack=$it->getStack();   $stack=$it->getStack();
     }      }
     ($src,$symb,$anchor)=getLinkForResource($stack);      ($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://})) {
                       unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
                           if ($hostname ne '') {
                               $src = 'http://'.$hostname.$src;
                           }
                           $src .= ($srcHasQuestion? '&amp;' : '?') . 'usehttp=1';
                           $srcHasQuestion = 1;
                       }
                   } elsif (($is_ssl) && ($src =~ m{^\Q/adm/wrapper/ext/\E(?!https:)})) {
                       unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
                           if ($hostname ne '') {
                               $src = 'http://'.$hostname.$src;
                           }
                           $src .= ($srcHasQuestion? '&amp;' : '?') . 'usehttp=1';
                           $srcHasQuestion = 1;
                       }
                   }
               }
     if (defined($anchor)) { $anchor='#'.$anchor; }      if (defined($anchor)) { $anchor='#'.$anchor; }
     my $srcHasQuestion = $src =~ /\?/;      if (($args->{'caller'} eq 'sequence') && ($curRes->is_map())) {
     $args->{"resourceLink"} = $src.          $args->{"resourceLink"} = $src.($srcHasQuestion?'&amp;':'?') .'navmap=1';
  ($srcHasQuestion?'&amp;':'?') .      } else {
  'symb=' . &escape($symb).$anchor;          $args->{"resourceLink"} = $src.
       ($srcHasQuestion?'&amp;':'?') .
       'symb=' . &escape($symb).$inhibitmenu.$anchor;
       }
  }   }
         # Now, we've decided what parts to show. Loop through them and          # Now, we've decided what parts to show. Loop through them and
         # show them.          # show them.
Line 1801  END Line 2016  END
                     $currentJumpDelta) {                      $currentJumpDelta) {
                     # Jam the anchor after the <td> tag;                      # Jam the anchor after the <td> tag;
                     # necessary for valid HTML (which Mozilla requires)                      # necessary for valid HTML (which Mozilla requires)
                     $colHTML =~ s/\>/\>\<a name="curloc" \/\>/;                      $colHTML =~ s/\>/\>\<a name="curloc" \>\<\/a\>/;
                     $displayedJumpMarker = 1;                      $displayedJumpMarker = 1;
                 }                  }
                 $result .= $colHTML . "\n";                  $result .= $colHTML . "\n";
Line 1825  END Line 2040  END
     }      }
  }   }
     }      }
   
       $result.=&Apache::loncommon::end_data_table();
           
     # Print out the part that jumps to #curloc if it exists      # Print out the part that jumps to #curloc if it exists
     # delay needed because the browser is processing the jump before      # delay needed because the browser is processing the jump before
Line 1841  if (location.href.indexOf('#curloc')==-1 Line 2058  if (location.href.indexOf('#curloc')==-1
 ");  ");
     }      }
   
     $result.=&Apache::loncommon::end_data_table();  
   
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $result = "";          $result = "";
Line 1873  sub show_linkitems_toolbar { Line 2088  sub show_linkitems_toolbar {
                           '<td class="LC_middle">'.&mt('Tools:').'</td>';                            '<td class="LC_middle">'.&mt('Tools:').'</td>';
             }              }
             $result .= '<td align="left">'."\n".              $result .= '<td align="left">'."\n".
                        '<span class="LC_nobreak">'."\n".  
                        '<ul id="LC_toolbar">';                         '<ul id="LC_toolbar">';
             my @linkorder = ('firsthomework','everything','uncompleted',              my @linkorder = ('firsthomework','everything','uncompleted',
                              'changefolder','clearbubbles','edittoplevel');                               'changefolder','clearbubbles','edittoplevel');
Line 1901  sub show_linkitems_toolbar { Line 2115  sub show_linkitems_toolbar {
                 }                  }
             }              }
             $result .= '</ul>'.              $result .= '</ul>'.
                        '</span></td>';                         '</td>';
             if (($numlinks==1) && (exists($args->{'linkitems'}{'edittoplevel'}))) {              if (($numlinks==1) && (exists($args->{'linkitems'}{'edittoplevel'}))) {
                 $result .= '<td><a href="'.$args->{'linkitems'}{'edittoplevel'}{'cmd'}.'">'.                  $result .= '<td><a href="'.$args->{'linkitems'}{'edittoplevel'}{'cmd'}.'">'.
                            &mt('Content Editor').'</a></td>';                             &mt('Content Editor').'</a></td>';
Line 2004  sub new { Line 2218  sub new {
     my $proto = shift;      my $proto = shift;
     my $class = ref($proto) || $proto;      my $class = ref($proto) || $proto;
     my $self = {};      my $self = {};
     bless($self); # So we can call change_user if neceesary      bless($self); # So we can call change_user if necessary
   
     $self->{USERNAME} = shift || $env{'user.name'};      $self->{USERNAME} = shift || $env{'user.name'};
     $self->{DOMAIN}   = shift || $env{'user.domain'};      $self->{DOMAIN}   = shift || $env{'user.domain'};
       $self->{CODE}     = shift;
       $self->{NOHIDE} = shift;
   
   
   
Line 2021  sub new { Line 2237  sub new {
   
     # We can only tie the nav hash as done below if the username/domain      # We can only tie the nav hash as done below if the username/domain
     # match the env one. Otherwise change_user does everything we need...since we can't      # match the env one. Otherwise change_user does everything we need...since we can't
     # assume there are course hashes for the specific requested user@domamin:      # assume there are course hashes for the specific requested user:domain
       # Note: change_user is also called if we need the nav hash when printing CODEd 
       # assignments or printing an exam, in which the enclosing folder for the items in
       # the exam has hidden set.
     #      #
   
     if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'})) {      if (($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'}) &&
            !$self->{CODE} && !$self->{NOHIDE}) {
   
  # tie the nav hash   # tie the nav hash
   
Line 2047  sub new { Line 2267  sub new {
  $self->{PARM_HASH} = \%parmhash;   $self->{PARM_HASH} = \%parmhash;
  $self->{PARM_CACHE} = {};   $self->{PARM_CACHE} = {};
     } else {      } else {
  $self->change_user($self->{USERNAME}, $self->{DOMAIN});   $self->change_user($self->{USERNAME}, $self->{DOMAIN},  $self->{CODE}, $self->{NOHIDE});
     }      }
   
     return $self;      return $self;
Line 2060  sub new { Line 2280  sub new {
 # Parameters:  # Parameters:
 #    user  - New user.  #    user  - New user.
 #    domain- Domain the user belongs to.  #    domain- Domain the user belongs to.
   #    code  - Anonymous CODE in use.
 # Implicit inputs:  # Implicit inputs:
 #     #   
 sub change_user {  sub change_user {
     my $self = shift;      my $self = shift;
     $self->{USERNAME} = shift;      $self->{USERNAME} = shift;
     $self->{DOMAIN}   = shift;      $self->{DOMAIN}   = shift;
       $self->{CODE}     = shift;
       $self->{NOHIDE}   = shift;
   
     # If the hashes are already tied make sure to break that bond:      # If the hashes are already tied make sure to break that bond:
   
Line 2081  sub change_user { Line 2304  sub change_user {
     my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});      my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});
   
     my %big_hash;      my %big_hash;
     &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, \%big_hash);      &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, $self->{CODE}, $self->{NOHIDE}, \%big_hash);
     $self->{NAV_HASH} = \%big_hash;      $self->{NAV_HASH} = \%big_hash;
   
   
   
     # 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.      # param.xxxx keys.
   
     $self->{PARM_CACHE} = {};      $self->{PARM_CACHE} = {};
           
     my %parm_hash = {};      my %parm_hash = {};
     foreach my $key (keys %big_hash) {      foreach my $key (keys(%big_hash)) {
  if ($key =~ /^param\./) {   if ($key =~ /^param\./) {
     my $param_key = $key;      my $param_key = $key;
     $param_key =~ s/^param\.//;      $param_key =~ s/^param\.//;
Line 2102  sub change_user { Line 2325  sub change_user {
   
     $self->{PARM_HASH} = \%parm_hash;      $self->{PARM_HASH} = \%parm_hash;
   
   }
       
 }   
   
 sub generate_course_user_opt {  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
Line 2146  sub generate_course_user_opt { Line 2367  sub generate_course_user_opt {
     return;      return;
 }  }
   
   
   
 sub generate_email_discuss_status {  sub generate_email_discuss_status {
     my $self = shift;      my $self = shift;
     my $symb = shift;      my $symb = shift;
Line 2165  sub generate_email_discuss_status { Line 2388  sub generate_email_discuss_status {
     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',      my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                         $self->{DOMAIN},$self->{USERNAME},'lastread');                                          $self->{DOMAIN},$self->{USERNAME},'lastread');
     my %lastreadtime = ();      my %lastreadtime = ();
     foreach my $key (keys %lastread) {      foreach my $key (keys(%lastread)) {
         my $shortkey = $key;          my $shortkey = $key;
         $shortkey =~ s/_lastread$//;          $shortkey =~ s/_lastread$//;
         $lastreadtime{$shortkey} = $lastread{$key};          $lastreadtime{$shortkey} = $lastread{$key};
Line 2179  sub generate_email_discuss_status { Line 2402  sub generate_email_discuss_status {
     foreach my $msgid (@keys) {      foreach my $msgid (@keys) {
  if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
             my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,              my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
                 $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);                  $symb,$error) = &Apache::lonmsg::unpackmsgid(&LONCAPA::escape($msgid));
             &Apache::lonenc::check_decrypt(\$symb);               &Apache::lonenc::check_decrypt(\$symb); 
             if (($fromcid ne '') && ($fromcid ne $cid)) {              if (($fromcid ne '') && ($fromcid ne $cid)) {
                 next;                  next;
Line 2280  sub getIterator { Line 2503  sub getIterator {
     my $self = shift;      my $self = shift;
     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,      my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
                                                      shift, undef, shift,                                                       shift, undef, shift,
      shift, shift);       shift, shift, shift);
     return $iterator;      return $iterator;
 }  }
   
Line 2551  sub parmval { Line 2774  sub parmval {
             return $self->{PARM_CACHE}->{$hashkey};              return $self->{PARM_CACHE}->{$hashkey};
         }          }
     }      }
   
     my $result = $self->parmval_real($what, $symb, $recurse);      my $result = $self->parmval_real($what, $symb, $recurse);
     $self->{PARM_CACHE}->{$hashkey} = $result;      $self->{PARM_CACHE}->{$hashkey} = $result;
     if (wantarray) {      if (wantarray) {
Line 2559  sub parmval { Line 2783  sub parmval {
     return $result->[0];      return $result->[0];
 }  }
   
   
 sub parmval_real {  sub parmval_real {
     my $self = shift;      my $self = shift;
     my ($what,$symb,$recurse) = @_;      my ($what,$symb,$recurse) = @_;
   
   
     # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated      # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
     $self->generate_course_user_opt();      $self->generate_course_user_opt();
   
Line 2582  sub parmval_real { Line 2808  sub parmval_real {
   
     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);      my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
     $mapname = &Apache::lonnet::deversion($mapname);      $mapname = &Apache::lonnet::deversion($mapname);
       my $toolsymb = '';
       if ($fn =~ /ext\.tool$/) {
           $toolsymb = $symb;
       }
       my ($recursed,@recurseup); 
       
 # ----------------------------------------------------- Cascading lookup scheme  # ----------------------------------------------------- Cascading lookup scheme
     my $rwhat=$what;      my $rwhat=$what;
     $what=~s/^parameter\_//;      $what=~s/^parameter\_//;
     $what=~s/\_/\./;      $what=~s/\_/\./;
   
     my $symbparm=$symb.'.'.$what;      my $symbparm=$symb.'.'.$what;
       my $recurseparm=$mapname.'___(rec).'.$what;
     my $mapparm=$mapname.'___(all).'.$what;      my $mapparm=$mapname.'___(all).'.$what;
     my $usercourseprefix=$cid;      my $usercourseprefix=$cid;
       
   
   
     my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;      my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;
     my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;      my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
       my $grpleveli=$usercourseprefix.'.['.$cgroup.'].'.$recurseparm;
     my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;      my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;
   
   
     my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;      my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
     my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;      my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
       my $secleveli=$usercourseprefix.'.['.$csec.'].'.$recurseparm;
     my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;      my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;
   
   
     my $courselevel= $usercourseprefix.'.'.$what;      my $courselevel= $usercourseprefix.'.'.$what;
     my $courselevelr=$usercourseprefix.'.'.$symbparm;      my $courselevelr=$usercourseprefix.'.'.$symbparm;
       my $courseleveli=$usercourseprefix.'.'.$recurseparm;
     my $courselevelm=$usercourseprefix.'.'.$mapparm;      my $courselevelm=$usercourseprefix.'.'.$mapparm;
   
   
     my $useropt = $self->{USER_OPT};      my $useropt = $self->{USER_OPT};
     my $courseopt = $self->{COURSE_OPT};      my $courseopt = $self->{COURSE_OPT};
     my $parmhash = $self->{PARM_HASH};      my $parmhash = $self->{PARM_HASH};
Line 2611  sub parmval_real { Line 2852  sub parmval_real {
     if ($uname and defined($useropt)) {      if ($uname and defined($useropt)) {
         if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }          if (defined($$useropt{$courselevelr})) { return [$$useropt{$courselevelr},'resource']; }
         if (defined($$useropt{$courselevelm})) { return [$$useropt{$courselevelm},'map']; }          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']; }          if (defined($$useropt{$courselevel})) { return [$$useropt{$courselevel},'course']; }
     }      }
   
Line 2618  sub parmval_real { Line 2876  sub parmval_real {
     if ($cgroup ne '' and defined($courseopt)) {      if ($cgroup ne '' and defined($courseopt)) {
         if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }          if (defined($$courseopt{$grplevelr})) { return [$$courseopt{$grplevelr},'resource']; }
         if (defined($$courseopt{$grplevelm})) { return [$$courseopt{$grplevelm},'map']; }          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 (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{$seclevelr})) { return [$$courseopt{$seclevelr},'resource']; }
         if (defined($$courseopt{$seclevelm})) { return [$$courseopt{$seclevelm},'map']; }          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']; }          if (defined($$courseopt{$seclevel})) { return [$$courseopt{$seclevel},'course']; }
     }      }
   
Line 2640  sub parmval_real { Line 2932  sub parmval_real {
   
     my $meta_rwhat=$rwhat;      my $meta_rwhat=$rwhat;
     $meta_rwhat=~s/\./_/g;      $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']}      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']}      if (defined($default)) { return [$default,'resource']}
 # --------------------------------------------------- fifth, check more course  # --------------------------------------------------- fifth, check more course
     if (defined($courseopt)) {      if (defined($courseopt)) {
         if (defined($$courseopt{$courselevelm})) { return [$$courseopt{$courselevelm},'map']; }          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})) {          if (defined($$courseopt{$courselevel})) {
            my $ret = [$$courseopt{$courselevel},'course'];             my $ret = [$$courseopt{$courselevel},'course'];
            return $ret;             return $ret;
Line 2665  sub parmval_real { Line 2976  sub parmval_real {
        if (defined($partgeneral[0])) { return \@partgeneral; }         if (defined($partgeneral[0])) { return \@partgeneral; }
     }      }
     if ($recurse) { return []; }      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']; }      if (defined($pack_def)) { return [$pack_def,'resource']; }
     return [''];      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' => $env{'request.use_absolute'}.$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' => $env{'request.use_absolute'}.$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.
   #
   sub map_printdates {
       my ($self, $res, $part) = @_;
   
   
   
   
   
       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, $mapname, $what) = @_;
   
       # Ensure the course option hash is populated:
   
       $self->generate_course_user_opt();
   
       # Get the course id and section if there is one.
   
       my $cid=$env{'request.course.id'};
       my $csec=$env{'request.course.sec'};
       my $cgroup='';
       my @cgrps=split(/:/,$env{'request.course.groups'});
       if (@cgrps > 0) {
           @cgrps = sort(@cgrps);
           $cgroup = $cgrps[0];
       } 
       my $uname=$self->{USERNAME};
       my $udom=$self->{DOMAIN};
   
       unless ($symb || $mapname) { return; }
       my $result='';
       my ($recursed,@recurseup);
   
   
       # Figure out which map we are in.
   
       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\_//;
       $what=~s/\_/\./;
   
       # Build the hash keys for the lookup:
   
       my $mapparm=$mapname.'___(all).'.$what;
       my $recurseparm=$mapname.'___(rec).'.$what; 
       my $usercourseprefix=$cid;
   
   
       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:
   
       my $useropt = $self->{USER_OPT};
       my $courseopt = $self->{COURSE_OPT};
       my $parmhash = $self->{PARM_HASH};
   
       # Check per user 
   
   
   
       if ($uname and defined($useropt)) {
    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
   
   
   
       if ($cgroup ne '' and defined ($courseopt)) {
    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 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:
   
       if ($symb) {
           my $symbparm=$symb.'.'.$what;
           my $thisparm = $$parmhash{$symbparm};
           if (defined($thisparm)) {
       return $thisparm;
           }
       }
   
   
       # Additional course parameters:
   
       if (defined($courseopt)) {
    if (defined($$courseopt{$courselevelm})) {
       return $$courseopt{$courselevelm};
    }
           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; # Undefined if we got here.
   }
   
   sub course_printdates {
       my ($self, $symb,  $part) = @_;
   
   
       my $opendate  = $self->getcourseparam($symb, $part . '.printstartdate');
       my $closedate = $self->getcourseparam($symb, $part . '.printenddate');
       return ($opendate, $closedate);
   
   }
   
   sub getcourseparam {
       my ($self, $symb, $what) = @_;
   
       $self->generate_course_user_opt(); # If necessary populate the hashes.
   
       my $uname = $self->{USERNAME};
       my $udom  = $self->{DOMAIN};
       
       # Course, section, group ids come from the env:
   
       my $cid   = $env{'request.course.id'};
       my $csec  = $env{'request.course.sec'};
       my $cgroup = ''; # Assume no group
   
       my @cgroups = split(/:/, $env{'request.course.groups'});
       if(@cgroups > 0) {
    @cgroups = sort(@cgroups);
    $cgroup  = $cgroups[0]; # There is a course group. 
      }
       my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
       $mapname = &Apache::lonnet::deversion($mapname);
   
       #
       # Make the various lookup keys:
       #
   
       $what=~s/^parameter\_//;
       $what=~s/\_/\./;
   
       # Local refs to the hashes we're going to look at:
   
       my $useropt   = $self->{USER_OPT};
       my $courseopt = $self->{COURSE_OPT};
   
       # 
       # We want the course level stuff from the way
       # parmval_real operates 
       # TODO: Factor some of this stuff out of
       # both parmval_real and here
       #
       my $courselevel = $cid . '.' .  $what;
       my $grplevel    = $cid . '.[' . $cgroup   . ']' . $what;
       my $seclevel    = $cid . '.[' . $csec     . ']' . $what;
   
   
       # Try for the user's course level option:
   
       if ($uname and defined($useropt)) {
    if (defined($$useropt{$courselevel})) {
       return $$useropt{$courselevel};
    }
       }
       # Try for the group's course level option:
   
       if ($cgroup ne '' and defined($courseopt)) {
    if (defined($$courseopt{$grplevel})) {
       return $$courseopt{$grplevel};
    }
       }
   
       #  Try for section level parameters:
   
       if ($csec ne '' and defined($courseopt)) {
    if (defined($$courseopt{$seclevel})) {
       return $$courseopt{$seclevel};
    }
       }
       # Try for 'additional' course parameters:
   
       if (defined($courseopt)) {
    if (defined($$courseopt{$courselevel})) {
       return $$courseopt{$courselevel};
    }
       }
       return undef;
   
   }
   
   
 =pod  =pod
   
 =item * B<getResourceByUrl>(url,multiple):  =item * B<getResourceByUrl>(url,multiple):
Line 2684  resource appears multiple times in the c Line 3397  resource appears multiple times in the c
 will be returned (useful for maps), unless the multiple parameter has  will be returned (useful for maps), unless the multiple parameter has
 been included, in which case all instances are returned in an array.  been included, in which case all instances are returned in an array.
   
 =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall):  =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall, noblockcheck):
   
 The map is a specification of a map to retreive the resources from,  The map is a specification of a map to retreive the resources from,
 either as a url or as an object. The filterFunc is a reference to a  either as a url or as an object. The filterFunc is a reference to a
Line 2693  true if the resource should be included, Line 3406  true if the resource should be included,
 be. If recursive is true, the map will be recursively examined,  be. If recursive is true, the map will be recursively examined,
 otherwise it will not be. If bailout is true, the function will return  otherwise it will not be. If bailout is true, the function will return
 as soon as it finds a resource, if false it will finish. If showall is  as soon as it finds a resource, if false it will finish. If showall is
 true it will not hide maps that contain nothing but one other map. By  true it will not hide maps that contain nothing but one other map. The 
 default, the map is the top-level map of the course, filterFunc is a  noblockcheck arg is propagated to become the sixth arg in the call to
 function that always returns 1, recursive is true, bailout is false,  lonnet::allowed when checking a resource's availability during collection
 showall is false. The resources will be returned in a list containing  of resources using the iterator. noblockcheck needs to be true if 
 the resource objects for the corresponding resources, with B<no  retrieveResources() was called by a routine that itself was called by 
 structure information> in the list; regardless of branching,  lonnet::allowed, in order to avoid recursion.  By default the map  
 recursion, etc., it will be a flat list.  is the top-level map of the course, filterFunc is a function that 
   always returns 1, recursive is true, bailout is false, showall is
   false. The resources will be returned in a list containing the
   resource objects for the corresponding resources, with B<no structure 
   information> in the list; regardless of branching, recursion, etc.,
   it will be a flat list.
   
 Thus, this is suitable for cases where you don't want the structure,  Thus, this is suitable for cases where you don't want the structure,
 just a list of all resources. It is also suitable for finding out how  just a list of all resources. It is also suitable for finding out how
Line 2766  sub retrieveResources { Line 3484  sub retrieveResources {
     my $bailout = shift;      my $bailout = shift;
     if (!defined($bailout)) { $bailout = 0; }      if (!defined($bailout)) { $bailout = 0; }
     my $showall = shift;      my $showall = shift;
       my $noblockcheck = shift;
     # Create the necessary iterator.      # Create the necessary iterator.
     if (!ref($map)) { # assume it's a url of a map.      if (!ref($map)) { # assume it's a url of a map.
         $map = $self->getResourceByUrl($map);          $map = $self->getResourceByUrl($map);
Line 2795  sub retrieveResources { Line 3514  sub retrieveResources {
     # Run down the iterator and collect the resources.      # Run down the iterator and collect the resources.
     my $curRes;      my $curRes;
   
     while ($curRes = $it->next()) {      while ($curRes = $it->next(undef,$noblockcheck)) {
         if (ref($curRes)) {          if (ref($curRes)) {
             if (!&$filterFunc($curRes)) {              if (!&$filterFunc($curRes)) {
                 next;                  next;
Line 2829  sub usedVersion { Line 3548  sub usedVersion {
     return $self->navhash("version_$linkurl");      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;  1;
   
 package Apache::lonnavmaps::iterator;  package Apache::lonnavmaps::iterator;
Line 2859  getIterator behaves as follows: Line 3643  getIterator behaves as follows:
   
 =over 4  =over 4
   
 =item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap):  =item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap, $deeplinklisted):
   
 All parameters are optional. firstResource is a resource reference  All parameters are optional. firstResource is a resource reference
 corresponding to where the iterator should start. It defaults to  corresponding to where the iterator should start. It defaults to
Line 2876  that is not just a single, 'redirecting' Line 3660  that is not just a single, 'redirecting'
 will return all information, starting with the top-level map,  will return all information, starting with the top-level map,
 regardless of content. returnTopMap, if true (default false), will  regardless of content. returnTopMap, if true (default false), will
 cause the iterator to return the top-level map object (resource 0.0)  cause the iterator to return the top-level map object (resource 0.0)
 before anything else.  before anything else. deeplinklisted if true (default false), will
   check "listed" status of a resource with a deeplink, and unless "absent"
   will exclude deeplink checking when retrieving the browsePriv from
   lonnet::allowed().
   
 Thus, by default, only top-level resources will be shown. Change the  Thus, by default, only top-level resources will be shown. Change the
 condition to a 1 without changing the hash, and all resources will be  condition to a 1 without changing the hash, and all resources will be
Line 2946  Note that inside of the loop, it's frequ Line 3733  Note that inside of the loop, it's frequ
 resource objects will be references, and any non-references will   resource objects will be references, and any non-references will 
 be the tokens described above.  be the tokens described above.
   
 Also note there is some old code floating around that trys to track  The next() routine can take two (optional) arguments:
   closeAllPages - if true will not recurse down a .page
   noblockcheck - passed to browsePriv() for passing as sixth arg to
   call to lonnet::allowed. This needs to be set if retrieveResources
   was already called from another routine called within lonnet::allowed, 
   so as to prevent recursion.
   
   Also note there is some old code floating around that tries to track
 the depth of the iterator to see when it's done; do not copy that   the depth of the iterator to see when it's done; do not copy that 
 code. It is difficult to get right and harder to understand than  code. It is difficult to get right and harder to understand than
 this. They should be migrated to this new style.  this. They should be migrated to this new style.
Line 3006  sub new { Line 3800  sub new {
     # have we done that yet?      # have we done that yet?
     $self->{HAVE_RETURNED_0} = 0;      $self->{HAVE_RETURNED_0} = 0;
   
       # Do we want to check the "listed" status for a resource for which
       # deeplinking applies.
       $self->{DEEPLINKLISTED} = shift;
   
     # Now, we need to pre-process the map, by walking forward and backward      # Now, we need to pre-process the map, by walking forward and backward
     # over the parts of the map we're going to look at.      # over the parts of the map we're going to look at.
   
Line 3093  sub new { Line 3891  sub new {
     if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) {       if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
         my $firstResource = $resource->map_start();          my $firstResource = $resource->map_start();
         my $finishResource = $resource->map_finish();          my $finishResource = $resource->map_finish();
  my $result;   return Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
  return   $finishResource, $self->{FILTER},
     Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,   $self->{ALREADY_SEEN}, 
       $finishResource, $self->{FILTER},   $self->{CONDITION},
       $self->{ALREADY_SEEN},    $self->{FORCE_TOP},
       $self->{CONDITION},                                                   undef,$self->{DEEPLINKLISTED});
       $self->{FORCE_TOP});  
     }      }
   
     # Set up some bookkeeping information.      # Set up some bookkeeping information.
Line 3124  sub new { Line 3921  sub new {
 sub next {  sub next {
     my $self = shift;      my $self = shift;
     my $closeAllPages=shift;      my $closeAllPages=shift;
       my $noblockcheck = shift;
     if ($self->{FINISHED}) {      if ($self->{FINISHED}) {
  return END_ITERATOR();   return END_ITERATOR();
     }      }
Line 3258  sub next { Line 4056  sub next {
     # That ends the main iterator logic. Now, do we want to recurse      # That ends the main iterator logic. Now, do we want to recurse
     # down this map (if this resource is a map)?      # down this map (if this resource is a map)?
     if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&      if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
         (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {          (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION}) &&
           ($env{'request.role.adv'} || !$self->{HERE}->randomout())) {
         $self->{RECURSIVE_ITERATOR_FLAG} = 1;          $self->{RECURSIVE_ITERATOR_FLAG} = 1;
         my $firstResource = $self->{HERE}->map_start();          my $firstResource = $self->{HERE}->map_start();
         my $finishResource = $self->{HERE}->map_finish();          my $finishResource = $self->{HERE}->map_finish();
Line 3267  sub next { Line 4066  sub next {
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
                                               $self->{ALREADY_SEEN},                                                $self->{ALREADY_SEEN},
       $self->{CONDITION},        $self->{CONDITION},
       $self->{FORCE_TOP});        $self->{FORCE_TOP},
                                                 undef,$self->{DEEPLINKLISTED});
     }      }
   
     # If this is a blank resource, don't actually return it.      # If this is a blank resource, don't actually return it.
     # Should you ever find you need it, make sure to add an option to the code      # Should you ever find you need it, make sure to add an option to the code
     #  that you can use; other things depend on this behavior.      #  that you can use; other things depend on this behavior.
     my $browsePriv = $self->{HERE}->browsePriv();      my $browsePriv = $self->{HERE}->browsePriv($noblockcheck,$self->{DEEPLINKLISTED});
     if (!$self->{HERE}->src() ||       if (!$self->{HERE}->src() || 
         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {          (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
         return $self->next($closeAllPages);          return $self->next($closeAllPages);
Line 3605  sub new { Line 4405  sub new {
     # about this resource in. Not used by the resource object      # about this resource in. Not used by the resource object
     # directly.      # directly.
     $self->{DATA} = {};      $self->{DATA} = {};
          
     bless($self);      bless($self);
           
       # This is a speed optimization, to avoid calling symb() too often.
       $self->{SYMB} = $self->symb();
   
     return $self;      return $self;
 }  }
   
Line 3698  sub from { my $self=shift; return $self- Line 4501  sub from { my $self=shift; return $self-
 sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }  sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
 sub kind { my $self=shift; return $self->navHash("kind_", 1); }  sub kind { my $self=shift; return $self->navHash("kind_", 1); }
 sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }  sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
   sub deeplinkout { my $self=shift; return $self->navHash("deeplinkout_", 1); }
 sub randompick {   sub randompick { 
     my $self = shift;      my $self = shift;
     my $randompick = $self->parmval('randompick');      my $randompick = $self->parmval('randompick');
Line 3719  sub src { Line 4523  sub src {
 }  }
 sub shown_symb {  sub shown_symb {
     my $self=shift;      my $self=shift;
     if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());}      if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->{SYMB});}
     return $self->symb();      return $self->{SYMB};
 }  }
 sub id {  sub id {
     my $self=shift;      my $self=shift;
Line 3733  sub enclosing_map_src { Line 4537  sub enclosing_map_src {
 }  }
 sub symb {  sub symb {
     my $self=shift;      my $self=shift;
       if (defined $self->{SYMB}) { return $self->{SYMB}; }
     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;      (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
     my $symbSrc = &Apache::lonnet::declutter($self->src());      my $symbSrc = &Apache::lonnet::declutter($self->src());
     my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first))       my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) 
Line 3741  sub symb { Line 4546  sub symb {
 }  }
 sub wrap_symb {  sub wrap_symb {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->wrap_symb($self->symb());      return $self->{NAV_MAP}->wrap_symb($self->{SYMB});
 }  }
 sub title {   sub title { 
     my $self=shift;       my $self=shift; 
Line 3865  sub is_problem { Line 4670  sub is_problem {
     }      }
     return 0;      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'  #  The has below is the set of status that are considered 'incomplete'
 #  #
Line 3952  sub is_task { Line 4770  sub is_task {
   
 sub is_empty_sequence {  sub is_empty_sequence {
     my $self=shift;      my $self=shift;
     my $src = $self->src();  
     return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());      return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
 }  }
   
Line 3964  sub parmval { Line 4781  sub parmval {
     if (!defined($part)) {       if (!defined($part)) { 
         $part = '0';           $part = '0'; 
     }      }
     return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb());      return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->{SYMB});
 }  }
   
 =pod  =pod
Line 3997  resource of the map. Line 4814  resource of the map.
   
 Returns a string with the type of the map in it.  Returns a string with the type of the map in it.
   
 =item *B<map_hierarchy>:  =item * B<map_hierarchy>:
   
 Returns a string with a comma-separated ordered list of map_pc IDs  Returns a string with a comma-separated ordered list of map_pc IDs
 for the hierarchy of maps containing a map, with the top level  for the hierarchy of maps containing a map, with the top level
 map first, then descending to deeper levels, with the enclosing map last.  map first, then descending to deeper levels, with the enclosing map last.
   
   =item * B<map_breadcrumbs>:
   
   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  =back
   
 =cut  =cut
Line 4038  sub map_hierarchy { Line 4861  sub map_hierarchy {
     my $pc = $self->map_pc();      my $pc = $self->map_pc();
     return $self->navHash("map_hierarchy_$pc", 0);      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  # Property queries
Line 4068  their code.) Line 4896  their code.)
   
 =over 4  =over 4
   
   
 =item * B<printable>  =item * B<printable>
   
 returns true if the current date is such that the   returns true if the current date is such that the 
 specified resource part is printable.  specified resource part is printable.
   
   
 =item * B<resprintable>  =item * B<resprintable>
   
 Returns true if all parts in the resource are printable making the  Returns true if all parts in the resource are printable making the
Line 4131  Get the weight for the problem. Line 4961  Get the weight for the problem.
   
 =cut  =cut
   
 sub printable {  
   
     my ($self, $part) = @_;  
   
     # Get the print open/close dates for the resource.  
   
     my $start = $self->parmval("printstartdate", $part);  sub printable {
     my $end   = $self->parmval("printenddate", $part);  
       my ($self, $part) = @_;
   
     #  The following cases apply:      #  The following cases apply:
     #  - No dates set: Printable.      #  - If a start date is not set, it is replaced by the open date.
       #  - Ditto for start/open replaced by content open.
       #  - If neither start nor printdates are set the part is printable.
     #  - Start date set but no end date: Printable if now >= start date.      #  - Start date set but no end date: Printable if now >= start date.
     #  - End date set but no start date: Printable if now <= end date.      #  - End date set but no start date: Printable if now <= end date.
     #  - both defined: printable if start <= now <= end      #  - both defined: printable if start <= now <= end
     #      #
   
       # Get the print open/close dates for the resource.
   
       my $start = $self->parmval("printstartdate", $part);
       my $end   = $self->parmval("printenddate", $part);
   
       if (!$start) {
    $start = $self->parmval("opendate", $part);
       }
       if (!$start) {
    $start = $self->parmval("contentopen", $part);
       }
   
   
     my $now  = time();      my $now  = time();
   
   
     my $startok = 1;      my $startok = 1;
     my $endok   = 1;      my $endok   = 1;
   
Line 4168  sub resprintable { Line 5013  sub resprintable {
     my $partsref = $self->parts();      my $partsref = $self->parts();
     my @parts    = @$partsref;      my @parts    = @$partsref;
   
     if ((!defined(@parts)) || (scalar(@parts) == 0)) {      if (!@parts) {
  return $self->printable(0);   return $self->printable(0);
     } else {      } else {
  foreach my $part  (@parts) {   foreach my $part  (@parts) {
Line 4200  sub awarded { Line 5045  sub awarded {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      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 {  sub taskversion {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      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 {  sub taskstatus {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      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 {  sub solved {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     $self->{NAV_MAP}->get_user_data();      $self->{NAV_MAP}->get_user_data();
     if (!defined($part)) { $part = '0'; }      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 {  sub checkedin {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
Line 4226  sub checkedin { Line 5071  sub checkedin {
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     if ($self->is_task()) {      if ($self->is_task()) {
         my $version = $self->taskversion($part);          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 {      } 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  # this should work exactly like the copy in lonhomework.pm
Line 4245  sub duedate { Line 5090  sub duedate {
     my $date;      my $date;
     my @interval=$self->parmval("interval", $part);      my @interval=$self->parmval("interval", $part);
     my $due_date=$self->parmval("duedate", $part);      my $due_date=$self->parmval("duedate", $part);
     if ($interval[0] =~ /\d+/) {      if ($interval[0] =~ /^(\d+)/) {
        my $first_access=&Apache::lonnet::get_first_access($interval[1],          my $timelimit = $1;
                                                           $self->symb);          my $first_access=&Apache::lonnet::get_first_access($interval[1],
                                                              $self->{SYMB});
  if (defined($first_access)) {   if (defined($first_access)) {
            my $interval = $first_access+$interval[0];              my $interval = $first_access+$timelimit;
     $date = (!$due_date || $interval < $due_date) ? $interval       $date = (!$due_date || $interval < $due_date) ? $interval 
                                                           : $due_date;                                                            : $due_date;
  } else {   } else {
Line 4320  sub weight { Line 5166  sub weight {
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',      my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
                                 $self->symb(), $self->{DOMAIN},                                  $self->{SYMB}, $self->{DOMAIN},
                                 $self->{USERNAME},                                  $self->{USERNAME},
                                 $env{'request.course.sec'});                                  $env{'request.course.sec'});
     return $weight;      return $weight;
Line 4329  sub part_display { Line 5175  sub part_display {
     my $self= shift(); my $partID = shift();      my $self= shift(); my $partID = shift();
     if (! defined($partID)) { $partID = '0'; }      if (! defined($partID)) { $partID = '0'; }
     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',      my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
                                      $self->symb);                                       $self->{SYMB});
     if (! defined($display) || $display eq '') {      if (! defined($display) || $display eq '') {
         $display = $partID;          $display = $partID;
     }      }
Line 4343  sub slot_control { Line 5189  sub slot_control {
     my $available = $self->parmval("available", $part);       my $available = $self->parmval("available", $part); 
     return ($useslots,$availablestudent,$available);      return ($useslots,$availablestudent,$available);
 }  }
   sub deeplink {
       my ($self,$caller,$action) = @_;
       my $deeplink = $self->parmval("deeplink");
       if ($deeplink) {
           my ($state,$others,$listed,$scope) = split(/,/,$deeplink);
           if ($action eq 'getlisted') {
               return $listed;
           }
           if ($env{'request.deeplink.login'}) {
               my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
               my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
               my $deeplink_symb = &Apache::loncommon::deeplink_login_symb($cnum,$cdom);
               if ($deeplink_symb) {
                   my ($loginmap,$mapname);
                   if ($deeplink_symb =~ /\.(page|sequence)$/) {
                       $mapname = $self->enclosing_map_src();
                       $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[2]);
                       return if ($mapname eq $loginmap);
                   } else {
                       return if ($deeplink_symb eq $self->symb());
                       if (($scope eq 'map') || ($scope eq 'rec')) {
                           $mapname = $self->enclosing_map_src();
                           $loginmap = &Apache::lonnet::clutter((&Apache::lonnet::decode_symb($deeplink_symb))[0]);
                           return if ($mapname eq $loginmap);
                       }
                   }
                   if ($scope eq 'rec') {
                       my $map_pc = $self->navHash('map_pc_'.$mapname);
                       my @recurseup = split(/,/,$self->navHash('map_hierarchy_'.$map_pc));
                       my $login_pc = $self->navHash('map_pc_'.$loginmap);
                       return if (grep(/^\Q$login_pc\E$/,@recurseup));
                   }
               }
           }
           unless (($caller eq 'sequence') || ($state eq 'both')) {
               return $listed;
           }
       }
       return;
   }
   
 # Multiple things need this  # Multiple things need this
 sub getReturnHash {  sub getReturnHash {
     my $self = shift;      my $self = shift;
           
     if (!defined($self->{RETURN_HASH})) {      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;          #$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}};
     }      }
 }         }       
   
Line 4414  and use the link as appropriate. Line 5306  and use the link as appropriate.
   
 sub hasDiscussion {  sub hasDiscussion {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->hasDiscussion($self->symb());      return $self->{NAV_MAP}->hasDiscussion($self->{SYMB});
 }  }
   
 sub last_post_time {  sub last_post_time {
     my $self = shift;      my $self = shift;
     return $self->{NAV_MAP}->last_post_time($self->symb());      return $self->{NAV_MAP}->last_post_time($self->{SYMB});
 }  }
   
 sub discussion_info {  sub discussion_info {
     my ($self,$filter) = @_;      my ($self,$filter) = @_;
     return $self->{NAV_MAP}->discussion_info($self->symb(),$filter);      return $self->{NAV_MAP}->discussion_info($self->{SYMB},$filter);
 }  }
   
 sub getFeedback {  sub getFeedback {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
     my $symb = $self->symb();      my $symb = $self->{SYMB};
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getFeedback($symb,$source);      return $self->{NAV_MAP}->getFeedback($symb,$source);
 }  }
Line 4438  sub getFeedback { Line 5330  sub getFeedback {
 sub getErrors {  sub getErrors {
     my $self = shift;      my $self = shift;
     my $source = $self->src();      my $source = $self->src();
     my $symb = $self->symb();      my $symb = $self->{SYMB};
     if ($source =~ /^\/res\//) { $source = substr $source, 5; }      if ($source =~ /^\/res\//) { $source = substr $source, 5; }
     return $self->{NAV_MAP}->getErrors($symb,$source);      return $self->{NAV_MAP}->getErrors($symb,$source);
 }  }
Line 4491  sub parts { Line 5383  sub parts {
     my $self = shift;      my $self = shift;
   
     if ($self->ext) { return []; }      if ($self->ext) { return []; }
       if (($self->is_tool()) &&
           ($self->is_gradable())) { return ['0']; }
   
     $self->extractParts();      $self->extractParts();
     return $self->{PARTS};      return $self->{PARTS};
Line 4581  sub extractParts { Line 5475  sub extractParts {
     my %parts;      my %parts;
   
     # Retrieve part count, if this is a problem      # 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 $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
         my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');          my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
   
  if ($partorder) {   if ($partorder) {
     my @parts;      my @parts;
     for my $part (split (/,/,$partorder)) {      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;      push @parts, $part;
     $parts{$part} = 1;      $parts{$part} = 1;
  }   }
Line 4606  sub extractParts { Line 5500  sub extractParts {
     my $part = $1;      my $part = $1;
     # This floods the logs if it blows up      # This floods the logs if it blows up
     if (defined($parts{$part})) {      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.      # 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;   $parts{$part} = 1;
     }      }
  }   }
     }      }
     my @sortedParts = sort keys %parts;      my @sortedParts = sort(keys(%parts));
     $self->{PARTS} = \@sortedParts;      $self->{PARTS} = \@sortedParts;
         }          }
                   
Line 4637  sub extractParts { Line 5531  sub extractParts {
         # So we have to use our knowlege of part names to figure out           # So we have to use our knowlege of part names to figure out 
         # where the part names begin and end, and even then, it is possible          # where the part names begin and end, and even then, it is possible
         # to construct ambiguous situations.          # to construct ambiguous situations.
         foreach my $data (split /,/, $metadata) {          foreach my $data (split(/,/, $metadata)) {
             if ($data =~ /^([a-zA-Z]+)response_(.*)/              if ($data =~ /^([a-zA-Z]+)response_(.*)/
  || $data =~ /^(Task)_(.*)/) {   || $data =~ /^(Task)_(.*)/) {
                 my $responseType = $1;                  my $responseType = $1;
                 my $partStuff = $2;                  my $partStuff = $2;
                 my $partIdSoFar = '';                  my $partIdSoFar = '';
                 my @partChunks = split /_/, $partStuff;                  my @partChunks = split(/_/, $partStuff);
                 my $i = 0;                  my $i = 0;
                 for ($i = 0; $i < scalar(@partChunks); $i++) {                  for ($i = 0; $i < scalar(@partChunks); $i++) {
                     if ($partIdSoFar) { $partIdSoFar .= '_'; }                      if ($partIdSoFar) { $partIdSoFar .= '_'; }
Line 4865  Attempted, and not yet graded. Line 5759  Attempted, and not yet graded.
   
 Attempted, and credit received for attempt (survey and anonymous survey only).  Attempted, and credit received for attempt (survey and anonymous survey only).
   
   =item * B<INCORRECT_BY_PASSBACK>:
   
   Attempted, but wrong for LTI Tool Provider by passback of grade
   
   =item * B<CORRECT_BY_PASSBACK>:
   
   Correct for LTI Tool Provider by passback of grade
   
 =back  =back
   
 =cut  =cut
Line 4877  sub CORRECT_BY_OVERRIDE   { return 14; } Line 5779  sub CORRECT_BY_OVERRIDE   { return 14; }
 sub EXCUSED               { return 15; }  sub EXCUSED               { return 15; }
 sub ATTEMPTED             { return 16; }  sub ATTEMPTED             { return 16; }
 sub CREDIT_ATTEMPTED      { return 17; }  sub CREDIT_ATTEMPTED      { return 17; }
   sub INCORRECT_BY_PASSBACK { return 18; }
   sub CORRECT_BY_PASSBACK   { return 19; }
   
 sub getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
Line 4891  sub getCompletionStatus { Line 5795  sub getCompletionStatus {
     if ($status eq 'correct_by_override') {      if ($status eq 'correct_by_override') {
  return $self->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_attempted') {return $self->INCORRECT; }
     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }      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 'excused') {return $self->EXCUSED; }
     if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }      if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
     if ($status eq 'credit_attempted') {      if ($status eq 'credit_attempted') {
Line 5046  sub status { Line 5954  sub status {
   
     # There are a few whole rows we can dispose of:      # There are a few whole rows we can dispose of:
     if ($completionStatus == CORRECT ||      if ($completionStatus == CORRECT ||
         $completionStatus == CORRECT_BY_OVERRIDE ) {          $completionStatus == CORRECT_BY_OVERRIDE ||
           $completionStatus == CORRECT_BY_PASSBACK ) {
  if ( $suppressFeedback ) { return ANSWER_SUBMITTED }   if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
  my $awarded=$self->awarded($part);   my $awarded=$self->awarded($part);
  if ($awarded < 1 && $awarded > 0) {   if ($awarded < 1 && $awarded > 0) {
Line 5059  sub status { Line 5968  sub status {
   
     # If it's WRONG... and not open      # If it's WRONG... and not open
     if ( ($completionStatus == INCORRECT ||       if ( ($completionStatus == INCORRECT || 
   $completionStatus == INCORRECT_BY_OVERRIDE)    $completionStatus == INCORRECT_BY_OVERRIDE ||
     $completionStatus == INCORRECT_BY_PASSBACK)
  && (!$self->opendate($part) ||  $self->opendate($part) > time()) ) {   && (!$self->opendate($part) ||  $self->opendate($part) > time()) ) {
  return INCORRECT;   return INCORRECT;
     }      }
Line 5101  sub status { Line 6011  sub status {
     }      }
   
     # If it's WRONG...      # 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:          # and there are TRIES LEFT:
         if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {          if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
             return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;              return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
Line 5116  sub status { Line 6027  sub status {
 sub check_for_slot {  sub check_for_slot {
     my $self = shift;      my $self = shift;
     my $part = shift;      my $part = shift;
     my $symb = $self->symb();      my $symb = $self->{SYMB};
     my ($use_slots,$available,$availablestudent) = $self->slot_control($part);      my ($use_slots,$available,$availablestudent) = $self->slot_control($part);
     if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {      if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {
         my @slots = (split(/:/,$availablestudent),split(/:/,$available));          my @slots = (split(/:/,$availablestudent),split(/:/,$available));
Line 5125  sub check_for_slot { Line 6036  sub check_for_slot {
         my $cnum=$env{'course.'.$cid.'.num'};          my $cnum=$env{'course.'.$cid.'.num'};
         my $now = time;          my $now = time;
         my $num_usable_slots = 0;          my $num_usable_slots = 0;
           my ($checkedin,$checkedinslot,%consumed_uniq,%slots);
         if (@slots > 0) {          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)) {              if (&Apache::lonnet::error(%slots)) {
                 return (UNKNOWN);                  return (UNKNOWN);
             }              }
             my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');              my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
             my ($checkedin,$checkedinslot);  
             foreach my $slot_name (@sorted_slots) {              foreach my $slot_name (@sorted_slots) {
                 next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));                  next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
                 my $end = $slots{$slot_name}->{'endtime'};                  my $end = $slots{$slot_name}->{'endtime'};
Line 5144  sub check_for_slot { Line 6055  sub check_for_slot {
                         } else {                          } else {
                             if ($ip ne '') {                              if ($ip ne '') {
                                 if (!&Apache::loncommon::check_ip_acc($ip)) {                                  if (!&Apache::loncommon::check_ip_acc($ip)) {
                                     return (RESERVED_LOCATION,$ip,$slot_name);                                      return (RESERVED_LOCATION,$end,$slot_name);
                                 }                                  }
                             }                              }
                             my @proctors;                              my @proctors;
Line 5155  sub check_for_slot { Line 6066  sub check_for_slot {
                                 ($checkedin,$checkedinslot) = $self->checkedin();                                  ($checkedin,$checkedinslot) = $self->checkedin();
                                 unless ((grep(/^\Q$checkedin\E/,@proctors)) &&                                  unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
                                         ($checkedinslot eq $slot_name)) {                                          ($checkedinslot eq $slot_name)) {
                                     return (NEEDS_CHECKIN,undef,$slot_name);                                       return (NEEDS_CHECKIN,$end,$slot_name); 
                                 }                                  }
                             }                              }
                             return (RESERVED,$end,$slot_name);                              return (RESERVED,$end,$slot_name);
Line 5165  sub check_for_slot { Line 6076  sub check_for_slot {
                     $num_usable_slots ++;                      $num_usable_slots ++;
                 }                  }
             }              }
             my ($is_correct,$got_grade);              my ($is_correct,$wait_for_grade);
             if ($self->is_task()) {              if ($self->is_task()) {
                 my $taskstatus = $self->taskstatus();                  my $taskstatus = $self->taskstatus();
                 $is_correct = (($taskstatus eq 'pass') ||                   $is_correct = (($taskstatus eq 'pass') || 
                                ($self->solved() =~ /^correct_/));                                 ($self->solved() =~ /^correct_/));
                 $got_grade = ($taskstatus =~ /^(?:pass|fail)$/);                  unless ($taskstatus =~ /^(?:pass|fail)$/) {
                       $wait_for_grade = 1;
                   }
             } else {              } else {
                 $got_grade = 1;                  unless ($self->completable()) {
                 $is_correct = ($self->solved() =~ /^correct_/);                         $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();              ($checkedin,$checkedinslot) = $self->checkedin();
             if ($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);                      return (WAITING_FOR_GRADE);
                 } elsif ($is_correct) {                  } elsif ($is_correct) {
                     return (CORRECT);                       return (CORRECT); 
Line 5190  sub check_for_slot { Line 6112  sub check_for_slot {
         my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},          my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},
                                                                $env{'user.domain'});                                                                 $env{'user.domain'});
         if (ref($reservable) eq 'HASH') {          if (ref($reservable) eq 'HASH') {
               my ($map) = &Apache::lonnet::decode_symb($symb);
             if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {              if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
                 foreach my $slot (reverse (@{$reservable->{'now_order'}})) {                  foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
                     if (($reservable->{'now'}{$slot}{'symb'} eq '') ||                      my $canuse;
                         ($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) {
                               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'});                          return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
                     }                      }
                 }                  }
             }              }
             if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {              if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
                 foreach my $slot (@{$reservable->{'future_order'}}) {                  foreach my $slot (@{$reservable->{'future_order'}}) {
                     if (($reservable->{'future'}{$slot}{'symb'} eq '') ||                      my $canuse;
                         ($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) {
                           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'});                          return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
                     }                      }
                 }                  }
Line 5352  sub completable { Line 6339  sub completable {
   
 =pod  =pod
   
   B<Answerable>
   
   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  =head2 Resource/Nav Map Navigation
   
 =over 4  =over 4
Line 5387  sub getPrevious { Line 6407  sub getPrevious {
     my $self = shift;      my $self = shift;
     my @branches;      my @branches;
     my $from = $self->from();      my $from = $self->from();
     foreach my $branch ( split /,/, $from) {      foreach my $branch ( split(/,/, $from)) {
         my $choice = $self->{NAV_MAP}->getById($branch);          my $choice = $self->{NAV_MAP}->getById($branch);
         my $prev = $choice->comesfrom();          my $prev = $choice->comesfrom();
         $prev = $self->{NAV_MAP}->getById($prev);          $prev = $self->{NAV_MAP}->getById($prev);
Line 5399  sub getPrevious { Line 6419  sub getPrevious {
   
 sub browsePriv {  sub browsePriv {
     my $self = shift;      my $self = shift;
       my $noblockcheck = shift;
       my $deeplinklisted = shift;
     if (defined($self->{BROWSE_PRIV})) {      if (defined($self->{BROWSE_PRIV})) {
         return $self->{BROWSE_PRIV};          return $self->{BROWSE_PRIV};
     }      }
       my ($nodeeplinkcheck,$nodeeplinkout);
       if ($deeplinklisted) {
           my $deeplink = $self->deeplink(undef,'getlisted');
           if (($deeplink) && ($deeplink ne 'absent')) {
               $nodeeplinkcheck = 1;
           }
           $nodeeplinkout = 1;
       }
     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),      $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
     $self->symb());      $self->{SYMB},undef,
                                                       undef,$noblockcheck,
                                                       undef,$nodeeplinkcheck,
                                                       $nodeeplinkout);
 }  }
   
 =pod  =pod

Removed from v.1.484  
changed lines
  Added in v.1.554


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>