Diff for /loncom/interface/lonnavmaps.pm between versions 1.406 and 1.475

version 1.406, 2007/11/20 17:53:46 version 1.475, 2011/12/21 20:21:49
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 27 Line 28
 #  #
 ###  ###
   
 package Apache::lonnavmaps;  
   
 use strict;  
 use GDBM_File;  
 use Apache::loncommon();  
 use Apache::lonenc();  
 use Apache::lonlocal;  
 use Apache::lonnet;  
 use POSIX qw (floor strftime);  
 use Time::HiRes qw( gettimeofday tv_interval );  
 use LONCAPA;  
 use DateTime();  
   
 # symbolic constants  
 sub SYMB { return 1; }  
 sub URL { return 2; }  
 sub NOTHING { return 3; }  
   
 # Some data  
   
 my $resObj = "Apache::lonnavmaps::resource";  
   
 # Keep these mappings in sync with lonquickgrades, which uses the colors  
 # instead of the icons.  
 my %statusIconMap =   
     (  
      $resObj->CLOSED       => '',  
      $resObj->OPEN         => 'navmap.open.gif',  
      $resObj->CORRECT      => 'navmap.correct.gif',  
      $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',  
      $resObj->INCORRECT    => 'navmap.wrong.gif',  
      $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',  
      $resObj->ERROR        => ''  
      );  
   
 my %iconAltTags =   
     ( 'navmap.correct.gif' => 'Correct',  
       'navmap.wrong.gif'   => 'Incorrect',  
       'navmap.open.gif'    => 'Open' );  
   
 # Defines a status->color mapping, null string means don't color  
 my %colormap =   
     ( $resObj->NETWORK_FAILURE        => '',  
       $resObj->CORRECT                => '',  
       $resObj->EXCUSED                => '#3333FF',  
       $resObj->PAST_DUE_ANSWER_LATER  => '',  
       $resObj->PAST_DUE_NO_ANSWER     => '',  
       $resObj->ANSWER_OPEN            => '#006600',  
       $resObj->OPEN_LATER             => '',  
       $resObj->TRIES_LEFT             => '',  
       $resObj->INCORRECT              => '',  
       $resObj->OPEN                   => '',  
       $resObj->NOTHING_SET            => '',  
       $resObj->ATTEMPTED              => '',  
       $resObj->ANSWER_SUBMITTED       => '',  
       $resObj->PARTIALLY_CORRECT      => '#006600'  
       );  
 # And a special case in the nav map; what to do when the assignment  
 # is not yet done and due in less than 24 hours  
 my $hurryUpColor = "#FF0000";  
   
 sub close {  
     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }  
     return(<<ENDCLOSE);  
 <script type="text/javascript">  
 window.status='Accessing Nav Control';  
 menu=window.open("/adm/rat/empty.html","loncapanav",  
                  "height=600,width=400,scrollbars=1");  
 window.status='Closing Nav Control';  
 menu.close();  
 window.status='Done.';  
 </script>  
 ENDCLOSE  
 }  
   
 sub update {  
     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }  
     if (!$env{'request.course.id'}) { return ''; }  
     if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }  
     return(<<ENDUPDATE);  
 <form name="navform"></form>  
 <script type="text/javascript">  
 this.document.navform.action='/adm/navmaps#curloc';  
 this.document.navform.target='loncapanav';  
 this.document.navform.submit();  
 </script>  
 ENDUPDATE  
 }  
   
 # Convenience functions: Returns a string that adds or subtracts  
 # the second argument from the first hash, appropriate for the   
 # query string that determines which folders to recurse on  
 sub addToFilter {  
     my $hashIn = shift;  
     my $addition = shift;  
     my %hash = %$hashIn;  
     $hash{$addition} = 1;  
   
     return join (",", keys(%hash));  
 }  
   
 sub removeFromFilter {  
     my $hashIn = shift;  
     my $subtraction = shift;  
     my %hash = %$hashIn;  
   
     delete $hash{$subtraction};  
     return join(",", keys(%hash));  
 }  
   
 # Convenience function: Given a stack returned from getStack on the iterator,  
 # return the correct src() value.  
 sub getLinkForResource {  
     my $stack = shift;  
     my $res;  
   
     # Check to see if there are any pages in the stack  
     foreach $res (@$stack) {  
         if (defined($res)) {  
     my $anchor;  
     if ($res->is_page()) {  
  foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }  
  $anchor=&escape($anchor->shown_symb());  
  return ($res->link(),$res->shown_symb(),$anchor);  
     }  
             # in case folder was skipped over as "only sequence"  
     my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());  
     if ($map=~/\.page$/) {  
  my $url=&Apache::lonnet::clutter($map);  
  $anchor=&escape($src->shown_symb());  
  return ($url,$res->shown_symb(),$anchor);  
     }  
         }  
     }  
   
     # Failing that, return the src of the last resource that is defined  
     # (when we first recurse on a map, it puts an undefined resource  
     # on the bottom because $self->{HERE} isn't defined yet, and we  
     # want the src for the map anyhow)  
     foreach my $item (@$stack) {  
         if (defined($item)) { $res = $item; }  
     }  
   
     if ($res) {  
  return ($res->link(),$res->shown_symb());  
     }  
     return;  
 }  
   
 # Convenience function: This separates the logic of how to create  
 # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",  
 # etc.) into a separate function. It takes a resource object as the  
 # first parameter, and the part number of the resource as the second.  
 # It's basically a big switch statement on the status of the resource.  
   
 sub getDescription {  
     my $res = shift;  
     my $part = shift;  
     my $status = $res->status($part);  
   
     if ($status == $res->NETWORK_FAILURE) {   
         return &mt("Having technical difficulties; please check status later");   
     }  
     if ($status == $res->NOTHING_SET) {  
         return &mt("Not currently assigned.");  
     }  
     if ($status == $res->OPEN_LATER) {  
         return "Open " . timeToHumanString($res->opendate($part),'start');  
     }  
     if ($status == $res->OPEN) {  
         if ($res->duedate($part)) {  
     if ($res->is_practice()) {  
  return &mt("Closes ")."  " .timeToHumanString($res->duedate($part),'start');  
     } else {  
  return &mt("Due")."  " .timeToHumanString($res->duedate($part),'end');  
     }  
         } else {  
             return &mt("Open, no due date");  
         }  
     }  
     if ($status == $res->PAST_DUE_ANSWER_LATER) {  
         return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start');  
     }  
     if ($status == $res->PAST_DUE_NO_ANSWER) {  
  if ($res->is_practice()) {  
     return &mt("Closed")." " . timeToHumanString($res->duedate($part),'start');  
  } else {  
     return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end');  
  }  
     }  
     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)  
  && $res->handgrade($part) ne 'yes') {  
         return &mt("Answer available");  
     }  
     if ($status == $res->EXCUSED) {  
         return &mt("Excused by instructor");  
     }  
     if ($status == $res->ATTEMPTED) {  
         return &mt("Answer submitted, not yet graded");  
     }  
     if ($status == $res->TRIES_LEFT) {  
         my $tries = $res->tries($part);  
         my $maxtries = $res->maxtries($part);  
         my $triesString = "";  
         if ($tries && $maxtries) {  
             $triesString = "<font size=\"-1\"><i>($tries of $maxtries tries used)</i></font>";  
             if ($maxtries > 1 && $maxtries - $tries == 1) {  
                 $triesString = "<b>$triesString</b>";  
             }  
         }  
         if ($res->duedate($part)) {  
             return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .  
                 " $triesString";  
         } else {  
             return &mt("No due date")." $triesString";  
         }  
     }  
     if ($status == $res->ANSWER_SUBMITTED) {  
         return &mt('Answer submitted');  
     }  
 }  
   
 # Convenience function, so others can use it: Is the problem due in less than  
 # 24 hours, and still can be done?  
   
 sub dueInLessThan24Hours {  
     my $res = shift;  
     my $part = shift;  
     my $status = $res->status($part);  
   
     return ($status == $res->OPEN() ||  
             $status == $res->TRIES_LEFT()) &&  
     $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&  
     $res->duedate($part) > time();  
 }  
   
 # Convenience function, so others can use it: Is there only one try remaining for the  
 # part, with more than one try to begin with, not due yet and still can be done?  
 sub lastTry {  
     my $res = shift;  
     my $part = shift;  
   
     my $tries = $res->tries($part);  
     my $maxtries = $res->maxtries($part);  
     return $tries && $maxtries && $maxtries > 1 &&  
         $maxtries - $tries == 1 && $res->duedate($part) &&  
         $res->duedate($part) > time();  
 }  
   
 # This puts a human-readable name on the env variable.  
   
 sub advancedUser {  
     return $env{'request.role.adv'};  
 }  
   
   
 # timeToHumanString takes a time number and converts it to a  
 # human-readable representation, meant to be used in the following  
 # manner:  
 # print "Due $timestring"  
 # print "Open $timestring"  
 # print "Answer available $timestring"  
 # Very, very, very, VERY English-only... goodness help a localizer on  
 # this func...  
   
   
 sub timeToHumanString {  
     my ($time,$type,$format) = @_;  
   
     # zero, '0' and blank are bad times  
     if (!$time) {  
         return &mt('never');  
     }  
     unless (&Apache::lonlocal::current_language()=~/^en/) {  
  return &Apache::lonlocal::locallocaltime($time);  
     }   
     my $now = time();  
   
     # Positive = future  
     my $delta = $time - $now;  
   
     my $minute = 60;  
     my $hour = 60 * $minute;  
     my $day = 24 * $hour;  
     my $week = 7 * $day;  
     my $inPast = 0;  
   
     # Logic in comments:  
     # Is it now? (extremely unlikely)  
     if ( $delta == 0 ) {  
         return "this instant";  
     }  
   
     if ($delta < 0) {  
         $inPast = 1;  
         $delta = -$delta;  
     }  
   
     if ( $delta > 0 ) {  
   
         my $tense = $inPast ? " ago" : "";  
         my $prefix = $inPast ? "" : "in ";  
           
         # Less than a minute  
         if ( $delta < $minute ) {  
             if ($delta == 1) { return "${prefix}1 second$tense"; }  
             return "$prefix$delta seconds$tense";  
         }  
   
         # Less than an hour  
         if ( $delta < $hour ) {  
             # If so, use minutes  
             my $minutes = floor($delta / 60);  
             if ($minutes == 1) { return "${prefix}1 minute$tense"; }  
             return "$prefix$minutes minutes$tense";  
         }  
           
         # Is it less than 24 hours away? If so,  
         # display hours + minutes  
         if ( $delta < $hour * 24) {  
             my $hours = floor($delta / $hour);  
             my $minutes = floor(($delta % $hour) / $minute);  
             my $hourString = "$hours hours";  
             my $minuteString = ", $minutes minutes";  
             if ($hours == 1) {  
                 $hourString = "1 hour";  
             }  
             if ($minutes == 1) {  
                 $minuteString = ", 1 minute";  
             }  
             if ($minutes == 0) {  
                 $minuteString = "";  
             }  
             return "$prefix$hourString$minuteString$tense";  
         }  
   
  my $dt = DateTime->from_epoch(epoch => $time)  
                  ->set_time_zone(&Apache::lonlocal::gettimezone());  
   
  # If there's a caller supplied format, use it.  
   
  if ($format ne '') {  
     my $timeStr = $dt->strftime($format);  
     return $timeStr.' ('.$dt->time_zone_short_name().')';  
  }  
   
         # Less than 5 days away, display day of the week and  
         # HH:MM  
   
         if ( $delta < $day * 5 ) {  
             my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");  
             $timeStr =~ s/12:00 am/00:00/;  
             $timeStr =~ s/12:00 pm/noon/;  
             return ($inPast ? "last " : "this ") .  
                 $timeStr;  
         }  
           
  my $conjunction='on';  
  if ($type eq 'start') {  
     $conjunction='at';  
  } elsif ($type eq 'end') {  
     $conjunction='by';  
  }  
         # Is it this year?  
  my $dt_now = DateTime->from_epoch(epoch => $now)  
                      ->set_time_zone(&Apache::lonlocal::gettimezone());  
         if ( $dt->year() == $dt_now->year()) {  
             # Return on Month Day, HH:MM meridian  
             my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");  
             $timeStr =~ s/12:00 am/00:00/;  
             $timeStr =~ s/12:00 pm/noon/;  
             return $timeStr;  
         }  
   
         # Not this year, so show the year  
         my $timeStr =   
     $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");  
         $timeStr =~ s/12:00 am/00:00/;  
         $timeStr =~ s/12:00 pm/noon/;  
         return $timeStr;  
     }  
 }  
   
   
 =pod  =pod
   
 =head1 NAME  =head1 NAME
   
 Apache::lonnavmap - Subroutines to handle and render the navigation  Apache::lonnavmaps - Subroutines to handle and render the navigation
     maps  
   
 =head1 SYNOPSIS  =head1 SYNOPSIS
   
   Handles navigational maps.
   
 The main handler generates the navigational listing for the course,  The main handler generates the navigational listing for the course,
 the other objects export this information in a usable fashion for  the other objects export this information in a usable fashion for
 other modules.  other modules.
   
   
   This is part of the LearningOnline Network with CAPA project
   described at http://www.lon-capa.org.
   
   
 =head1 OVERVIEW  =head1 OVERVIEW
   
 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
Line 602  automatically. Line 225  automatically.
   
 =over 4  =over 4
   
 =item * B<iterator>: default: constructs one from %env  =item * B<iterator: default: constructs one from %env
   
 A reference to a fresh ::iterator to use from the navmaps. The  A reference to a fresh ::iterator to use from the navmaps. The
 rendering will reflect the options passed to the iterator, so you can  rendering will reflect the options passed to the iterator, so you can
Line 743  navmaps screen) uses this to display the Line 366  navmaps screen) uses this to display the
   
 =cut  =cut
   
   
   =head1 SUBROUTINES
   
   =over
   
   =item update()
   
   =item addToFilter()
   
   Convenience functions: Returns a string that adds or subtracts
   the second argument from the first hash, appropriate for the 
   query string that determines which folders to recurse on
   
   =item removeFromFilter()
   
   =item getLinkForResource()
   
   Convenience function: Given a stack returned from getStack on the iterator,
   return the correct src() value.
   
   =item getDescription()
   
   Convenience function: This separates the logic of how to create
   the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
   etc.) into a separate function. It takes a resource object as the
   first parameter, and the part number of the resource as the second.
   It's basically a big switch statement on the status of the resource.
   
   =item dueInLessThan24Hours()
   
   Convenience function, so others can use it: Is the problem due in less than 24 hours, and still can be done?
   
   =item lastTry()
   
   Convenience function, so others can use it: Is there only one try remaining for the
   part, with more than one try to begin with, not due yet and still can be done?
   
   =item advancedUser()
   
   This puts a human-readable name on the env variable.
   
   =item timeToHumanString()
   
   timeToHumanString takes a time number and converts it to a
   human-readable representation, meant to be used in the following
   manner:
   
   =over 4
   
   =item * print "Due $timestring"
   
   =item * print "Open $timestring"
   
   =item * print "Answer available $timestring"
   
   =back
   
   Very, very, very, VERY English-only... goodness help a localizer on
   this func...
   
   =item resource()
   
   returns 0
   
   =item communication_status()
   
   returns 1
   
   =item quick_status()
   
   returns 2
   
   =item long_status()
   
   returns 3
   
   =item part_status_summary()
   
   returns 4
   
   =item render_resource()
   
   =item render_communication_status()
   
   =item render_quick_status()
   
   =item render_long_status()
   
   =item render_parts_summary_status()
   
   =item setDefault()
   
   =item cmp_title()
   
   =item render()
   
   =item add_linkitem()
   
   =item show_linkitems()
   
   =back
   
   =cut
   
   package Apache::lonnavmaps;
   
   use strict;
   use GDBM_File;
   use Apache::loncommon();
   use Apache::lonenc();
   use Apache::lonlocal;
   use Apache::lonnet;
   use Apache::lonmap;
   
   use POSIX qw (floor strftime);
   use Time::HiRes qw( gettimeofday tv_interval );
   use LONCAPA;
   use DateTime();
   
   # For debugging
   
   # use Data::Dumper;
   
   
   # symbolic constants
   sub SYMB { return 1; }
   sub URL { return 2; }
   sub NOTHING { return 3; }
   
   # Some data
   
   my $resObj = "Apache::lonnavmaps::resource";
   
   # Keep these mappings in sync with lonquickgrades, which usesthe colors
   # instead of the icons.
   my %statusIconMap = 
       (
        $resObj->CLOSED       => '',
        $resObj->OPEN         => 'navmap.open.gif',
        $resObj->CORRECT      => 'navmap.correct.gif',
        $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',
        $resObj->INCORRECT    => 'navmap.wrong.gif',
        $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
        $resObj->ERROR        => ''
        );
   
   my %iconAltTags =   #texthash does not work here
       ( 'navmap.correct.gif'  => 'Correct',
         'navmap.wrong.gif'    => 'Incorrect',
         'navmap.open.gif'     => 'Is Open',
         'navmap.partial.gif'  => 'Partially Correct',
         'navmap.ellipsis.gif' => 'Attempted',
        );
   
   # Defines a status->color mapping, null string means don't color
   my %colormap = 
       ( $resObj->NETWORK_FAILURE        => '',
         $resObj->CORRECT                => '',
         $resObj->EXCUSED                => '#3333FF',
         $resObj->PAST_DUE_ANSWER_LATER  => '',
         $resObj->PAST_DUE_NO_ANSWER     => '',
         $resObj->ANSWER_OPEN            => '#006600',
         $resObj->OPEN_LATER             => '',
         $resObj->TRIES_LEFT             => '',
         $resObj->INCORRECT              => '',
         $resObj->OPEN                   => '',
         $resObj->NOTHING_SET            => '',
         $resObj->ATTEMPTED              => '',
         $resObj->CREDIT_ATTEMPTED       => '',
         $resObj->ANSWER_SUBMITTED       => '',
         $resObj->PARTIALLY_CORRECT      => '#006600'
         );
   # And a special case in the nav map; what to do when the assignment
   # is not yet done and due in less than 24 hours
   my $hurryUpColor = "#FF0000";
   
   sub addToFilter {
       my $hashIn = shift;
       my $addition = shift;
       my %hash = %$hashIn;
       $hash{$addition} = 1;
   
       return join (",", keys(%hash));
   }
   
   sub removeFromFilter {
       my $hashIn = shift;
       my $subtraction = shift;
       my %hash = %$hashIn;
   
       delete $hash{$subtraction};
       return join(",", keys(%hash));
   }
   
   sub getLinkForResource {
       my $stack = shift;
       my $res;
   
       # Check to see if there are any pages in the stack
       foreach $res (@$stack) {
           if (defined($res)) {
       my $anchor;
       if ($res->is_page()) {
    foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
    $anchor=&escape($anchor->shown_symb());
    return ($res->link(),$res->shown_symb(),$anchor);
       }
               # in case folder was skipped over as "only sequence"
       my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
       if ($map=~/\.page$/) {
    my $url=&Apache::lonnet::clutter($map);
    $anchor=&escape($res->shown_symb());
    return ($url,$res->shown_symb(),$anchor);
       }
           }
       }
   
       # Failing that, return the src of the last resource that is defined
       # (when we first recurse on a map, it puts an undefined resource
       # on the bottom because $self->{HERE} isn't defined yet, and we
       # want the src for the map anyhow)
       foreach my $item (@$stack) {
           if (defined($item)) { $res = $item; }
       }
   
       if ($res) {
    return ($res->link(),$res->shown_symb());
       }
       return;
   }
   
   
   
   sub getDescription {
       my $res = shift;
       my $part = shift;
       my $status = $res->status($part);
   
       my $open = $res->opendate($part);
       my $due = $res->duedate($part);
       my $answer = $res->answerdate($part);
   
       if ($status == $res->NETWORK_FAILURE) { 
           return &mt("Having technical difficulties; please check status later"); 
       }
       if ($status == $res->NOTHING_SET) {
           return &Apache::lonhtmlcommon::direct_parm_link(&mt("Not currently assigned.",$res->symb(),'opendate'),$part);
       }
       if ($status == $res->OPEN_LATER) {
           return &mt("Open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($open,'start'),$res->symb(),'opendate',$part));
       }
       if ($res->simpleStatus($part) == $res->OPEN) {
           unless (&Apache::lonnet::allowed('mgr',$env{'request.course.id'})) {
               my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part);
               if ($slot_status == $res->UNKNOWN) {
                   return &mt('Reservation status unknown');
               } elsif ($slot_status == $res->RESERVED) {
                   return &mt('Reserved - ends [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVED_LOCATION) {
                   return &mt('Reserved - specific location(s) - ends [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVED_LATER) {
                   return &mt('Reserved - next open [_1]',
                              timeToHumanString($slot_time,'start'));
               } elsif ($slot_status == $res->RESERVABLE) {
                   return &mt('Reservable, reservations close [_1]',
                              timeToHumanString($slot_time,'end'));
               } elsif ($slot_status == $res->RESERVABLE_LATER) {
                   return &mt('Reservable, reservations open [_1]',
                              timeToHumanString($slot_time,'start'));
               } elsif ($slot_status == $res->NOT_IN_A_SLOT) {
                   return &mt('Reserve a time/place to work');
               } elsif ($slot_status == $res->NOTRESERVABLE) {
                   return &mt('Reservation not available');
               } elsif ($slot_status == $res->WAITING_FOR_GRADE) {
                   return &mt('Submission in grading queue');
               }
           }
       }
       if ($status == $res->OPEN) {
           if ($due) {
       if ($res->is_practice()) {
    return &mt("Closes [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'duedate',$part));
       } else {
    return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part));
       }
           } else {
               return &Apache::lonhtmlcommon::direct_parm_link(&mt("Open, no due date"),$res->symb(),'duedate',$part);
           }
       }
       if ($status == $res->PAST_DUE_ANSWER_LATER) {
           return &mt("Answer open [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($answer,'start'),$res->symb(),'answerdate',$part));
       }
       if ($status == $res->PAST_DUE_NO_ANSWER) {
    if ($res->is_practice()) {
       return &mt("Closed [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'start'),$res->symb(),'answerdate,duedate',$part));
    } else {
       return &mt("Was due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'answerdate,duedate',$part));
    }
       }
       if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
    && $res->handgrade($part) ne 'yes') {
           return &Apache::lonhtmlcommon::direct_parm_link(&mt("Answer available"),$res->symb(),'answerdate,duedate',$part);
       }
       if ($status == $res->EXCUSED) {
           return &mt("Excused by instructor");
       }
       if ($status == $res->ATTEMPTED) {
           if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
               return &mt("Survey submission recorded");
           } else {
               return &mt("Answer submitted, not yet graded");
           }
       }
       if ($status == $res->CREDIT_ATTEMPTED) {
           if ($res->is_anonsurvey($part) || $res->is_survey($part)) {
               return &mt("Credit for survey submission");
           }
       }
       if ($status == $res->TRIES_LEFT) {
           my $tries = $res->tries($part);
           my $maxtries = $res->maxtries($part);
           my $triesString = "";
           if ($tries && $maxtries) {
               $triesString = '<font size="-1"><i>('.&mt('[_1] of [quant,_2,try,tries] used',$tries,$maxtries).')</i></font>';
               if ($maxtries > 1 && $maxtries - $tries == 1) {
                   $triesString = "<b>$triesString</b>";
               }
           }
           if ($due) {
               return &mt("Due [_1]",&Apache::lonhtmlcommon::direct_parm_link(&timeToHumanString($due,'end'),$res->symb(),'duedate',$part)) .
                   " $triesString";
           } else {
               return &Apache::lonhtmlcommon::direct_parm_link(&mt("No due date"),$res->symb(),'duedate',$part)." $triesString";
           }
       }
       if ($status == $res->ANSWER_SUBMITTED) {
           return &mt('Answer submitted');
       }
   }
   
   
   sub dueInLessThan24Hours {
       my $res = shift;
       my $part = shift;
       my $status = $res->status($part);
   
       return ($status == $res->OPEN() ||
               $status == $res->TRIES_LEFT()) &&
       $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
       $res->duedate($part) > time();
   }
   
   
   sub lastTry {
       my $res = shift;
       my $part = shift;
   
       my $tries = $res->tries($part);
       my $maxtries = $res->maxtries($part);
       return $tries && $maxtries && $maxtries > 1 &&
           $maxtries - $tries == 1 && $res->duedate($part) &&
           $res->duedate($part) > time();
   }
   
   
   sub advancedUser {
       return $env{'request.role.adv'};
   }
   
   sub timeToHumanString {
       my ($time,$type,$format) = @_;
   
       # zero, '0' and blank are bad times
       if (!$time) {
           return &mt('never');
       }
       unless (&Apache::lonlocal::current_language()=~/^en/) {
    return &Apache::lonlocal::locallocaltime($time);
       } 
       my $now = time();
   
       # Positive = future
       my $delta = $time - $now;
   
       my $minute = 60;
       my $hour = 60 * $minute;
       my $day = 24 * $hour;
       my $week = 7 * $day;
       my $inPast = 0;
   
       # Logic in comments:
       # Is it now? (extremely unlikely)
       if ( $delta == 0 ) {
           return "this instant";
       }
   
       if ($delta < 0) {
           $inPast = 1;
           $delta = -$delta;
       }
   
       if ( $delta > 0 ) {
   
           my $tense = $inPast ? " ago" : "";
           my $prefix = $inPast ? "" : "in ";
           
           # Less than a minute
           if ( $delta < $minute ) {
               if ($delta == 1) { return "${prefix}1 second$tense"; }
               return "$prefix$delta seconds$tense";
           }
   
           # Less than an hour
           if ( $delta < $hour ) {
               # If so, use minutes; or minutes, seconds (if format requires)
               my $minutes = floor($delta / 60);
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display;
                   if ($minutes == 1) {
                       $display = "${prefix}1 minute";
                   } else {
                       $display = "$prefix$minutes minutes";
                   }
                   my $seconds = $delta % $minute;
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
               if ($minutes == 1) { return "${prefix}1 minute$tense"; }
               return "$prefix$minutes minutes$tense";
           }
           
           # Is it less than 24 hours away? If so,
           # display hours + minutes, (and + seconds, if format specified it)  
           if ( $delta < $hour * 24) {
               my $hours = floor($delta / $hour);
               my $minutes = floor(($delta % $hour) / $minute);
               my $hourString = "$hours hours";
               my $minuteString = ", $minutes minutes";
               if ($hours == 1) {
                   $hourString = "1 hour";
               }
               if ($minutes == 1) {
                   $minuteString = ", 1 minute";
               }
               if ($minutes == 0) {
                   $minuteString = "";
               }
               if (($format ne '') && ($format =~ /\%(T|S)/)) {
                   my $display = "$prefix$hourString$minuteString";
                   my $seconds = $delta-(($hours * $hour)+($minutes * $minute));
                   if ($seconds == 0) {
                       $display .= $tense;
                   } elsif ($seconds == 1) {
                       $display .= ", 1 second$tense";
                   } else {
                       $display .= ", $seconds seconds$tense";
                   }
                   return $display;
               }
               return "$prefix$hourString$minuteString$tense";
           }
   
           # Date/time is more than 24 hours away
   
    my $dt = DateTime->from_epoch(epoch => $time)
                    ->set_time_zone(&Apache::lonlocal::gettimezone());
   
    # If there's a caller supplied format, use it, unless it only displays
           # H:M:S or H:M.
   
    if (($format ne '') && ($format ne '%T') && ($format ne '%R')) {
       my $timeStr = $dt->strftime($format);
       return $timeStr.' ('.$dt->time_zone_short_name().')';
    }
   
           # Less than 5 days away, display day of the week and
           # HH:MM
   
           if ( $delta < $day * 5 ) {
               my $timeStr = $dt->strftime("%A, %b %e at %I:%M %P (%Z)");
               $timeStr =~ s/12:00 am/00:00/;
               $timeStr =~ s/12:00 pm/noon/;
               return ($inPast ? "last " : "this ") .
                   $timeStr;
           }
           
    my $conjunction='on';
    if ($type eq 'start') {
       $conjunction='at';
    } elsif ($type eq 'end') {
       $conjunction='by';
    }
           # Is it this year?
    my $dt_now = DateTime->from_epoch(epoch => $now)
                        ->set_time_zone(&Apache::lonlocal::gettimezone());
           if ( $dt->year() == $dt_now->year()) {
               # Return on Month Day, HH:MM meridian
               my $timeStr = $dt->strftime("$conjunction %A, %b %e at %I:%M %P (%Z)");
               $timeStr =~ s/12:00 am/00:00/;
               $timeStr =~ s/12:00 pm/noon/;
               return $timeStr;
           }
   
           # Not this year, so show the year
           my $timeStr = 
       $dt->strftime("$conjunction %A, %b %e %Y at %I:%M %P (%Z)");
           $timeStr =~ s/12:00 am/00:00/;
           $timeStr =~ s/12:00 pm/noon/;
           return $timeStr;
       }
   }
   
   
 sub resource { return 0; }  sub resource { return 0; }
 sub communication_status { return 1; }  sub communication_status { return 1; }
 sub quick_status { return 2; }  sub quick_status { return 2; }
Line 757  sub render_resource { Line 901  sub render_resource {
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
   
     #  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... 
     #  The stuff to the left of the ? must have ' replaced by \' since  
     #  it will be quoted with ' in the href.  
   
     my ($left,$right) = split(/\?/, $link);  
     $link = $left.'?'.$right;  
   
     my $src = $resource->src();      my $src = $resource->src();
     my $it = $params->{"iterator"};      my $it = $params->{"iterator"};
Line 774  sub render_resource { Line 913  sub render_resource {
     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");      my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
     # If this is a new branch, label it so      # If this is a new branch, label it so
     if ($params->{'isNewBranch'}) {      if ($params->{'isNewBranch'}) {
         $newBranchText = "<img src='$location/branch.gif' border='0' alt='Branch' />";          $newBranchText = "<img src='$location/branch.gif' alt=".mt('Branch')." />";
     }      }
   
     # links to open and close the folder      # links to open and close the folder
   
           my $whitespace = $location.'/whitespace_21.gif';
     my $linkopen = "<a href=\"$link\">";      my $linkopen = "<img src='$whitespace' alt='' />"."<a href=\"$link\">";
   
   
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
     # Default icon: unknown page      # Default icon: unknown page
     my $icon = "<img src='$location/unknown.gif' alt='' border='0' alt='&nbsp;&nbsp;' ' />";      my $icon = "<img class=\"LC_contentImage\" src='$location/unknown.gif' alt='' />";
           
     if ($resource->is_problem()) {      if ($resource->is_problem()) {
         if ($part eq '0' || $params->{'condensed'}) {          if ($part eq '0' || $params->{'condensed'}) {
     $icon = '<img src="'.$location.'/';      $icon = '<img class="LC_contentImage" src="'.$location.'/';
     if ($resource->is_task()) {      if ($resource->is_task()) {
  $icon .= 'task.gif" alt="'.&mt('Task');   $icon .= 'task.gif" alt="'.&mt('Task');
     } else {      } else {
  $icon .= 'problem.gif" alt="'.&mt('Problem');   $icon .= 'problem.gif" alt="'.&mt('Problem');
     }      }
     $icon .='" border="0" />';      $icon .='" />';
         } else {          } else {
             $icon = $params->{'indentString'};              $icon = $params->{'indentString'};
         }          }
     } else {      } else {
  $icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";   $icon = "<img class=\"LC_contentImage\" src='".&Apache::loncommon::icon($resource->src)."' alt='' />";
     }      }
   
     # Display the correct map icon to open or shut map      # Display the correct map icon to open or shut map
Line 811  sub render_resource { Line 948  sub render_resource {
         if ($it->{CONDITION}) {          if ($it->{CONDITION}) {
             $nowOpen = !$nowOpen;              $nowOpen = !$nowOpen;
         }          }
   
  my $folderType = $resource->is_sequence() ? 'folder' : 'page';   my $folderType = $resource->is_sequence() ? 'folder' : 'page';
         my $title=$resource->title;          my $title=$resource->title;
         $title=~s/\"/\&quot;/g;   $title=~s/\"/\&qout;/g;
         if (!$params->{'resource_no_folder_link'}) {          if (!$params->{'resource_no_folder_link'}) {
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
     $icon = "<img src='$location/$icon' alt=\"".              $icon = "<img src='$location/arrow." . ($nowOpen ? 'closed' : 'open') . ".gif' alt='' />"
  ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";                      ."<img class=\"LC_contentImage\" src='$location/$icon' alt=\""
                       .($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" />";
             $linkopen = "<a href=\"" . $params->{'url'} . '?' .               $linkopen = "<a href=\"" . $params->{'url'} . '?' . 
                 $params->{'queryString'} . '&amp;filter=';                  $params->{'queryString'} . '&amp;filter=';
             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?              $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
Line 834  sub render_resource { Line 971  sub render_resource {
   
         } else {          } else {
             # Don't allow users to manipulate folder              # Don't allow users to manipulate folder
             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .              $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
                 '.nomanip.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 src='$location/$icon' alt=\"".  
  ($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";  
   
             $linkopen = "";              $linkopen = "";
             $linkclose = "";              $linkclose = "";
Line 845  sub render_resource { Line 980  sub render_resource {
     }      }
   
     if ($resource->randomout()) {      if ($resource->randomout()) {
         $nonLinkedText .= ' <i>('.&mt('hidden').')</i> ';          $nonLinkedText .= ' <span class="LC_warning">('.&mt('hidden').')</span> ';
     }      }
     if (!$resource->condval()) {      if (!$resource->condval()) {
         $nonLinkedText .= ' <i>('.&mt('conditionally hidden').')</i> ';          $nonLinkedText .= ' <span class="LC_info">('.&mt('conditionally hidden').')</span> ';
       }
       if (($resource->is_practice()) && ($resource->is_raw_problem())) {
           $nonLinkedText .=' <font color="green"><b>'.&mt('not graded').'</b></font>';
     }      }
       
     # We're done preparing and finally ready to start the rendering  
     my $result = "<td align='left' valign='middle'>";  
   
       # We're done preparing and finally ready to start the rendering
       my $result = '<td class="LC_middle">';
       my $newfolderType = $resource->is_sequence() ? 'folder' : 'page';
   
     my $indentLevel = $params->{'indentLevel'};      my $indentLevel = $params->{'indentLevel'};
     if ($newBranchText) { $indentLevel--; }      if ($newBranchText) { $indentLevel--; }
   
Line 863  sub render_resource { Line 1002  sub render_resource {
     }      }
   
     # Decide what to display      # Decide what to display
   
     $result .= "$newBranchText$linkopen$icon$linkclose";      $result .= "$newBranchText$linkopen$icon$linkclose";
           
     my $curMarkerBegin = '';      my $curMarkerBegin = '';
Line 872  sub render_resource { Line 1010  sub render_resource {
     # Is this the current resource?      # Is this the current resource?
     if (!$params->{'displayedHereMarker'} &&       if (!$params->{'displayedHereMarker'} && 
         $resource->symb() eq $params->{'here'} ) {          $resource->symb() eq $params->{'here'} ) {
         $curMarkerBegin = '<font color="red" size="+2">&gt;</font>';          $curMarkerBegin = '<span class="LC_current_nav_location">';
         $curMarkerEnd = '<font color="red" size="+2">&lt;</font>';          $curMarkerEnd = '</span>';
         $params->{'displayedHereMarker'} = 1;   $params->{'displayedHereMarker'} = 1;
     }      }
   
     if ($resource->is_problem() && $part ne '0' &&       if ($resource->is_problem() && $part ne '0' && 
Line 889  sub render_resource { Line 1027  sub render_resource {
         $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';          $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
     }      }
   
     my $target;  
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     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 $target href=\"$link\">$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";          $result .= "$curMarkerBegin<a href=\"$link\">$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";
     } else {      } else {
         $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";          $result .= "$curMarkerBegin$linkopen$title$partLabel</a>$curMarkerEnd$nonLinkedText</td>";
     }      }
   
     return $result;      return $result;
Line 907  sub render_communication_status { Line 1041  sub render_communication_status {
     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";      my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");      my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
     if ($resource->hasDiscussion()) {      if ($resource->hasDiscussion()) {
         $discussionHTML = $linkopen .          $discussionHTML = $linkopen .
             '<img alt="'.&mt('New Discussion').'" border="0" src="'.$location.'/chat.gif" />' .              '<img alt="'.&mt('New Discussion').'" src="'.$location.'/chat.gif" title="'.&mt('New Discussion').'"/>' .
             $linkclose;              $linkclose;
     }      }
           
Line 924  sub render_communication_status { Line 1054  sub render_communication_status {
         my $feedback = $resource->getFeedback();          my $feedback = $resource->getFeedback();
         foreach my $msgid (split(/\,/, $feedback)) {          foreach my $msgid (split(/\,/, $feedback)) {
             if ($msgid) {              if ($msgid) {
                 $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $feedbackHTML .= '&nbsp;<a href="/adm/email?display='
                     . &escape($msgid) . '">'                      . &escape($msgid) . '">'
                     . '<img alt="'.&mt('New Email').'" src="'.$location.'/feedback.gif" '                      . '<img alt="'.&mt('New E-mail').'" src="'.$location.'/feedback.gif" title="'.&mt('New E-mail').'"/></a>';
                     . 'border="0" /></a>';  
             }              }
         }          }
     }      }
Line 939  sub render_communication_status { Line 1068  sub render_communication_status {
             last if ($errorcount>=10); # Only output 10 bombs maximum              last if ($errorcount>=10); # Only output 10 bombs maximum
             if ($msgid) {              if ($msgid) {
                 $errorcount++;                  $errorcount++;
                 $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='                  $errorHTML .= '&nbsp;<a href="/adm/email?display='
                     . &escape($msgid) . '">'                      . &escape($msgid) . '">'
                     . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" '                      . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" title="'.&mt('New Error').'"/></a>';
                     . 'border="0" /></a>';  
             }              }
         }          }
     }      }
Line 950  sub render_communication_status { Line 1078  sub render_communication_status {
     if ($params->{'multipart'} && $part != '0') {      if ($params->{'multipart'} && $part != '0') {
  $discussionHTML = $feedbackHTML = $errorHTML = '';   $discussionHTML = $feedbackHTML = $errorHTML = '';
     }      }
       return "<td class=\"LC_middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
     return "<td width=\"75\" align=\"left\" valign=\"middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";  
   
 }  }
 sub render_quick_status {  sub render_quick_status {
Line 961  sub render_quick_status { Line 1088  sub render_quick_status {
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
   
     my $link = $params->{"resourceLink"};      my $link = $params->{"resourceLink"};
     my $target;      my $linkopen = "<a href=\"$link\">";
     if ($env{'environment.remotenavmap'} eq 'on') {  
  $target=' target="loncapaclient" ';  
     }  
     my $linkopen = "<a $target href=\"$link\">";  
     my $linkclose = "</a>";      my $linkclose = "</a>";
   
    $result .= '<td class="LC_middle">';
     if ($resource->is_problem() &&      if ($resource->is_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
   
         my $icon = $statusIconMap{$resource->simpleStatus($part)};          my $icon = $statusIconMap{$resource->simpleStatus($part)};
         my $alt = $iconAltTags{$icon};          my $alt = $iconAltTags{$icon};
         if ($icon) {          if ($icon) {
     my $location=      my $location=
  &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");   &Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
             $result .= "<td valign='middle' width='50' align='right'>$linkopen<img width='25' height='25' src='$location' border='0' alt='$alt' />$linkclose</td>\n";   $result .= $linkopen.'<img src="'.$location.'" alt="'.&mt($alt).'" title="'.&mt($alt).'" />'.$linkclose;            
         } else {          } else {
             $result .= "<td width='30'>&nbsp;</td>\n";              $result .= "&nbsp;";
         }          }
     } else { # not problem, no icon      } else { # not problem, no icon
         $result .= "<td width='30'>&nbsp;</td>\n";          $result .= "&nbsp;";
     }      }
    $result .= "</td>\n";
     return $result;      return $result;
 }  }
 sub render_long_status {  sub render_long_status {
     my ($resource, $part, $params) = @_;      my ($resource, $part, $params) = @_;
     my $result = "<td align='right' valign='middle'>\n";      my $result = '<td class="LC_middle LC_right">';
     my $firstDisplayed = !$params->{'condensed'} &&       my $firstDisplayed = !$params->{'condensed'} && 
         $params->{'multipart'} && $part eq "0";          $params->{'multipart'} && $part eq "0";
                                   
Line 1003  sub render_long_status { Line 1126  sub render_long_status {
     }      }
           
     if ($resource->kind() eq "res" &&      if ($resource->kind() eq "res" &&
         ($resource->is_problem() || $resource->is_practice()) &&          $resource->is_raw_problem() &&
         !$firstDisplayed) {          !$firstDisplayed) {
         if ($color) {$result .= "<font color=\"$color\"><b>"; }          if ($color) {$result .= "<font color=\"$color\"><b>"; }
         $result .= getDescription($resource, $part);          $result .= getDescription($resource, $part);
         if ($color) {$result .= "</b></font>"; }          if ($color) {$result .= "</b></font>"; }
     }      }
       if ($resource->is_map()) {
           if (&Apache::lonnet::allowed('mdc')) {
               if ($resource->symb=~/\_\_\_[^\_]+\_\_\_uploaded/) {
                  $result.="&nbsp;<a href='/adm/coursedocs?command=direct&symb=".&escape($resource->symb())."'>".
                                 "<span class='LC_setting'>".&mt("Edit Content").'</span></a>&nbsp;';
               }
           }
       }
     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 1175  sub render { Line 1306  sub render {
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
     if (!defined($navmap)) {      if (!defined($navmap)) {
  # no londer in course   # no longer in course
  return '<span class="LC_error">'.&mt('No course selected').'</span><br />   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                         <a href="/adm/roles">'.&mt('Select a course').'</a><br />';                          <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
     }      }
Line 1243  sub render { Line 1374  sub render {
         # Step 1: Check to see if we have a navmap          # Step 1: Check to see if we have a navmap
         if (!defined($navmap)) {          if (!defined($navmap)) {
             $navmap = Apache::lonnavmaps::navmap->new();              $navmap = Apache::lonnavmaps::navmap->new();
               if (!defined($navmap)) {
                   # no longer in course
                   return '<span class="LC_error">'.&mt('No course selected').'</span><br />
                           <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
               }
         }          }
   
         # See if we're being passed a specific map          # See if we're being passed a specific map
Line 1258  sub render { Line 1394  sub render {
         }          }
     }      }
   
   
     # (re-)Locate the jump point, if any      # (re-)Locate the jump point, if any
     # Note this does not take filtering or hidden into account... need      # Note this does not take filtering or hidden into account... need
     # to be fixed?      # to be fixed?
Line 1287  sub render { Line 1424  sub render {
     my $printKey = $args->{'printKey'};      my $printKey = $args->{'printKey'};
     my $printCloseAll = $args->{'printCloseAll'};      my $printCloseAll = $args->{'printCloseAll'};
     if (!defined($printCloseAll)) { $printCloseAll = 1; }      if (!defined($printCloseAll)) { $printCloseAll = 1; }
      
     # Print key?      # Print key?
     if ($printKey) {      if ($printKey) {
         $result .= '<table border="0" cellpadding="2" cellspacing="0">';          $result .= '<table border="0" cellpadding="2" cellspacing="0">';
Line 1295  sub render { Line 1432  sub render {
  my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");   my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
         if ($navmap->{LAST_CHECK}) {          if ($navmap->{LAST_CHECK}) {
             $result .=               $result .= 
                 '<img src="'.$location.'/chat.gif"> '.&mt('New discussion since').' '.                  '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('New discussion since').' '.
                 strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).                  strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
                 '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.                  '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').'<p>'.                  '<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').'<p>'.
                 '</td>';                   '</td>'; 
         } else {          } else {
             $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.              $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.
                 '<img src="'.$location.'/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.                  '<img src="'.$location.'/chat.gif" alt="" /> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                 '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').                  '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif" alt="" /> '.&mt('New message (click to open)').
                 '</td>';                   '</td>'; 
         }          }
   
Line 1313  sub render { Line 1450  sub render {
     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {      if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
  my ($link,$text);   my ($link,$text);
         if ($condition) {          if ($condition) {
     $link='"navmaps?condition=0&amp;filter=&amp;'.$queryString.      $link='navmaps?condition=0&amp;filter=&amp;'.$queryString.
  '&here='.&escape($here).'"';   '&amp;here='.&escape($here);
     $text='Close all folders';      $text='Close all folders';
         } else {          } else {
     $link='"navmaps?condition=1&amp;filter=&amp;'.$queryString.      $link='navmaps?condition=1&amp;filter=&amp;'.$queryString.
  '&here='.&escape($here).'"';   '&amp;here='.&escape($here);
     $text='Open all folders';      $text='Open all folders';
         }          }
           if ($env{'form.register'}) {
               $link .= '&amp;register='.$env{'form.register'};
           }
  if ($args->{'caller'} eq 'navmapsdisplay') {   if ($args->{'caller'} eq 'navmapsdisplay') {
     &add_linkitem($args->{'linkitems'},'changefolder',      &add_linkitem($args->{'linkitems'},'changefolder',
   'location.href='.$link,$text);    "location.href='$link'",$text);
  } else {   } else {
     $result.='<a href='.$link.'>'.&mt($text).'</a>';      $result.= '<a href="'.$link.'">'.&mt($text).'</a>';
  }   }
         $result .= "\n";          $result .= "\n";
     }      }
Line 1341  sub render { Line 1481  sub render {
  <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />   <input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
  <input type="hidden" name="navtime" value="$time" />   <input type="hidden" name="navtime" value="$time" />
 END  END
           if ($env{'form.register'}) {
               $result .= '<input type="hidden" name="register" value="'.$env{'form.register'}.'" />';
           }
         if ($args->{'sort'} eq 'discussion') {           if ($args->{'sort'} eq 'discussion') { 
     my $totdisc = 0;      my $totdisc = 0;
     my $haveDisc = '';      my $haveDisc = '';
Line 1360  END Line 1503  END
             }              }
  }   }
  $result.='</form>';   $result.='</form>';
           if (&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) {
               my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
               my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
               if ($env{'course.'.$env{'request.course.id'}.'.url'} eq 
                   "uploaded/$cdom/$cnum/default.sequence") {
                   &add_linkitem($args->{'linkitems'},'edittoplevel',
                                 "javascript:gocmd('/adm/coursedocs','direct');",
                                 'Content Editor');
               }
           }
     }      }
   
     if ($args->{'caller'} eq 'navmapsdisplay') {      if ($args->{'caller'} eq 'navmapsdisplay') {
         $result .= '<table><tr><td>'.          $result .= '<table><tr><td>'.
                    &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';                     &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';
  if ($env{'environment.remotenavmap'} ne 'on') {  
     $result .= '<td>&nbsp;</td>';       $result .= '<td>&nbsp;</td>'; 
         } else {   $result.='<td class="LC_middle">'.&mt('Tools:').'</td>';
     $result .= '</tr><tr>';    $result.=&show_linkitems_toolbar($args->{'linkitems'});
         }  
  $result.=&show_linkitems($args->{'linkitems'});  
         if ($args->{'sort_html'}) {          if ($args->{'sort_html'}) {
     if ($env{'environment.remotenavmap'} ne 'on') {              $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
  $result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.                  '<td align="right">'.$args->{'sort_html'}.'</td></tr>';
     '<td align="right">'.$args->{'sort_html'}.'</td></tr>';          }
     } else {  
  $result.='</tr><tr><td align="left"><br />'.  
     $args->{'sort_html'}.'</td></tr>';  
     }  
  }  
         $result .= '</table>';          $result .= '</table>';
     } elsif ($args->{'sort_html'}) {       } elsif ($args->{'sort_html'}) { 
         $result.=$args->{'sort_html'};           $result.=$args->{'sort_html'}; 
     }      }
   
     $result .= "<br />\n";      #$result .= "<br />\n";
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $r->rflush();          $r->rflush();
         $result = "";          $result = "";
     }      }
     # End parameter setting      # End parameter setting
                   
       $result .= "<br />\n";
   
     # Data      # Data
     $result .= '<table cellspacing="0" cellpadding="3" border="0" bgcolor="#FFFFFF">' ."\n";      $result.=&Apache::loncommon::start_data_table("LC_tableOfContent");    
   
     my $res = "Apache::lonnavmaps::resource";      my $res = "Apache::lonnavmaps::resource";
     my %condenseStatuses =      my %condenseStatuses =
         ( $res->NETWORK_FAILURE    => 1,          ( $res->NETWORK_FAILURE    => 1,
           $res->NOTHING_SET        => 1,            $res->NOTHING_SET        => 1,
           $res->CORRECT            => 1 );            $res->CORRECT            => 1 );
     my @backgroundColors = ("#FFFFFF", "#F6F6F6");  
   
     # Shared variables      # Shared variables
     $args->{'counter'} = 0; # counts the rows      $args->{'counter'} = 0; # counts the rows
     $args->{'indentLevel'} = 0;      $args->{'indentLevel'} = 0;
     $args->{'isNewBranch'} = 0;      $args->{'isNewBranch'} = 0;
     $args->{'condensed'} = 0;          $args->{'condensed'} = 0;   
     my $location=  
  &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");      my $location = &Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif");
     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' width='25' height='1' alt='&nbsp;&nbsp;' border='0' />");      $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. Use DFS for speed,
Line 1510  END Line 1657  END
     $curRes = shift(@resources);      $curRes = shift(@resources);
  } else {   } else {
     $curRes = $it->next($closeAllPages);      $curRes = $it->next($closeAllPages);
   
  }   }
  if (!$curRes) { last; }   if (!$curRes) { last; }
   
Line 1628  END Line 1776  END
     if (defined($anchor)) { $anchor='#'.$anchor; }      if (defined($anchor)) { $anchor='#'.$anchor; }
     my $srcHasQuestion = $src =~ /\?/;      my $srcHasQuestion = $src =~ /\?/;
     $args->{"resourceLink"} = $src.      $args->{"resourceLink"} = $src.
  ($srcHasQuestion?'&':'?') .   ($srcHasQuestion?'&amp;':'?') .
  'symb=' . &escape($symb).$anchor;   'symb=' . &escape($symb).$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.
         foreach my $part (@parts) {          foreach my $part (@parts) {
             $rownum ++;              $rownum ++;
             my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];  
                           
             $result .= "  <tr bgcolor='$backgroundColor'>\n";              $result .= &Apache::loncommon::start_data_table_row();
   
             # Set up some data about the parts that the cols might want              # Set up some data about the parts that the cols might want
             my $filter = $it->{FILTER};              my $filter = $it->{FILTER};
Line 1663  END Line 1810  END
                 }                  }
                 $result .= $colHTML . "\n";                  $result .= $colHTML . "\n";
             }              }
             $result .= "    </tr>\n";              $result .= &Apache::loncommon::end_data_table_row();
             $args->{'isNewBranch'} = 0;              $args->{'isNewBranch'} = 0;
         }          }
   
Line 1691  END Line 1838  END
     # it's quite likely this might fix other browsers, too, and       # it's quite likely this might fix other browsers, too, and 
     # certainly won't hurt anything.      # certainly won't hurt anything.
     if ($displayedJumpMarker) {      if ($displayedJumpMarker) {
         $result .= "          $result .= &Apache::lonhtmlcommon::scripttag("
 <script>  
 if (location.href.indexOf('#curloc')==-1) {  if (location.href.indexOf('#curloc')==-1) {
     setTimeout(\"location += '#curloc';\", 0)      setTimeout(\"location += '#curloc';\", 0)
 }  }
 </script>";  ");
     }      }
   
     $result .= "</table>";      $result.=&Apache::loncommon::end_data_table();
       
     if ($r) {      if ($r) {
         $r->print($result);          $r->print($result);
         $result = "";          $result = "";
Line 1716  sub add_linkitem { Line 1862  sub add_linkitem {
     $$linkitems{$name}{'text'}=&mt($text);      $$linkitems{$name}{'text'}=&mt($text);
 }  }
   
 sub show_linkitems {  sub show_linkitems_toolbar {
     my ($linkitems)=@_;      my ($linkitems,$condition)=@_;
     my @linkorder = ("blank","launchnav","closenav","firsthomework",      my @linkorder = ('firsthomework','everything','uncompleted',
      "everything","uncompleted","changefolder","clearbubbles");                       'changefolder','clearbubbles','edittoplevel');
           my $result .='<td align="left">'."\n". 
     my $result .= (<<ENDBLOCK);                   '<span class="LC_nobreak">'."\n".
               <td align="left">                   '<ul id="LC_toolbar">';
 <script type="text/javascript">  
     function changeNavDisplay () {  
  var navchoice = document.linkitems.toplink[document.linkitems.toplink.selectedIndex].value;  
 ENDBLOCK  
     foreach my $link (@linkorder) {  
  $result.= "if (navchoice == '$link') {".  
     $linkitems->{$link}{'cmd'}."}\n";  
     }  
     $result.='}  
               </script>  
                    <form name="linkitems" method="post">  
                        <nobr><select name="toplink">'."\n";  
     foreach my $link (@linkorder) {      foreach my $link (@linkorder) {
  if (defined($linkitems->{$link})) {          my $link_id = 'LC_content_toolbar_'.$link;
     if ($linkitems->{$link}{'text'} ne '') {          if (defined($linkitems->{$link})) {
  $result .= ' <option value="'.$link.'">'.              if ($linkitems->{$link}{'text'} ne '') {
     $linkitems->{$link}{'text'}."</option>\n";                  $linkitems->{$link}{'cmd'}=~s/"/'/g;
     }                  if ($linkitems->{$link}{'cmd'}) {
  }                      if ($link eq 'changefolder') {
                           if ($condition) {
                               $link_id='LC_content_toolbar_changefolder_toggled';
                           } else {
                               $link_id='LC_content_toolbar_changefolder';
                           }
                       }
                       $result .= '<li><a href="#" '.
                                  'onclick="'.$linkitems->{$link}{'cmd'}.'" '.
                                  'id="'.$link_id.'" '.
                                  'class="LC_toolbarItem" '.
                                  'title="'.$linkitems->{$link}{'text'}.'">'.
                                  '</a></li>'."\n";
                   }
               }
           }
     }      }
     $result .= '</select>&nbsp;<input type="button" name="chgnav"      $result .= '</ul>'.
                    value="Go" onClick="javascript:changeNavDisplay()" />                 '</span></td>'."\n";
                 </nobr></form></td>'."\n";  
   
     return $result;      return $result;
 }  }
   
   
 1;  1;
   
   
   
   
   
   
   
   
   
 package Apache::lonnavmaps::navmap;  package Apache::lonnavmaps::navmap;
   
 =pod  =pod
Line 1828  sub new { Line 1984  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
   
       $self->{USERNAME} = shift || $env{'user.name'};
       $self->{DOMAIN}   = shift || $env{'user.domain'};
   
   
   
     # Resource cache stores navmap resources as we reference them. We generate      # Resource cache stores navmap resources as we reference them. We generate
     # them on-demand so we don't pay for creating resources unless we use them.      # them on-demand so we don't pay for creating resources unless we use them.
Line 1837  sub new { Line 1999  sub new {
     # failed      # failed
     $self->{NETWORK_FAILURE} = 0;      $self->{NETWORK_FAILURE} = 0;
   
     # tie the nav hash      # 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
       # assume there are course hashes for the specific requested user@domamin:
       #
   
     my %navmaphash;      if ( ($self->{USERNAME} eq $env{'user.name'}) && ($self->{DOMAIN} eq $env{'user.domain'})) {
     my %parmhash;  
     my $courseFn = $env{"request.course.fn"};   # tie the nav hash
     if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",  
               &GDBM_READER(), 0640))) {   my %navmaphash;
         return undef;   my %parmhash;
     }   my $courseFn = $env{"request.course.fn"};
        if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
     if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",    &GDBM_READER(), 0640))) {
               &GDBM_READER(), 0640)))      return undef;
     {   }
         untie %{$self->{PARM_HASH}};  
         return undef;   if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
     &GDBM_READER(), 0640)))
    {
       untie %{$self->{PARM_HASH}};
       return undef;
    }
   
    $self->{NAV_HASH} = \%navmaphash;
    $self->{PARM_HASH} = \%parmhash;
    $self->{PARM_CACHE} = {};
       } else {
    $self->change_user($self->{USERNAME}, $self->{DOMAIN});
     }      }
   
     $self->{NAV_HASH} = \%navmaphash;      my $d = Data::Dumper->new([$self]);
     $self->{PARM_HASH} = \%parmhash;  
     $self->{PARM_CACHE} = {};  
   
     bless($self);  
           
     return $self;      return $self;
 }  }
   
   #
   #  In some instances it is useful to be able to dynamically change the
   # username/domain associated with a navmap (e.g. to navigate for someone
   # else besides the current user...if sufficiently privileged.
   # Parameters:
   #    user  - New user.
   #    domain- Domain the user belongs to.
   # Implicit inputs:
   #   
   sub change_user {
       my $self = shift;
       $self->{USERNAME} = shift;
       $self->{DOMAIN}   = shift;
   
       # If the hashes are already tied make sure to break that bond:
   
       untie %{$self->{NAV_HASH}}; 
       untie %{$self->{PARM_HASH}};
   
       # The assumption is that we have to
       # use lonmap here to re-read the hash and from it reconstruct
       # new big and parameter hashes.  An implicit assumption at this time
       # is that the course file is probably not created locally yet
       # an that we will therefore just read without tying.
   
       my ($cdom, $cnum) = split(/\_/, $env{'request.course.id'});
   
       my %big_hash;
       &Apache::lonmap::loadmap($cnum, $cdom, $self->{USERNAME}, $self->{DOMAIN}, \%big_hash);
       $self->{NAV_HASH} = \%big_hash;
   
   
   
       # Now clear the parm cache and reconstruct the parm hash fromt he big_hash
       # param.xxxx keys.
   
       $self->{PARM_CACHE} = {};
       
       my %parm_hash = {};
       foreach my $key (keys %big_hash) {
    if ($key =~ /^param\./) {
       my $param_key = $key;
       $param_key =~ s/^param\.//;
       $parm_hash{$param_key} = $big_hash{$key};
    }
       }
   
       $self->{PARM_HASH} = \%parm_hash;
      
   
   
       
   } 
   
 sub generate_course_user_opt {  sub generate_course_user_opt {
     my $self = shift;      my $self = shift;
     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }      if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
   
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     my $cid=$env{'request.course.id'};      my $cid=$env{'request.course.id'};
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
Line 1911  sub generate_email_discuss_status { Line 2138  sub generate_email_discuss_status {
     my $cdom=$env{'course.'.$cid.'.domain'};      my $cdom=$env{'course.'.$cid.'.domain'};
     my $cnum=$env{'course.'.$cid.'.num'};      my $cnum=$env{'course.'.$cid.'.num'};
           
     my %emailstatus = &Apache::lonnet::dump('email_status');      my %emailstatus = &Apache::lonnet::dump('email_status',$self->{DOMAIN},$self->{USERNAME});
     my $logoutTime = $emailstatus{'logout'};      my $logoutTime = $emailstatus{'logout'};
     my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};      my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?      $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
Line 1919  sub generate_email_discuss_status { Line 2146  sub generate_email_discuss_status {
     my %discussiontime = &Apache::lonnet::dump('discussiontimes',       my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
        $cdom, $cnum);         $cdom, $cnum);
     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',      my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
                                         $env{'user.domain'},$env{'user.name'},'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;
Line 1929  sub generate_email_discuss_status { Line 2156  sub generate_email_discuss_status {
   
     my %feedback=();      my %feedback=();
     my %error=();      my %error=();
     my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},      my @keys = &Apache::lonnet::getkeys('nohist_email',$self->{DOMAIN},
  $env{'user.name'});   $self->{USERNAME});
           
     foreach my $msgid (@keys) {      foreach my $msgid (@keys) {
  if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {   if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
Line 1978  sub get_user_data { Line 2205  sub get_user_data {
   
     # Retrieve performance data on problems      # Retrieve performance data on problems
     my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},      my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
    $env{'user.domain'},     $self->{DOMAIN},
    $env{'user.name'});     $self->{USERNAME});
     $self->{STUDENT_DATA} = \%student_data;      $self->{STUDENT_DATA} = \%student_data;
   
     $self->{RETRIEVED_USER_DATA} = 1;      $self->{RETRIEVED_USER_DATA} = 1;
Line 2208  resource object. Line 2435  resource object.
 Based on the symb of the resource, get a resource object for that  Based on the symb of the resource, get a resource object for that
 resource. This is one of the proper ways to get a resource object.  resource. This is one of the proper ways to get a resource object.
   
 =item * B<getMapByMapPc>(map_pc):  =item * B<getByMapPc>(map_pc):
   
 Based on the map_pc of the resource, get a resource object for  Based on the map_pc of the resource, get a resource object for
 the given map. This is one of the proper ways to get a resource object.  the given map. This is one of the proper ways to get a resource object.
Line 2293  sub parmval { Line 2520  sub parmval {
     my $self = shift;      my $self = shift;
     my ($what,$symb,$recurse)=@_;      my ($what,$symb,$recurse)=@_;
     my $hashkey = $what."|||".$symb;      my $hashkey = $what."|||".$symb;
       my $cache = $self->{PARM_CACHE};
     if (defined($self->{PARM_CACHE}->{$hashkey})) {      if (defined($self->{PARM_CACHE}->{$hashkey})) {
        if (wantarray) {          if (ref($self->{PARM_CACHE}->{$hashkey}) eq 'ARRAY') { 
            return @{$self->{PARM_CACHE}->{$hashkey}};              if (defined($self->{PARM_CACHE}->{$hashkey}->[0])) {
        } else {                  if (wantarray) {
            return $self->{PARM_CACHE}->{$hashkey}->[0];                      return @{$self->{PARM_CACHE}->{$hashkey}};
        }                  } else {
                       return $self->{PARM_CACHE}->{$hashkey}->[0];
                   }
               }
           } else {
               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) {
        return @{$result};          return @{$result};
     }      }
     return $result->[0];      return $result->[0];
 }  }
Line 2325  sub parmval_real { Line 2557  sub parmval_real {
         @cgrps = sort(@cgrps);          @cgrps = sort(@cgrps);
         $cgroup = $cgrps[0];          $cgroup = $cgrps[0];
     }       } 
     my $uname=$env{'user.name'};      my $uname=$self->{USERNAME};
     my $udom=$env{'user.domain'};      my $udom=$self->{DOMAIN};
   
     unless ($symb) { return ['']; }      unless ($symb) { return ['']; }
     my $result='';      my $result='';
Line 2473  in the filter function. Line 2705  in the filter function.
 Retrieves version infomation for a url. Returns the version (a number, or   Retrieves version infomation for a url. Returns the version (a number, or 
 the string "mostrecent") for resources which have version information in    the string "mostrecent") for resources which have version information in  
 the big hash.  the big hash.
       
 =cut  =cut
   
   
Line 2728  sub new { Line 2960  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Handle the parameters      # Handle the parameters
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
Line 2841  sub new { Line 3076  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();
         return    my $result;
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,   my $rdump  = Data::Dumper->new([$result]);
                                               $finishResource, $self->{FILTER},   $result =  Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $self->{ALREADY_SEEN},        $finishResource, $self->{FILTER},
                                               $self->{CONDITION},       $self->{ALREADY_SEEN}, 
       $self->{FORCE_TOP});       $self->{CONDITION},
        $self->{FORCE_TOP});
    return $result;
              
                   
     }      }
   
Line 2866  sub new { Line 3104  sub new {
     $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;      $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;
   
     bless ($self);      bless ($self);
       my $selfDump = Data::Dumper->new([$self]);
   
     return $self;      return $self;
 }  }
Line 2881  sub next { Line 3120  sub next {
     # do so.      # do so.
     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {      if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
         $self->{HAVE_RETURNED_0} = 1;          $self->{HAVE_RETURNED_0} = 1;
    my $nextTopLevel = $self->{NAV_MAP}->getById('0.0');
   
         return $self->{NAV_MAP}->getById('0.0');          return $self->{NAV_MAP}->getById('0.0');
     }      }
     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {      if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0_BEGIN_MAP}) {
Line 2900  sub next { Line 3141  sub next {
         if ($self->{RECURSIVE_DEPTH} == 0) {          if ($self->{RECURSIVE_DEPTH} == 0) {
             $self->{RECURSIVE_ITERATOR_FLAG} = 0;              $self->{RECURSIVE_ITERATOR_FLAG} = 0;
         }          }
   
         return $next;          return $next;
     }      }
   
     if (defined($self->{FORCE_NEXT})) {      if (defined($self->{FORCE_NEXT})) {
         my $tmp = $self->{FORCE_NEXT};          my $tmp = $self->{FORCE_NEXT};
         $self->{FORCE_NEXT} = undef;          $self->{FORCE_NEXT} = undef;
   
         return $tmp;          return $tmp;
     }      }
   
Line 2976  sub next { Line 3217  sub next {
     # So we need to look at all the resources we can get to from here,      # So we need to look at all the resources we can get to from here,
     # categorize them if we haven't seen them, remember if we have a new      # categorize them if we haven't seen them, remember if we have a new
     my $nextUnfiltered = $here->getNext();      my $nextUnfiltered = $here->getNext();
   
   
     my $maxDepthAdded = -1;      my $maxDepthAdded = -1;
           
     for (@$nextUnfiltered) {      for (@$nextUnfiltered) {
Line 3009  sub next { Line 3252  sub next {
         $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();
   
         $self->{RECURSIVE_ITERATOR} =           $self->{RECURSIVE_ITERATOR} = 
             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,              Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                                               $finishResource, $self->{FILTER},                                                $finishResource, $self->{FILTER},
Line 3027  sub next { Line 3269  sub next {
         return $self->next($closeAllPages);          return $self->next($closeAllPages);
     }      }
   
       my $hereResource = $self->{HERE};
   
     return $self->{HERE};      return $self->{HERE};
   
 }  }
Line 3101  sub new { Line 3345  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     return undef unless ($self->{NAV_MAP});      return undef unless ($self->{NAV_MAP});
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();      $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();      $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
   
Line 3190  sub next { Line 3437  sub next {
     my $nextUnfiltered;      my $nextUnfiltered;
     if ($self->{DIRECTION} == FORWARD()) {      if ($self->{DIRECTION} == FORWARD()) {
         $nextUnfiltered = $self->{HERE}->getNext();          $nextUnfiltered = $self->{HERE}->getNext();
   
     } else {      } else {
         $nextUnfiltered = $self->{HERE}->getPrevious();          $nextUnfiltered = $self->{HERE}->getPrevious();
     }      }
Line 3337  sub new { Line 3585  sub new {
     weaken($self->{NAV_MAP} = shift);      weaken($self->{NAV_MAP} = shift);
     $self->{ID} = shift;      $self->{ID} = shift;
   
       $self->{USERNAME} = $self->{NAV_MAP}->{USERNAME};
       $self->{DOMAIN}   = $self->{NAV_MAP}->{DOMAIN};
   
     # Store this new resource in the parent nav map's cache.      # Store this new resource in the parent nav map's cache.
     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;      $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
     $self->{RESOURCE_ERROR} = 0;      $self->{RESOURCE_ERROR} = 0;
Line 3358  sub navHash { Line 3609  sub navHash {
     my $self = shift;      my $self = shift;
     my $param = shift;      my $param = shift;
     my $id = shift;      my $id = shift;
     return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:""));      my $arg = $param . ($id?$self->{ID}:"");
       if (ref($self) && ref($self->{NAV_MAP}) && defined($arg)) {
           return $self->{NAV_MAP}->navhash($arg);
       }
       return;
 }  }
   
 =pod  =pod
Line 3436  sub kind { my $self=shift; return $self- Line 3691  sub kind { my $self=shift; return $self-
 sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }  sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
 sub randompick {   sub randompick { 
     my $self = shift;      my $self = shift;
     return $self->parmval('randompick');      my $randompick = $self->parmval('randompick');
       return $randompick;
 }  }
 sub randomorder {   sub randomorder { 
     my $self = shift;      my $self = shift;
     return ($self->parmval('randomorder') =~ /^yes$/i);      my $randomorder = $self->parmval('randomorder');
       return ($randomorder =~ /^yes$/i);
 }  }
 sub link {  sub link {
     my $self=shift;      my $self=shift;
Line 3561  sub retrieveResources { Line 3818  sub retrieveResources {
   
 sub is_exam {  sub is_exam {
     my ($self,$part) = @_;      my ($self,$part) = @_;
     if ($self->parmval('type',$part) eq 'exam') {      my $type = $self->parmval('type',$part);
       if ($type eq 'exam') {
         return 1;          return 1;
     }      }
     if ($self->src() =~ /\.(exam)$/) {      if ($self->src() =~ /\.(exam)$/) {
Line 3584  sub is_page { Line 3842  sub is_page {
 sub is_practice {  sub is_practice {
     my $self=shift;      my $self=shift;
     my ($part) = @_;      my ($part) = @_;
     if ($self->parmval('type',$part) eq 'practice') {      my $type = $self->parmval('type',$part);
       if ($type eq 'practice') {
         return 1;          return 1;
     }      }
     return 0;      return 0;
Line 3592  sub is_practice { Line 3851  sub is_practice {
 sub is_problem {  sub is_problem {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
     if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {      if ($src =~ /$LONCAPA::assess_re/) {
  return !($self->is_practice());   return !($self->is_practice());
     }      }
     return 0;      return 0;
 }  }
   #
   #  The has below is the set of status that are considered 'incomplete'
   #
   my %incomplete_hash = 
   (
    TRIES_LEFT()     => 1,
    OPEN()           => 1,
    ATTEMPTED()      => 1
   
    );
   #
   #  Return tru if a problem is incomplete... for now incomplete means that
   #  any part of the problem is incomplete. 
   #  Note that if the resources is not a problem, 0 is returned.
   #
   sub is_incomplete {
       my $self = shift;
       if ($self->is_problem()) {
    foreach my $part (@{$self->parts()}) {
       if (exists($incomplete_hash{$self->status($part)})) {
    return 1;
       }
    }
       }
       return 0;
          
   }
   sub is_raw_problem {
       my $self=shift;
       my $src = $self->src();
       if ($src =~ /$LONCAPA::assess_re/) {
           return 1;
       }
       return 0;
   }
   
 sub contains_problem {  sub contains_problem {
     my $self=shift;      my $self=shift;
     if ($self->is_page()) {      if ($self->is_page()) {
Line 3617  sub map_contains_problem { Line 3912  sub map_contains_problem {
 sub is_sequence {  sub is_sequence {
     my $self=shift;      my $self=shift;
     return $self->navHash("is_map_", 1) &&       return $self->navHash("is_map_", 1) && 
  $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';      $self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
 }  }
 sub is_survey {  sub is_survey {
     my $self = shift();      my $self = shift();
     my $part = shift();      my $part = shift();
     if ($self->parmval('type',$part) eq 'survey') {      my $type = $self->parmval('type',$part);
       if (($type eq 'survey') || ($type eq 'surveycred')) {
         return 1;          return 1;
     }      }
     if ($self->src() =~ /\.(survey)$/) {      if ($self->src() =~ /\.(survey)$/) {
Line 3630  sub is_survey { Line 3926  sub is_survey {
     }      }
     return 0;      return 0;
 }  }
   sub is_anonsurvey {
       my $self = shift();
       my $part = shift();
       my $type = $self->parmval('type',$part);
       if (($type eq 'anonsurvey') || ($type eq 'anonsurveycred')) {
           return 1;
       }
       return 0;
   }
 sub is_task {  sub is_task {
     my $self=shift;      my $self=shift;
     my $src = $self->src();      my $src = $self->src();
Line 3683  resource of the map. Line 3988  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>:
   
   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
   map first, then descending to deeper levels, with the enclosing map last.
   
 =back  =back
   
 =cut  =cut
Line 3713  sub map_type { Line 4024  sub map_type {
     my $pc = $self->map_pc();      my $pc = $self->map_pc();
     return $self->navHash("map_type_$pc", 0);      return $self->navHash("map_type_$pc", 0);
 }  }
   sub map_hierarchy {
       my $self = shift;
       my $pc = $self->map_pc();
       return $self->navHash("map_hierarchy_$pc", 0);
   }
   
 #####  #####
 # Property queries  # Property queries
Line 3743  their code.) Line 4059  their code.)
   
 =over 4  =over 4
   
 =item * B<acc>:  =item * B<printable>
   
   returns true if the current date is such that the 
   specified resource part is printable.
   
   =item * B<resprintable>
   
   Returns true if all parts in the resource are printable making the
   entire resource printable.
   
   =item * B<acc>
   
 Get the Client IP/Name Access Control information.  Get the Client IP/Name Access Control information.
   
Line 3796  Get the weight for the problem. Line 4122  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("prinstartdate", $part);
       my $end   = $self->parmval("printenddate", $part);
   
       #  The following cases apply:
       #  - No dates set: Printable.
       #  - Start date set but no end date: Printable if now >= start date.
       #  - End date set but no start date: Printable if now <= end date.
       #  - both defined: printable if start <= now <= end
       #
       my $now  = time();
   
       my $startok = 1;
       my $endok   = 1;
   
       if ((defined $start) && ($start ne '')) {
    $startok = $start <= $now;
       }
       if ((defined $end) && ($end != '')) {
    $endok = $end >= $now;
       }
       return $startok && $endok;
   }
   
   sub resprintable {
       my $self = shift;
   
       # get parts...or realize there are no parts.
   
       my $partsref = $self->parts();
       my @parts    = @$partsref;
   
       if ((!defined(@parts)) || (scalar(@parts) == 0)) {
    return $self->printable(0);
       } else {
    foreach my $part  (@parts) {
       if (!$self->printable($part)) { 
    return 0; 
       }
    }
    return 1;
       }
   }
   
 sub acc {  sub acc {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("acc", $part);      my $acc = $self->parmval("acc", $part);
       return $acc;
 }  }
 sub answerdate {  sub answerdate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     # Handle intervals      # Handle intervals
     if ($self->parmval("answerdate.type", $part) eq 'date_interval') {      my $answerdatetype = $self->parmval("answerdate.type", $part);
         return $self->duedate($part) +       my $answerdate = $self->parmval("answerdate", $part);
             $self->parmval("answerdate", $part);      my $duedate = $self->parmval("duedate", $part);
       if ($answerdatetype eq 'date_interval') {
           $answerdate = $duedate + $answerdate; 
     }      }
     return $self->parmval("answerdate", $part);      return $answerdate;
 }  }
 sub awarded {   sub awarded { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
Line 3815  sub awarded { Line 4193  sub awarded {
     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 {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.version'};
   }
   sub taskstatus {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$self->taskversion($part).'.'.$part.'.status'};
   }
   sub solved {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.solved'};
   }
   sub checkedin {
       my $self = shift; my $part = shift;
       $self->{NAV_MAP}->get_user_data();
       if (!defined($part)) { $part = '0'; }
       if ($self->is_task()) {
           my $version = $self->taskversion($part);
           return ($self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin'},$self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$version .'.'.$part.'.checkedin.slot'});
       } else {
           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
   # Why is there a copy in lonhomework?  Why not centralized?
   #
   #  TODO: Centralize duedate.
   #
   
 sub duedate {  sub duedate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     my $date;      my $date;
Line 3841  sub handgrade { Line 4253  sub handgrade {
     my @response_ids = $self->responseIds($part);      my @response_ids = $self->responseIds($part);
     if (@response_ids) {      if (@response_ids) {
  foreach my $response_id (@response_ids) {   foreach my $response_id (@response_ids) {
     if (lc($self->parmval("handgrade",$part.'_'.$response_id))              my $handgrade = $self->parmval("handgrade",$part.'_'.$response_id);
  eq 'yes') {      if (lc($handgrade) eq 'yes') {
  return 'yes';   return 'yes';
     }      }
  }   }
     }      }
     return $self->parmval("handgrade", $part);      my $handgrade = $self->parmval("handgrade", $part);
       return $handgrade;
 }  }
 sub maxtries {  sub maxtries {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("maxtries", $part);      my $maxtries = $self->parmval("maxtries", $part);
       return $maxtries;
 }  }
 sub opendate {  sub opendate {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     if ($self->parmval("opendate.type", $part) eq 'date_interval') {      my $opendatetype = $self->parmval("opendate.type", $part);
         return $self->duedate($part) -      my $opendate = $self->parmval("opendate", $part); 
             $self->parmval("opendate", $part);      if ($opendatetype eq 'date_interval') {
           my $duedate = $self->duedate($part);
           $opendate = $duedate - $opendate; 
     }      }
     return $self->parmval("opendate");      return $opendate;
 }  }
 sub problemstatus {  sub problemstatus {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return lc $self->parmval("problemstatus", $part);      my $problemstatus = $self->parmval("problemstatus", $part);
       return lc($problemstatus);
 }  }
 sub sig {  sub sig {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("sig", $part);      my $sig = $self->parmval("sig", $part);
       return $sig;
 }  }
 sub tol {  sub tol {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("tol", $part);      my $tol = $self->parmval("tol", $part);
       return $tol;
 }  }
 sub tries {   sub tries {
     my $self = shift;       my $self = shift; 
     my $tries = $self->queryRestoreHash('tries', shift);      my $tries = $self->queryRestoreHash('tries', shift);
     if (!defined($tries)) { return '0';}      if (!defined($tries)) { return '0';}
Line 3881  sub tries { Line 4300  sub tries {
 }  }
 sub type {  sub type {
     (my $self, my $part) = @_;      (my $self, my $part) = @_;
     return $self->parmval("type", $part);      my $type = $self->parmval("type", $part);
       return $type;
 }  }
 sub weight {   sub weight { 
     my $self = shift; my $part = shift;      my $self = shift; my $part = shift;
     if (!defined($part)) { $part = '0'; }      if (!defined($part)) { $part = '0'; }
     return &Apache::lonnet::EXT('resource.'.$part.'.weight',      my $weight = &Apache::lonnet::EXT('resource.'.$part.'.weight',
  $self->symb(), $env{'user.domain'},                                  $self->symb(), $self->{DOMAIN},
  $env{'user.name'},                                   $self->{USERNAME},
  $env{'request.course.sec'});                                  $env{'request.course.sec'});
       return $weight;
 }  }
 sub part_display {  sub part_display {
     my $self= shift(); my $partID = shift();      my $self= shift(); my $partID = shift();
Line 3901  sub part_display { Line 4322  sub part_display {
     }      }
     return $display;      return $display;
 }  }
   sub slot_control {
       my $self=shift(); my $part = shift();
       if (!defined($part)) { $part = '0'; }
       my $useslots = $self->parmval("useslots", $part);
       my $availablestudent = $self->parmval("availablestudent", $part);
       my $available = $self->parmval("available", $part); 
       return ($useslots,$availablestudent,$available);
   }
   
 # 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());          my %tmpHash  = &Apache::lonnet::restore($self->symb(),undef,$self->{DOMAIN},$self->{USERNAME});
         $self->{RETURN_HASH} = \%tmpHash;          $self->{RETURN_HASH} = \%tmpHash;
     }      }
 }         }       
Line 4333  sub OPEN                   { return 1; } Line 4762  sub OPEN                   { return 1; }
 sub PAST_DUE_NO_ANSWER     { return 2; }  sub PAST_DUE_NO_ANSWER     { return 2; }
 sub PAST_DUE_ANSWER_LATER  { return 3; }  sub PAST_DUE_ANSWER_LATER  { return 3; }
 sub ANSWER_OPEN            { return 4; }  sub ANSWER_OPEN            { return 4; }
 sub NOTHING_SET            { return 5; }   sub NOTHING_SET            { return 5; }
 sub NETWORK_FAILURE        { return 100; }  sub NETWORK_FAILURE        { return 100; }
   
 # getDateStatus gets the date status for a given problem part.   # getDateStatus gets the date status for a given problem part. 
Line 4419  Information not available due to network Line 4848  Information not available due to network
   
 Attempted, and not yet graded.  Attempted, and not yet graded.
   
   =item * B<CREDIT_ATTEMPTED>:
   
   Attempted, and credit received for attempt (survey and anonymous survey only).
   
 =back  =back
   
 =cut  =cut
Line 4430  sub CORRECT               { return 13; } Line 4863  sub CORRECT               { return 13; }
 sub CORRECT_BY_OVERRIDE   { return 14; }  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 getCompletionStatus {  sub getCompletionStatus {
     my $self = shift;      my $self = shift;
Line 4448  sub getCompletionStatus { Line 4882  sub getCompletionStatus {
     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 '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 ($self->is_anonsurvey($part) || $self->is_survey($part)) {
               return $self->CREDIT_ATTEMPTED;
           } else {
               return $self->ATTEMPTED;
           }
       }
     return $self->NOT_ATTEMPTED;      return $self->NOT_ATTEMPTED;
 }  }
   
Line 4537  The item is open and not yet tried. Line 4978  The item is open and not yet tried.
   
 The problem has been attempted.  The problem has been attempted.
   
   =item * B<CREDIT_ATTEMPTED>:
   
   The problem has been attempted, and credit given for the attempt (survey and anonymous survey only).
   
 =item * B<ANSWER_SUBMITTED>:  =item * B<ANSWER_SUBMITTED>:
   
 An answer has been submitted, but the student should not see it.  An answer has been submitted, but the student should not see it.
Line 4545  An answer has been submitted, but the st Line 4990  An answer has been submitted, but the st
   
 =cut  =cut
   
 sub TRIES_LEFT       { return 20; }  sub TRIES_LEFT        { return 20; }
 sub ANSWER_SUBMITTED { return 21; }  sub ANSWER_SUBMITTED  { return 21; }
 sub PARTIALLY_CORRECT{ return 22; }  sub PARTIALLY_CORRECT { return 22; }
   
   sub RESERVED_LATER    { return 30; }
   sub RESERVED          { return 31; }
   sub RESERVED_LOCATION { return 32; }
   sub RESERVABLE        { return 33; }
   sub RESERVABLE_LATER  { return 34; }
   sub NOTRESERVABLE     { return 35; }
   sub NOT_IN_A_SLOT     { return 36; }
   sub NEEDS_CHECKIN     { return 37; }
   sub WAITING_FOR_GRADE { return 38; }
   sub UNKNOWN           { return 39; }
   
 sub status {  sub status {
     my $self = shift;      my $self = shift;
Line 4563  sub status { Line 5019  sub status {
     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }      #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }      if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
   
     my $suppressFeedback = $self->problemstatus($part) eq 'no';      my $suppressFeedback = 0;
       if (($self->problemstatus($part) eq 'no') ||
           ($self->problemstatus($part) eq 'no_feedback_ever')) {
           $suppressFeedback = 1;
       }
     # If there's an answer date and we're past it, don't      # If there's an answer date and we're past it, don't
     # suppress the feedback; student should know      # suppress the feedback; student should know
     if ($self->duedate($part) && $self->duedate($part) < time() &&      if ($self->duedate($part) && $self->duedate($part) < time() &&
Line 4595  sub status { Line 5055  sub status {
         return ATTEMPTED;          return ATTEMPTED;
     }      }
   
       if ($completionStatus == CREDIT_ATTEMPTED) {
           return CREDIT_ATTEMPTED;
       }
   
     # If it's EXCUSED, then return that no matter what      # If it's EXCUSED, then return that no matter what
     if ($completionStatus == EXCUSED) {      if ($completionStatus == EXCUSED) {
         return EXCUSED;           return EXCUSED; 
Line 4633  sub status { Line 5097  sub status {
     }      }
   
     # Otherwise, it's untried and open      # Otherwise, it's untried and open
     return OPEN;       return OPEN;
   }
   
   sub check_for_slot {
       my $self = shift;
       my $part = shift;
       my $symb = $self->symb();
       my ($use_slots,$available,$availablestudent) = $self->slot_control($part);
       if (($use_slots ne '') && ($use_slots !~ /^\s*no\s*$/i)) {
           my @slots = (split(/:/,$availablestudent),split(/:/,$available));
           my $cid=$env{'request.course.id'};
           my $cdom=$env{'course.'.$cid.'.domain'};
           my $cnum=$env{'course.'.$cid.'.num'};
           my $now = time;
           my $num_usable_slots = 0;
           if (@slots > 0) {
               my %slots=&Apache::lonnet::get('slots',[@slots],$cdom,$cnum);
               if (&Apache::lonnet::error(%slots)) {
                   return (UNKNOWN);
               }
               my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime');
               my ($checkedin,$checkedinslot);
               foreach my $slot_name (@sorted_slots) {
                   next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name}));
                   my $end = $slots{$slot_name}->{'endtime'};
                   my $start = $slots{$slot_name}->{'starttime'};
                   my $ip = $slots{$slot_name}->{'ip'};
                   if ($self->simpleStatus() == OPEN) {
                       if ($end > $now) {
                           if ($start > $now) {
                               return (RESERVED_LATER,$start,$slot_name);
                           } else {
                               if ($ip ne '') {
                                   if (!&Apache::loncommon::check_ip_acc($ip)) {
                                       return (RESERVED_LOCATION,$ip,$slot_name);
                                   }
                               }
                               my @proctors;
                               if ($slots{$slot_name}->{'proctor'} ne '') {
                                   @proctors = split(',',$slots{$slot_name}->{'proctor'});
                               }
                               if (@proctors > 0) {
                                   ($checkedin,$checkedinslot) = $self->checkedin();
                                   unless ((grep(/^\Q$checkedin\E/,@proctors)) &&
                                           ($checkedinslot eq $slot_name)) {
                                       return (NEEDS_CHECKIN,undef,$slot_name); 
                                   }
                               }
                               return (RESERVED,$end,$slot_name);
                           }
                       }
                   } elsif ($end > $now) {
                       $num_usable_slots ++;
                   }
               }
               my ($is_correct,$got_grade);
               if ($self->is_task()) {
                   my $taskstatus = $self->taskstatus();
                   $is_correct = (($taskstatus eq 'pass') || 
                                  ($self->solved() =~ /^correct_/));
                   $got_grade = ($taskstatus =~ /^(?:pass|fail)$/);
               } else {
                   $got_grade = 1;
                   $is_correct = ($self->solved() =~ /^correct_/);   
               }
               ($checkedin,$checkedinslot) = $self->checkedin();
               if ($checkedin) {
                   if (!$got_grade) {
                       return (WAITING_FOR_GRADE);
                   } elsif ($is_correct) {
                       return (CORRECT); 
                   }
               }
               if ($num_usable_slots) {
                   return(NOT_IN_A_SLOT);
               }
           }
           my $reservable = &Apache::lonnet::get_reservable_slots($cnum,$cdom,$env{'user.name'},
                                                                  $env{'user.domain'});
           if (ref($reservable) eq 'HASH') {
               if ((ref($reservable->{'now_order'}) eq 'ARRAY') && (ref($reservable->{'now'}) eq 'HASH')) {
                   foreach my $slot (reverse (@{$reservable->{'now_order'}})) {
                       if (($reservable->{'now'}{$slot}{'symb'} eq '') ||
                           ($reservable->{'now'}{$slot}{'symb'} eq $symb)) {
                           return(RESERVABLE,$reservable->{'now'}{$slot}{'endreserve'});
                       }
                   }
               }
               if ((ref($reservable->{'future_order'}) eq 'ARRAY') && (ref($reservable->{'future'}) eq 'HASH')) {
                   foreach my $slot (@{$reservable->{'future_order'}}) {
                       if (($reservable->{'future'}{$slot}{'symb'} eq '') ||
                           ($reservable->{'future'}{$slot}{'symb'} eq $symb)) {
                           return(RESERVABLE_LATER,$reservable->{'future'}{$slot}{'startreserve'});
                       }
                   }
               }
           }
           return(NOTRESERVABLE);
       }
       return;
 }  }
   
 sub CLOSED { return 23; }  sub CLOSED { return 23; }
Line 4685  my %compositeToSimple = Line 5248  my %compositeToSimple =
       INCORRECT()             => INCORRECT,        INCORRECT()             => INCORRECT,
       OPEN()                  => OPEN,        OPEN()                  => OPEN,
       ATTEMPTED()             => ATTEMPTED,        ATTEMPTED()             => ATTEMPTED,
         CREDIT_ATTEMPTED()      => CORRECT,
       ANSWER_SUBMITTED()      => ATTEMPTED        ANSWER_SUBMITTED()      => ATTEMPTED
      );       );
   
Line 4759  sub completable { Line 5323  sub completable {
         # and it is not "attempted" (manually graded problem), it is          # and it is not "attempted" (manually graded problem), it is
         # not "complete"          # not "complete"
  if ($self->getCompletionStatus($part) == ATTEMPTED() ||   if ($self->getCompletionStatus($part) == ATTEMPTED() ||
               $self->getCompletionStatus($part) == CREDIT_ATTEMPTED() ||
     $status == ANSWER_SUBMITTED() ) {      $status == ANSWER_SUBMITTED() ) {
     # did this part already, as well as we can      # did this part already, as well as we can
     next;      next;

Removed from v.1.406  
changed lines
  Added in v.1.475


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