Annotation of loncom/interface/lonnavmaps.pm, revision 1.397

1.2       www         1: # The LearningOnline Network with CAPA
                      2: # Navigate Maps Handler
1.1       www         3: #
1.397   ! albertel    4: # $Id: lonnavmaps.pm,v 1.396 2007/01/05 06:43:34 raeburn Exp $
1.20      albertel    5: #
                      6: # Copyright Michigan State University Board of Trustees
                      7: #
                      8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                      9: #
                     10: # LON-CAPA is free software; you can redistribute it and/or modify
                     11: # it under the terms of the GNU General Public License as published by
                     12: # the Free Software Foundation; either version 2 of the License, or
                     13: # (at your option) any later version.
                     14: #
                     15: # LON-CAPA is distributed in the hope that it will be useful,
                     16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     18: # GNU General Public License for more details.
                     19: #
                     20: # You should have received a copy of the GNU General Public License
                     21: # along with LON-CAPA; if not, write to the Free Software
                     22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     23: #
                     24: # /home/httpd/html/adm/gpl.txt
                     25: #
                     26: # http://www.lon-capa.org/
                     27: #
1.245     www        28: ###
1.2       www        29: 
1.1       www        30: package Apache::lonnavmaps;
                     31: 
                     32: use strict;
1.317     albertel   33: use GDBM_File;
1.18      albertel   34: use Apache::loncommon();
1.305     albertel   35: use Apache::lonenc();
1.229     www        36: use Apache::lonlocal;
1.306     foxr       37: use Apache::lonnet;
1.73      bowersj2   38: use POSIX qw (floor strftime);
1.300     albertel   39: use Data::Dumper; # for debugging, not always 
1.316     albertel   40: use Time::HiRes qw( gettimeofday tv_interval );
1.384     www        41: use LONCAPA;
1.2       www        42: 
1.133     bowersj2   43: # symbolic constants
                     44: sub SYMB { return 1; }
                     45: sub URL { return 2; }
                     46: sub NOTHING { return 3; }
                     47: 
                     48: # Some data
                     49: 
1.140     bowersj2   50: my $resObj = "Apache::lonnavmaps::resource";
                     51: 
1.135     bowersj2   52: # Keep these mappings in sync with lonquickgrades, which uses the colors
                     53: # instead of the icons.
                     54: my %statusIconMap = 
1.224     bowersj2   55:     (
                     56:      $resObj->CLOSED       => '',
                     57:      $resObj->OPEN         => 'navmap.open.gif',
                     58:      $resObj->CORRECT      => 'navmap.correct.gif',
1.355     www        59:      $resObj->PARTIALLY_CORRECT      => 'navmap.partial.gif',
1.224     bowersj2   60:      $resObj->INCORRECT    => 'navmap.wrong.gif',
                     61:      $resObj->ATTEMPTED    => 'navmap.ellipsis.gif',
                     62:      $resObj->ERROR        => ''
                     63:      );
1.135     bowersj2   64: 
                     65: my %iconAltTags = 
                     66:     ( 'navmap.correct.gif' => 'Correct',
                     67:       'navmap.wrong.gif'   => 'Incorrect',
                     68:       'navmap.open.gif'    => 'Open' );
1.133     bowersj2   69: 
1.136     bowersj2   70: # Defines a status->color mapping, null string means don't color
                     71: my %colormap = 
1.140     bowersj2   72:     ( $resObj->NETWORK_FAILURE        => '',
                     73:       $resObj->CORRECT                => '',
                     74:       $resObj->EXCUSED                => '#3333FF',
                     75:       $resObj->PAST_DUE_ANSWER_LATER  => '',
                     76:       $resObj->PAST_DUE_NO_ANSWER     => '',
                     77:       $resObj->ANSWER_OPEN            => '#006600',
                     78:       $resObj->OPEN_LATER             => '',
                     79:       $resObj->TRIES_LEFT             => '',
                     80:       $resObj->INCORRECT              => '',
                     81:       $resObj->OPEN                   => '',
1.207     bowersj2   82:       $resObj->NOTHING_SET            => '',
                     83:       $resObj->ATTEMPTED              => '',
1.332     albertel   84:       $resObj->ANSWER_SUBMITTED       => '',
                     85:       $resObj->PARTIALLY_CORRECT      => '#006600'
1.207     bowersj2   86:       );
1.136     bowersj2   87: # And a special case in the nav map; what to do when the assignment
                     88: # is not yet done and due in less then 24 hours
                     89: my $hurryUpColor = "#FF0000";
1.130     www        90: 
1.269     albertel   91: sub close {
1.320     albertel   92:     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
1.269     albertel   93:     return(<<ENDCLOSE);
                     94: <script type="text/javascript">
                     95: window.status='Accessing Nav Control';
                     96: menu=window.open("/adm/rat/empty.html","loncapanav",
                     97:                  "height=600,width=400,scrollbars=1");
                     98: window.status='Closing Nav Control';
                     99: menu.close();
                    100: window.status='Done.';
                    101: </script>
                    102: ENDCLOSE
                    103: }
1.268     albertel  104: 
1.270     albertel  105: sub update {
1.320     albertel  106:     if ($env{'environment.remotenavmap'} ne 'on') { return ''; }
                    107:     if (!$env{'request.course.id'}) { return ''; }
1.270     albertel  108:     if ($ENV{'REQUEST_URI'}=~m|^/adm/navmaps|) { return ''; }
                    109:     return(<<ENDUPDATE);
                    110: <form name="navform"></form>
                    111: <script type="text/javascript">
                    112: this.document.navform.action='/adm/navmaps#curloc';
                    113: this.document.navform.target='loncapanav';
                    114: this.document.navform.submit();
                    115: </script>
                    116: ENDUPDATE
                    117: }
1.268     albertel  118: 
1.51      bowersj2  119: # Convenience functions: Returns a string that adds or subtracts
                    120: # the second argument from the first hash, appropriate for the 
                    121: # query string that determines which folders to recurse on
                    122: sub addToFilter {
                    123:     my $hashIn = shift;
                    124:     my $addition = shift;
                    125:     my %hash = %$hashIn;
                    126:     $hash{$addition} = 1;
                    127: 
                    128:     return join (",", keys(%hash));
                    129: }
                    130: 
                    131: sub removeFromFilter {
                    132:     my $hashIn = shift;
                    133:     my $subtraction = shift;
                    134:     my %hash = %$hashIn;
                    135: 
                    136:     delete $hash{$subtraction};
                    137:     return join(",", keys(%hash));
                    138: }
                    139: 
                    140: # Convenience function: Given a stack returned from getStack on the iterator,
                    141: # return the correct src() value.
                    142: sub getLinkForResource {
                    143:     my $stack = shift;
                    144:     my $res;
                    145: 
                    146:     # Check to see if there are any pages in the stack
                    147:     foreach $res (@$stack) {
1.248     www       148:         if (defined($res)) {
1.308     albertel  149: 	    my $anchor;
1.248     www       150: 	    if ($res->is_page()) {
1.394     raeburn   151: 		foreach my $item (@$stack) { if (defined($item)) { $anchor = $item; }  }
1.384     www       152: 		$anchor=&escape($anchor->shown_symb());
1.308     albertel  153: 		return ($res->link(),$res->shown_symb(),$anchor);
1.248     www       154: 	    }
                    155:             # in case folder was skipped over as "only sequence"
                    156: 	    my ($map,$id,$src)=&Apache::lonnet::decode_symb($res->symb());
                    157: 	    if ($map=~/\.page$/) {
1.308     albertel  158: 		my $url=&Apache::lonnet::clutter($map);
1.384     www       159: 		$anchor=&escape($src->shown_symb());
1.308     albertel  160: 		return ($url,$res->shown_symb(),$anchor);
1.248     www       161: 	    }
1.51      bowersj2  162:         }
                    163:     }
                    164: 
                    165:     # Failing that, return the src of the last resource that is defined
                    166:     # (when we first recurse on a map, it puts an undefined resource
                    167:     # on the bottom because $self->{HERE} isn't defined yet, and we
                    168:     # want the src for the map anyhow)
1.394     raeburn   169:     foreach my $item (@$stack) {
                    170:         if (defined($item)) { $res = $item; }
1.51      bowersj2  171:     }
                    172: 
1.308     albertel  173:     return ($res->link(),$res->shown_symb());
1.51      bowersj2  174: }
                    175: 
1.251     www       176: # Convenience function: This separates the logic of how to create
1.53      bowersj2  177: # the problem text strings ("Due: DATE", "Open: DATE", "Not yet assigned",
1.251     www       178: # etc.) into a separate function. It takes a resource object as the
1.53      bowersj2  179: # first parameter, and the part number of the resource as the second.
                    180: # It's basically a big switch statement on the status of the resource.
                    181: 
                    182: sub getDescription {
                    183:     my $res = shift;
                    184:     my $part = shift;
1.69      bowersj2  185:     my $status = $res->status($part);
1.53      bowersj2  186: 
1.182     bowersj2  187:     if ($status == $res->NETWORK_FAILURE) { 
1.229     www       188:         return &mt("Having technical difficulties; please check status later"); 
1.182     bowersj2  189:     }
1.53      bowersj2  190:     if ($status == $res->NOTHING_SET) {
1.229     www       191:         return &mt("Not currently assigned.");
1.53      bowersj2  192:     }
                    193:     if ($status == $res->OPEN_LATER) {
1.341     albertel  194:         return "Open " . timeToHumanString($res->opendate($part),'start');
1.53      bowersj2  195:     }
                    196:     if ($status == $res->OPEN) {
1.79      bowersj2  197:         if ($res->duedate($part)) {
1.341     albertel  198:             return &mt("Due")."  " .timeToHumanString($res->duedate($part),'end');
1.66      bowersj2  199:         } else {
1.229     www       200:             return &mt("Open, no due date");
1.66      bowersj2  201:         }
1.53      bowersj2  202:     }
1.54      bowersj2  203:     if ($status == $res->PAST_DUE_ANSWER_LATER) {
1.341     albertel  204:         return &mt("Answer open")." " . timeToHumanString($res->answerdate($part),'start');
1.54      bowersj2  205:     }
                    206:     if ($status == $res->PAST_DUE_NO_ANSWER) {
1.341     albertel  207:         return &mt("Was due")." " . timeToHumanString($res->duedate($part),'end');
1.53      bowersj2  208:     }
1.357     albertel  209:     if (($status == $res->ANSWER_OPEN || $status == $res->PARTIALLY_CORRECT)
                    210: 	&& $res->handgrade($part) ne 'yes') {
1.229     www       211:         return &mt("Answer available");
1.53      bowersj2  212:     }
1.59      bowersj2  213:     if ($status == $res->EXCUSED) {
1.229     www       214:         return &mt("Excused by instructor");
1.59      bowersj2  215:     }
1.69      bowersj2  216:     if ($status == $res->ATTEMPTED) {
1.229     www       217:         return &mt("Answer submitted, not yet graded");
1.69      bowersj2  218:     }
1.59      bowersj2  219:     if ($status == $res->TRIES_LEFT) {
1.79      bowersj2  220:         my $tries = $res->tries($part);
                    221:         my $maxtries = $res->maxtries($part);
                    222:         my $triesString = "";
                    223:         if ($tries && $maxtries) {
                    224:             $triesString = "<font size=\"-1\"><i>($tries of $maxtries tries used)</i></font>";
                    225:             if ($maxtries > 1 && $maxtries - $tries == 1) {
                    226:                 $triesString = "<b>$triesString</b>";
                    227:             }
                    228:         }
1.244     albertel  229:         if ($res->duedate($part)) {
1.341     albertel  230:             return &mt("Due")." " . timeToHumanString($res->duedate($part),'end') .
1.66      bowersj2  231:                 " $triesString";
                    232:         } else {
1.229     www       233:             return &mt("No due date")." $triesString";
1.66      bowersj2  234:         }
1.59      bowersj2  235:     }
1.185     bowersj2  236:     if ($status == $res->ANSWER_SUBMITTED) {
1.229     www       237:         return &mt('Answer submitted');
1.185     bowersj2  238:     }
1.53      bowersj2  239: }
                    240: 
1.115     bowersj2  241: # Convenience function, so others can use it: Is the problem due in less then
                    242: # 24 hours, and still can be done?
                    243: 
1.252     matthew   244: sub dueInLessThan24Hours {
1.115     bowersj2  245:     my $res = shift;
                    246:     my $part = shift;
                    247:     my $status = $res->status($part);
                    248: 
1.207     bowersj2  249:     return ($status == $res->OPEN() ||
1.115     bowersj2  250:             $status == $res->TRIES_LEFT()) &&
1.244     albertel  251: 	    $res->duedate($part) && $res->duedate($part) < time()+(24*60*60) &&
                    252: 	    $res->duedate($part) > time();
1.115     bowersj2  253: }
                    254: 
                    255: # Convenience function, so others can use it: Is there only one try remaining for the
                    256: # part, with more then one try to begin with, not due yet and still can be done?
                    257: sub lastTry {
                    258:     my $res = shift;
                    259:     my $part = shift;
                    260: 
                    261:     my $tries = $res->tries($part);
                    262:     my $maxtries = $res->maxtries($part);
                    263:     return $tries && $maxtries && $maxtries > 1 &&
1.244     albertel  264:         $maxtries - $tries == 1 && $res->duedate($part) &&
                    265:         $res->duedate($part) > time();
1.115     bowersj2  266: }
                    267: 
1.320     albertel  268: # This puts a human-readable name on the env variable.
1.177     www       269: 
1.68      bowersj2  270: sub advancedUser {
1.320     albertel  271:     return $env{'request.role.adv'};
1.68      bowersj2  272: }
                    273: 
1.73      bowersj2  274: 
                    275: # timeToHumanString takes a time number and converts it to a
                    276: # human-readable representation, meant to be used in the following
                    277: # manner:
                    278: # print "Due $timestring"
                    279: # print "Open $timestring"
                    280: # print "Answer available $timestring"
                    281: # Very, very, very, VERY English-only... goodness help a localizer on
                    282: # this func...
1.347     www       283: 
                    284: 
1.53      bowersj2  285: sub timeToHumanString {
1.346     foxr      286:     my ($time,$type,$format) = @_;
                    287: 
1.58      albertel  288:     # zero, '0' and blank are bad times
1.73      bowersj2  289:     if (!$time) {
1.229     www       290:         return &mt('never');
1.73      bowersj2  291:     }
1.231     www       292:     unless (&Apache::lonlocal::current_language()=~/^en/) {
1.237     www       293: 	return &Apache::lonlocal::locallocaltime($time);
1.229     www       294:     } 
1.73      bowersj2  295:     my $now = time();
                    296: 
                    297:     my @time = localtime($time);
                    298:     my @now = localtime($now);
                    299: 
                    300:     # Positive = future
                    301:     my $delta = $time - $now;
                    302: 
                    303:     my $minute = 60;
                    304:     my $hour = 60 * $minute;
                    305:     my $day = 24 * $hour;
                    306:     my $week = 7 * $day;
                    307:     my $inPast = 0;
                    308: 
                    309:     # Logic in comments:
                    310:     # Is it now? (extremely unlikely)
                    311:     if ( $delta == 0 ) {
                    312:         return "this instant";
                    313:     }
                    314: 
                    315:     if ($delta < 0) {
                    316:         $inPast = 1;
                    317:         $delta = -$delta;
                    318:     }
                    319: 
                    320:     if ( $delta > 0 ) {
1.101     bowersj2  321: 
1.73      bowersj2  322:         my $tense = $inPast ? " ago" : "";
                    323:         my $prefix = $inPast ? "" : "in ";
1.101     bowersj2  324:         
                    325:         # Less then a minute
1.73      bowersj2  326:         if ( $delta < $minute ) {
                    327:             if ($delta == 1) { return "${prefix}1 second$tense"; }
                    328:             return "$prefix$delta seconds$tense";
                    329:         }
                    330: 
1.101     bowersj2  331:         # Less then an hour
1.73      bowersj2  332:         if ( $delta < $hour ) {
                    333:             # If so, use minutes
                    334:             my $minutes = floor($delta / 60);
                    335:             if ($minutes == 1) { return "${prefix}1 minute$tense"; }
                    336:             return "$prefix$minutes minutes$tense";
                    337:         }
                    338:         
                    339:         # Is it less then 24 hours away? If so,
                    340:         # display hours + minutes
                    341:         if ( $delta < $hour * 24) {
                    342:             my $hours = floor($delta / $hour);
                    343:             my $minutes = floor(($delta % $hour) / $minute);
                    344:             my $hourString = "$hours hours";
                    345:             my $minuteString = ", $minutes minutes";
                    346:             if ($hours == 1) {
                    347:                 $hourString = "1 hour";
                    348:             }
                    349:             if ($minutes == 1) {
                    350:                 $minuteString = ", 1 minute";
                    351:             }
                    352:             if ($minutes == 0) {
                    353:                 $minuteString = "";
                    354:             }
                    355:             return "$prefix$hourString$minuteString$tense";
                    356:         }
                    357: 
1.346     foxr      358: 	# If there's a caller supplied format, use it.
                    359: 
                    360: 	if($format ne '') {
                    361: 	    my $timeStr = strftime($format, localtime($time));
1.378     albertel  362: 	    return $timeStr.&Apache::lonlocal::gettimezone($time);
1.346     foxr      363: 	}
                    364: 
1.73      bowersj2  365:         # Less then 5 days away, display day of the week and
                    366:         # HH:MM
1.346     foxr      367: 
1.73      bowersj2  368:         if ( $delta < $day * 5 ) {
1.85      bowersj2  369:             my $timeStr = strftime("%A, %b %e at %I:%M %P", localtime($time));
1.238     albertel  370:             $timeStr =~ s/12:00 am/00:00/;
1.73      bowersj2  371:             $timeStr =~ s/12:00 pm/noon/;
1.333     albertel  372:             return ($inPast ? "last " : "this ") .
1.378     albertel  373:                 $timeStr.&Apache::lonlocal::gettimezone($time);
1.73      bowersj2  374:         }
                    375:         
1.341     albertel  376: 	my $conjunction='on';
                    377: 	if ($type eq 'start') {
                    378: 	    $conjunction='at';
                    379: 	} elsif ($type eq 'end') {
                    380: 	    $conjunction='by';
                    381: 	}
1.73      bowersj2  382:         # Is it this year?
                    383:         if ( $time[5] == $now[5]) {
                    384:             # Return on Month Day, HH:MM meridian
1.341     albertel  385:             my $timeStr = strftime("$conjunction %A, %b %e at %I:%M %P", localtime($time));
1.238     albertel  386:             $timeStr =~ s/12:00 am/00:00/;
1.73      bowersj2  387:             $timeStr =~ s/12:00 pm/noon/;
1.378     albertel  388:             return $timeStr.&Apache::lonlocal::gettimezone($time);
1.73      bowersj2  389:         }
                    390: 
                    391:         # Not this year, so show the year
1.341     albertel  392:         my $timeStr = strftime("$conjunction %A, %b %e %Y at %I:%M %P", localtime($time));
1.238     albertel  393:         $timeStr =~ s/12:00 am/00:00/;
1.73      bowersj2  394:         $timeStr =~ s/12:00 pm/noon/;
1.378     albertel  395:         return $timeStr.&Apache::lonlocal::gettimezone($time);
1.58      albertel  396:     }
1.53      bowersj2  397: }
                    398: 
1.51      bowersj2  399: 
1.133     bowersj2  400: =pod
                    401: 
1.174     albertel  402: =head1 NAME
                    403: 
1.217     bowersj2  404: Apache::lonnavmap - Subroutines to handle and render the navigation
                    405:     maps
1.174     albertel  406: 
                    407: =head1 SYNOPSIS
                    408: 
                    409: The main handler generates the navigational listing for the course,
                    410: the other objects export this information in a usable fashion for
1.205     bowersj2  411: other modules.
1.133     bowersj2  412: 
1.216     bowersj2  413: =head1 OVERVIEW
                    414: 
1.217     bowersj2  415: X<lonnavmaps, overview> When a user enters a course, LON-CAPA examines the
1.218     bowersj2  416: course structure and caches it in what is often referred to as the
                    417: "big hash" X<big hash>. You can see it if you are logged into
                    418: LON-CAPA, in a course, by going to /adm/test. (You may need to
                    419: tweak the /home/httpd/lonTabs/htpasswd file to view it.) The
                    420: content of the hash will be under the heading "Big Hash".
1.216     bowersj2  421: 
                    422: Big Hash contains, among other things, how resources are related
                    423: to each other (next/previous), what resources are maps, which 
                    424: resources are being chosen to not show to the student (for random
                    425: selection), and a lot of other things that can take a lot of time
                    426: to compute due to the amount of data that needs to be collected and
                    427: processed.
                    428: 
                    429: Apache::lonnavmaps provides an object model for manipulating this
                    430: information in a higher-level fashion then directly manipulating 
                    431: the hash. It also provides access to several auxilary functions 
                    432: that aren't necessarily stored in the Big Hash, but are a per-
                    433: resource sort of value, like whether there is any feedback on 
                    434: a given resource.
                    435: 
                    436: Apache::lonnavmaps also abstracts away branching, and someday, 
                    437: conditions, for the times where you don't really care about those
                    438: things.
                    439: 
                    440: Apache::lonnavmaps also provides fairly powerful routines for
                    441: rendering navmaps, and last but not least, provides the navmaps
                    442: view for when the user clicks the NAV button.
                    443: 
                    444: B<Note>: Apache::lonnavmaps I<only> works for the "currently
                    445: logged in user"; if you want things like "due dates for another
                    446: student" lonnavmaps can not directly retrieve information like
                    447: that. You need the EXT function. This module can still help,
                    448: because many things, such as the course structure, are constant
                    449: between users, and Apache::lonnavmaps can help by providing
                    450: symbs for the EXT call.
                    451: 
                    452: The rest of this file will cover the provided rendering routines, 
                    453: which can often be used without fiddling with the navmap object at
                    454: all, then documents the Apache::lonnavmaps::navmap object, which
                    455: is the key to accessing the Big Hash information, covers the use
                    456: of the Iterator (which provides the logic for traversing the 
                    457: somewhat-complicated Big Hash data structure), documents the
                    458: Apache::lonnavmaps::Resource objects that are returned by 
                    459: 
1.205     bowersj2  460: =head1 Subroutine: render
1.133     bowersj2  461: 
1.174     albertel  462: The navmap renderer package provides a sophisticated rendering of the
                    463: standard navigation maps interface into HTML. The provided nav map
                    464: handler is actually just a glorified call to this.
1.133     bowersj2  465: 
1.216     bowersj2  466: Because of the large number of parameters this function accepts,
1.174     albertel  467: instead of passing it arguments as is normal, pass it in an anonymous
1.216     bowersj2  468: hash with the desired options.
1.174     albertel  469: 
                    470: The package provides a function called 'render', called as
1.205     bowersj2  471: Apache::lonnavmaps::render({}).
1.51      bowersj2  472: 
1.133     bowersj2  473: =head2 Overview of Columns
1.51      bowersj2  474: 
1.174     albertel  475: The renderer will build an HTML table for the navmap and return
                    476: it. The table is consists of several columns, and a row for each
                    477: resource (or possibly each part). You tell the renderer how many
                    478: columns to create and what to place in each column, optionally using
1.205     bowersj2  479: one or more of the prepared columns, and the renderer will assemble
1.174     albertel  480: the table.
                    481: 
                    482: Any additional generally useful column types should be placed in the
                    483: renderer code here, so anybody can use it anywhere else. Any code
                    484: specific to the current application (such as the addition of <input>
                    485: elements in a column) should be placed in the code of the thing using
                    486: the renderer.
                    487: 
                    488: At the core of the renderer is the array reference COLS (see Example
                    489: section below for how to pass this correctly). The COLS array will
                    490: consist of entries of one of two types of things: Either an integer
                    491: representing one of the pre-packaged column types, or a sub reference
                    492: that takes a resource reference, a part number, and a reference to the
                    493: argument hash passed to the renderer, and returns a string that will
                    494: be inserted into the HTML representation as it.
                    495: 
1.216     bowersj2  496: All other parameters are ways of either changing how the columns
                    497: are printing, or which rows are shown.
                    498: 
1.174     albertel  499: The pre-packaged column names are refered to by constants in the
1.205     bowersj2  500: Apache::lonnavmaps namespace. The following currently exist:
1.51      bowersj2  501: 
1.174     albertel  502: =over 4
1.51      bowersj2  503: 
1.216     bowersj2  504: =item * B<Apache::lonnavmaps::resource>:
1.51      bowersj2  505: 
1.174     albertel  506: The general info about the resource: Link, icon for the type, etc. The
1.216     bowersj2  507: first column in the standard nav map display. This column provides the
                    508: indentation effect seen in the B<NAV> screen. This column also accepts
1.205     bowersj2  509: the following parameters in the renderer hash:
1.51      bowersj2  510: 
1.133     bowersj2  511: =over 4
1.51      bowersj2  512: 
1.216     bowersj2  513: =item * B<resource_nolink>: default false
1.51      bowersj2  514: 
1.216     bowersj2  515: If true, the resource will not be linked. By default, all non-folder
                    516: resources are linked.
1.174     albertel  517: 
1.216     bowersj2  518: =item * B<resource_part_count>: default true
1.51      bowersj2  519: 
1.216     bowersj2  520: If true, the resource will show a part count B<if> the full
                    521: part list is not displayed. (See "condense_parts" later.) If false,
                    522: the resource will never show a part count.
1.133     bowersj2  523: 
1.174     albertel  524: =item * B<resource_no_folder_link>:
1.133     bowersj2  525: 
1.174     albertel  526: If true, the resource's folder will not be clickable to open or close
                    527: it. Default is false. True implies printCloseAll is false, since you
                    528: can't close or open folders when this is on anyhow.
1.144     bowersj2  529: 
1.133     bowersj2  530: =back
1.51      bowersj2  531: 
1.225     bowersj2  532: =item * B<Apache::lonnavmaps::communication_status>:
1.174     albertel  533: 
                    534: Whether there is discussion on the resource, email for the user, or
                    535: (lumped in here) perl errors in the execution of the problem. This is
                    536: the second column in the main nav map.
                    537: 
1.225     bowersj2  538: =item * B<Apache::lonnavmaps::quick_status>:
1.51      bowersj2  539: 
1.216     bowersj2  540: An icon for the status of a problem, with five possible states:
                    541: Correct, incorrect, open, awaiting grading (for a problem where the
                    542: computer's grade is suppressed, or the computer can't grade, like
                    543: essay problem), or none (not open yet, not a problem). The
1.174     albertel  544: third column of the standard navmap.
1.51      bowersj2  545: 
1.225     bowersj2  546: =item * B<Apache::lonnavmaps::long_status>:
1.174     albertel  547: 
                    548: A text readout of the details of the current status of the problem,
                    549: such as "Due in 22 hours". The fourth column of the standard navmap.
1.51      bowersj2  550: 
1.225     bowersj2  551: =item * B<Apache::lonnavmaps::part_status_summary>:
                    552: 
                    553: A text readout summarizing the status of the problem. If it is a
                    554: single part problem, will display "Correct", "Incorrect", 
                    555: "Not yet open", "Open", "Attempted", or "Error". If there are
                    556: multiple parts, this will output a string that in HTML will show a
                    557: status of how many parts are in each status, in color coding, trying
                    558: to match the colors of the icons within reason.
                    559: 
                    560: Note this only makes sense if you are I<not> showing parts. If 
                    561: C<showParts> is true (see below), this column will not output
                    562: anything. 
                    563: 
1.133     bowersj2  564: =back
1.51      bowersj2  565: 
1.133     bowersj2  566: If you add any others please be sure to document them here.
1.51      bowersj2  567: 
1.174     albertel  568: An example of a column renderer that will show the ID number of a
                    569: resource, along with the part name if any:
1.51      bowersj2  570: 
1.133     bowersj2  571:  sub { 
                    572:   my ($resource, $part, $params) = @_;   
                    573:   if ($part) { return '<td>' . $resource->{ID} . ' ' . $part . '</td>'; }
                    574:   return '<td>' . $resource->{ID} . '</td>';
                    575:  }
1.51      bowersj2  576: 
1.174     albertel  577: Note these functions are responsible for the TD tags, which allow them
                    578: to override vertical and horizontal alignment, etc.
1.130     www       579: 
1.133     bowersj2  580: =head2 Parameters
1.115     bowersj2  581: 
1.217     bowersj2  582: Minimally, you should be
1.174     albertel  583: able to get away with just using 'cols' (to specify the columns
                    584: shown), 'url' (necessary for the folders to link to the current screen
                    585: correctly), and possibly 'queryString' if your app calls for it. In
                    586: that case, maintaining the state of the folders will be done
                    587: automatically.
1.140     bowersj2  588: 
1.133     bowersj2  589: =over 4
1.51      bowersj2  590: 
1.320     albertel  591: =item * B<iterator>: default: constructs one from %env
1.174     albertel  592: 
                    593: A reference to a fresh ::iterator to use from the navmaps. The
                    594: rendering will reflect the options passed to the iterator, so you can
                    595: use that to just render a certain part of the course, if you like. If
                    596: one is not passed, the renderer will attempt to construct one from
1.320     albertel  597: env{'form.filter'} and env{'form.condition'} information, plus the
1.174     albertel  598: 'iterator_map' parameter if any.
                    599: 
1.216     bowersj2  600: =item * B<iterator_map>: default: not used
1.174     albertel  601: 
                    602: If you are letting the renderer do the iterator handling, you can
                    603: instruct the renderer to render only a particular map by passing it
                    604: the source of the map you want to process, like
                    605: '/res/103/jerf/navmap.course.sequence'.
                    606: 
1.320     albertel  607: =item * B<navmap>: default: constructs one from %env
1.174     albertel  608: 
                    609: A reference to a navmap, used only if an iterator is not passed in. If
                    610: this is necessary to make an iterator but it is not passed in, a new
1.320     albertel  611: one will be constructed based on env info. This is useful to do basic
1.174     albertel  612: error checking before passing it off to render.
                    613: 
1.216     bowersj2  614: =item * B<r>: default: must be passed in
1.174     albertel  615: 
                    616: The standard Apache response object. This must be passed to the
                    617: renderer or the course hash will be locked.
                    618: 
1.216     bowersj2  619: =item * B<cols>: default: empty (useless)
1.174     albertel  620: 
                    621: An array reference
                    622: 
1.216     bowersj2  623: =item * B<showParts>:default true
1.174     albertel  624: 
1.216     bowersj2  625: A flag. If true, a line for the resource itself, and a line
1.174     albertel  626: for each part will be displayed. If not, only one line for each
                    627: resource will be displayed.
                    628: 
1.216     bowersj2  629: =item * B<condenseParts>: default true
1.174     albertel  630: 
1.216     bowersj2  631: A flag. If true, if all parts of the problem have the same
1.174     albertel  632: status and that status is Nothing Set, Correct, or Network Failure,
                    633: then only one line will be displayed for that resource anyhow. If no,
                    634: all parts will always be displayed. If showParts is 0, this is
                    635: ignored.
                    636: 
1.320     albertel  637: =item * B<jumpCount>: default: determined from %env
1.174     albertel  638: 
1.216     bowersj2  639: A string identifying the URL to place the anchor 'curloc' at.
                    640: It is the responsibility of the renderer user to
1.174     albertel  641: ensure that the #curloc is in the URL. By default, determined through
1.320     albertel  642: the use of the env{} 'jump' information, and should normally "just
1.174     albertel  643: work" correctly.
1.144     bowersj2  644: 
1.216     bowersj2  645: =item * B<here>: default: empty string
1.140     bowersj2  646: 
1.216     bowersj2  647: A Symb identifying where to place the 'here' marker. The empty
                    648: string means no marker.
1.92      bowersj2  649: 
1.216     bowersj2  650: =item * B<indentString>: default: 25 pixel whitespace image
1.164     bowersj2  651: 
1.216     bowersj2  652: A string identifying the indentation string to use. 
1.92      bowersj2  653: 
1.216     bowersj2  654: =item * B<queryString>: default: empty
1.51      bowersj2  655: 
1.174     albertel  656: A string which will be prepended to the query string used when the
1.216     bowersj2  657: folders are opened or closed. You can use this to pass
                    658: application-specific values.
1.55      bowersj2  659: 
1.216     bowersj2  660: =item * B<url>: default: none
1.84      bowersj2  661: 
1.174     albertel  662: The url the folders will link to, which should be the current
1.216     bowersj2  663: page. Required if the resource info column is shown, and you 
                    664: are allowing the user to open and close folders.
1.115     bowersj2  665: 
1.216     bowersj2  666: =item * B<currentJumpIndex>: default: no jumping
1.55      bowersj2  667: 
1.174     albertel  668: Describes the currently-open row number to cause the browser to jump
                    669: to, because the user just opened that folder. By default, pulled from
1.320     albertel  670: the Jump information in the env{'form.*'}.
1.51      bowersj2  671: 
1.216     bowersj2  672: =item * B<printKey>: default: false
1.51      bowersj2  673: 
1.174     albertel  674: If true, print the key that appears on the top of the standard
1.216     bowersj2  675: navmaps.
1.139     bowersj2  676: 
1.216     bowersj2  677: =item * B<printCloseAll>: default: true
1.140     bowersj2  678: 
1.174     albertel  679: If true, print the "Close all folders" or "open all folders"
1.216     bowersj2  680: links.
1.140     bowersj2  681: 
1.216     bowersj2  682: =item * B<filterFunc>: default: sub {return 1;} (accept everything)
1.153     bowersj2  683: 
1.174     albertel  684: A function that takes the resource object as its only parameter and
                    685: returns a true or false value. If true, the resource is displayed. If
1.216     bowersj2  686: false, it is simply skipped in the display.
1.174     albertel  687: 
1.216     bowersj2  688: =item * B<suppressEmptySequences>: default: false
1.190     bowersj2  689: 
                    690: If you're using a filter function, and displaying sequences to orient
                    691: the user, then frequently some sequences will be empty. Setting this to
                    692: true will cause those sequences not to display, so as not to confuse the
                    693: user into thinking that if the sequence is there there should be things
1.216     bowersj2  694: under it; for example, see the "Show Uncompleted Homework" view on the
                    695: B<NAV> screen.
1.190     bowersj2  696: 
1.216     bowersj2  697: =item * B<suppressNavmaps>: default: false
1.174     albertel  698: 
1.216     bowersj2  699: If true, will not display Navigate Content resources. 
1.143     bowersj2  700: 
1.133     bowersj2  701: =back
1.51      bowersj2  702: 
1.133     bowersj2  703: =head2 Additional Info
1.51      bowersj2  704: 
1.174     albertel  705: In addition to the parameters you can pass to the renderer, which will
                    706: be passed through unchange to the column renderers, the renderer will
                    707: generate the following information which your renderer may find
                    708: useful:
                    709: 
1.216     bowersj2  710: =over 4
                    711: 
                    712: =item * B<counter>: 
1.145     bowersj2  713: 
1.216     bowersj2  714: Contains the number of rows printed. Useful after calling the render 
                    715: function, as you can detect whether anything was printed at all.
                    716: 
                    717: =item * B<isNewBranch>:
                    718: 
                    719: Useful for renderers: If this resource is currently the first resource
                    720: of a new branch, this will be true. The Resource column (leftmost in the
                    721: navmaps screen) uses this to display the "new branch" icon 
1.59      bowersj2  722: 
1.133     bowersj2  723: =back
1.59      bowersj2  724: 
1.133     bowersj2  725: =cut
1.59      bowersj2  726: 
1.133     bowersj2  727: sub resource { return 0; }
                    728: sub communication_status { return 1; }
                    729: sub quick_status { return 2; }
                    730: sub long_status { return 3; }
1.225     bowersj2  731: sub part_status_summary { return 4; }
1.51      bowersj2  732: 
1.133     bowersj2  733: sub render_resource {
                    734:     my ($resource, $part, $params) = @_;
1.51      bowersj2  735: 
1.133     bowersj2  736:     my $nonLinkedText = ''; # stuff after resource title not in link
1.51      bowersj2  737: 
1.134     bowersj2  738:     my $link = $params->{"resourceLink"};
1.306     foxr      739: 
                    740:     #  The URL part is not escaped at this point, but the symb is... 
                    741:     #  The stuff to the left of the ? must have ' replaced by \' since
                    742:     #  it will be quoted with ' in the href.
                    743: 
                    744:     my ($left,$right) = split(/\?/, $link);
                    745:     $link = $left.'?'.$right;
                    746: 
1.134     bowersj2  747:     my $src = $resource->src();
                    748:     my $it = $params->{"iterator"};
1.133     bowersj2  749:     my $filter = $it->{FILTER};
                    750: 
                    751:     my $title = $resource->compTitle();
1.258     albertel  752: 
1.133     bowersj2  753:     my $partLabel = "";
                    754:     my $newBranchText = "";
1.298     albertel  755:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
1.133     bowersj2  756:     # If this is a new branch, label it so
                    757:     if ($params->{'isNewBranch'}) {
1.329     albertel  758:         $newBranchText = "<img src='$location/branch.gif' border='0' alt='Branch' />";
1.51      bowersj2  759:     }
                    760: 
1.133     bowersj2  761:     # links to open and close the folder
1.306     foxr      762: 
                    763:     
1.349     foxr      764:     my $linkopen = "<a href=\"$link\">";
1.306     foxr      765: 
                    766: 
1.133     bowersj2  767:     my $linkclose = "</a>";
1.51      bowersj2  768: 
1.204     albertel  769:     # Default icon: unknown page
1.329     albertel  770:     my $icon = "<img src='$location/unknown.gif' alt='' border='0' alt='&nbsp;&nbsp;' ' />";
1.133     bowersj2  771:     
                    772:     if ($resource->is_problem()) {
1.192     bowersj2  773:         if ($part eq '0' || $params->{'condensed'}) {
1.389     albertel  774: 	    $icon = '<img src="'.$location.'/';
                    775: 	    if ($resource->is_task()) {
                    776: 		$icon .= 'task.gif" alt="'.&mt('Task');
                    777: 	    } else {
                    778: 		$icon .= 'problem.gif" alt="'.&mt('Problem');
                    779: 	    }
                    780: 	    $icon .='" border="0" />';
1.133     bowersj2  781:         } else {
                    782:             $icon = $params->{'indentString'};
                    783:         }
1.204     albertel  784:     } else {
1.329     albertel  785: 	$icon = "<img src='".&Apache::loncommon::icon($resource->src)."' alt='&nbsp;&nbsp;' border='0' />";
1.133     bowersj2  786:     }
1.51      bowersj2  787: 
1.133     bowersj2  788:     # Display the correct map icon to open or shut map
                    789:     if ($resource->is_map()) {
                    790:         my $mapId = $resource->map_pc();
                    791:         my $nowOpen = !defined($filter->{$mapId});
                    792:         if ($it->{CONDITION}) {
                    793:             $nowOpen = !$nowOpen;
                    794:         }
1.144     bowersj2  795: 
1.205     bowersj2  796: 	my $folderType = $resource->is_sequence() ? 'folder' : 'page';
1.362     www       797:         my $title=$resource->title;
1.363     albertel  798:         $title=~s/\"/\&quot;/g;
1.144     bowersj2  799:         if (!$params->{'resource_no_folder_link'}) {
1.205     bowersj2  800:             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') . '.gif';
1.363     albertel  801: 	    $icon = "<img src='$location/$icon' alt=\"".
                    802: 		($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
1.144     bowersj2  803: 
1.349     foxr      804:             $linkopen = "<a href=\"" . $params->{'url'} . '?' . 
1.392     albertel  805:                 $params->{'queryString'} . '&amp;filter=';
1.144     bowersj2  806:             $linkopen .= ($nowOpen xor $it->{CONDITION}) ?
                    807:                 addToFilter($filter, $mapId) :
                    808:                 removeFromFilter($filter, $mapId);
1.392     albertel  809:             $linkopen .= "&amp;condition=" . $it->{CONDITION} . '&amp;hereType='
                    810:                 . $params->{'hereType'} . '&amp;here=' .
1.384     www       811:                 &escape($params->{'here'}) . 
1.392     albertel  812:                 '&amp;jump=' .
1.384     www       813:                 &escape($resource->symb()) . 
1.392     albertel  814:                 "&amp;folderManip=1\">";
1.306     foxr      815: 
1.144     bowersj2  816:         } else {
                    817:             # Don't allow users to manipulate folder
1.205     bowersj2  818:             $icon = "navmap.$folderType." . ($nowOpen ? 'closed' : 'open') .
1.144     bowersj2  819:                 '.nomanip.gif';
1.364     www       820:             $icon = "<img src='$location/$icon' alt=\"".
                    821: 		($nowOpen ? &mt('Open Folder') : &mt('Close Folder')).' '.$title."\" border='0' />";
1.144     bowersj2  822: 
                    823:             $linkopen = "";
                    824:             $linkclose = "";
                    825:         }
1.133     bowersj2  826:     }
1.51      bowersj2  827: 
1.133     bowersj2  828:     if ($resource->randomout()) {
1.362     www       829:         $nonLinkedText .= ' <i>('.&mt('hidden').')</i> ';
1.133     bowersj2  830:     }
1.342     albertel  831:     if (!$resource->condval()) {
1.362     www       832:         $nonLinkedText .= ' <i>('.&mt('conditionally hidden').')</i> ';
1.342     albertel  833:     }
1.133     bowersj2  834:     
                    835:     # We're done preparing and finally ready to start the rendering
1.392     albertel  836:     my $result = "<td align='left' valign='middle'>";
1.140     bowersj2  837: 
                    838:     my $indentLevel = $params->{'indentLevel'};
                    839:     if ($newBranchText) { $indentLevel--; }
                    840: 
1.133     bowersj2  841:     # print indentation
1.140     bowersj2  842:     for (my $i = 0; $i < $indentLevel; $i++) {
1.133     bowersj2  843:         $result .= $params->{'indentString'};
1.84      bowersj2  844:     }
                    845: 
1.133     bowersj2  846:     # Decide what to display
1.306     foxr      847: 
1.133     bowersj2  848:     $result .= "$newBranchText$linkopen$icon$linkclose";
                    849:     
                    850:     my $curMarkerBegin = '';
                    851:     my $curMarkerEnd = '';
1.51      bowersj2  852: 
1.133     bowersj2  853:     # Is this the current resource?
1.158     bowersj2  854:     if (!$params->{'displayedHereMarker'} && 
                    855:         $resource->symb() eq $params->{'here'} ) {
1.345     www       856:         $curMarkerBegin = '<font color="red" size="+2">&gt;</font>';
1.133     bowersj2  857:         $curMarkerEnd = '<font color="red" size="+2">&lt;</font>';
1.158     bowersj2  858:         $params->{'displayedHereMarker'} = 1;
1.51      bowersj2  859:     }
                    860: 
1.192     bowersj2  861:     if ($resource->is_problem() && $part ne '0' && 
1.133     bowersj2  862:         !$params->{'condensed'}) {
1.274     matthew   863: 	my $displaypart=$resource->part_display($part);
1.363     albertel  864:         $partLabel = " (".&mt('Part: [_1]', $displaypart).")";
1.384     www       865: 	if ($link!~/\#/) { $link.='#'.&escape($part); }
1.133     bowersj2  866:         $title = "";
1.51      bowersj2  867:     }
                    868: 
1.163     bowersj2  869:     if ($params->{'condensed'} && $resource->countParts() > 1) {
1.363     albertel  870:         $nonLinkedText .= ' ('.&mt('[_1] parts', $resource->countParts()).')';
1.51      bowersj2  871:     }
                    872: 
1.268     albertel  873:     my $target;
1.320     albertel  874:     if ($env{'environment.remotenavmap'} eq 'on') {
1.268     albertel  875: 	$target=' target="loncapaclient" ';
                    876:     }
1.266     raeburn   877:     if (!$params->{'resource_nolink'} && !$resource->is_sequence() && !$resource->is_empty_sequence) {
1.349     foxr      878:         $result .= "  $curMarkerBegin<a $target href=\"$link\">$title$partLabel</a>$curMarkerEnd $nonLinkedText</td>";
1.144     bowersj2  879:     } else {
                    880:         $result .= "  $curMarkerBegin$title$partLabel$curMarkerEnd $nonLinkedText</td>";
                    881:     }
1.51      bowersj2  882: 
1.133     bowersj2  883:     return $result;
                    884: }
1.51      bowersj2  885: 
1.133     bowersj2  886: sub render_communication_status {
                    887:     my ($resource, $part, $params) = @_;
1.134     bowersj2  888:     my $discussionHTML = ""; my $feedbackHTML = ""; my $errorHTML = "";
                    889: 
                    890:     my $link = $params->{"resourceLink"};
1.335     albertel  891:     my $target;
                    892:     if ($env{'environment.remotenavmap'} eq 'on') {
                    893: 	$target=' target="loncapaclient" ';
                    894:     }
1.349     foxr      895:     my $linkopen = "<a $target href=\"$link\">";
1.134     bowersj2  896:     my $linkclose = "</a>";
1.298     albertel  897:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
1.134     bowersj2  898:     if ($resource->hasDiscussion()) {
                    899:         $discussionHTML = $linkopen .
1.392     albertel  900:             '<img alt="'.&mt('New Discussion').'" border="0" src="'.$location.'/chat.gif" />' .
1.134     bowersj2  901:             $linkclose;
                    902:     }
                    903:     
                    904:     if ($resource->getFeedback()) {
                    905:         my $feedback = $resource->getFeedback();
1.394     raeburn   906:         foreach my $msgid (split(/\,/, $feedback)) {
                    907:             if ($msgid) {
1.335     albertel  908:                 $feedbackHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
1.394     raeburn   909:                     . &escape($msgid) . '">'
1.392     albertel  910:                     . '<img alt="'.&mt('New Email').'" src="'.$location.'/feedback.gif" '
1.134     bowersj2  911:                     . 'border="0" /></a>';
                    912:             }
                    913:         }
                    914:     }
                    915:     
                    916:     if ($resource->getErrors()) {
                    917:         my $errors = $resource->getErrors();
1.254     matthew   918:         my $errorcount = 0;
1.394     raeburn   919:         foreach my $msgid (split(/,/, $errors)) {
1.254     matthew   920:             last if ($errorcount>=10); # Only output 10 bombs maximum
1.394     raeburn   921:             if ($msgid) {
1.254     matthew   922:                 $errorcount++;
1.335     albertel  923:                 $errorHTML .= '&nbsp;<a '.$target.' href="/adm/email?display='
1.394     raeburn   924:                     . &escape($msgid) . '">'
1.392     albertel  925:                     . '<img alt="'.&mt('New Error').'" src="'.$location.'/bomb.gif" '
1.134     bowersj2  926:                     . 'border="0" /></a>';
                    927:             }
                    928:         }
1.197     bowersj2  929:     }
                    930: 
                    931:     if ($params->{'multipart'} && $part != '0') {
                    932: 	$discussionHTML = $feedbackHTML = $errorHTML = '';
1.134     bowersj2  933:     }
                    934: 
1.392     albertel  935:     return "<td width=\"75\" align=\"left\" valign=\"middle\">$discussionHTML$feedbackHTML$errorHTML&nbsp;</td>";
1.134     bowersj2  936: 
1.133     bowersj2  937: }
                    938: sub render_quick_status {
                    939:     my ($resource, $part, $params) = @_;
1.135     bowersj2  940:     my $result = "";
                    941:     my $firstDisplayed = !$params->{'condensed'} && 
                    942:         $params->{'multipart'} && $part eq "0";
                    943: 
                    944:     my $link = $params->{"resourceLink"};
1.335     albertel  945:     my $target;
                    946:     if ($env{'environment.remotenavmap'} eq 'on') {
                    947: 	$target=' target="loncapaclient" ';
                    948:     }
1.349     foxr      949:     my $linkopen = "<a $target href=\"$link\">";
1.135     bowersj2  950:     my $linkclose = "</a>";
                    951: 
                    952:     if ($resource->is_problem() &&
                    953:         !$firstDisplayed) {
1.224     bowersj2  954: 	
                    955:         my $icon = $statusIconMap{$resource->simpleStatus($part)};
1.135     bowersj2  956:         my $alt = $iconAltTags{$icon};
                    957:         if ($icon) {
1.298     albertel  958: 	    my $location=
                    959: 		&Apache::loncommon::lonhttpdurl("/adm/lonIcons/$icon");
1.392     albertel  960:             $result .= "<td valign='middle' width='50' align='right'>$linkopen<img width='25' height='25' src='$location' border='0' alt='$alt' />$linkclose</td>\n";
1.135     bowersj2  961:         } else {
                    962:             $result .= "<td width='30'>&nbsp;</td>\n";
                    963:         }
                    964:     } else { # not problem, no icon
                    965:         $result .= "<td width='30'>&nbsp;</td>\n";
                    966:     }
                    967: 
                    968:     return $result;
1.133     bowersj2  969: }
                    970: sub render_long_status {
                    971:     my ($resource, $part, $params) = @_;
1.392     albertel  972:     my $result = "<td align='right' valign='middle'>\n";
1.136     bowersj2  973:     my $firstDisplayed = !$params->{'condensed'} && 
                    974:         $params->{'multipart'} && $part eq "0";
                    975:                 
                    976:     my $color;
1.212     bowersj2  977:     if ($resource->is_problem()) {
1.136     bowersj2  978:         $color = $colormap{$resource->status};
                    979:         
1.252     matthew   980:         if (dueInLessThan24Hours($resource, $part) ||
1.136     bowersj2  981:             lastTry($resource, $part)) {
                    982:             $color = $hurryUpColor;
                    983:         }
                    984:     }
                    985:     
                    986:     if ($resource->kind() eq "res" &&
                    987:         $resource->is_problem() &&
                    988:         !$firstDisplayed) {
                    989:         if ($color) {$result .= "<font color=\"$color\"><b>"; }
                    990:         $result .= getDescription($resource, $part);
                    991:         if ($color) {$result .= "</b></font>"; }
                    992:     }
                    993:     if ($resource->is_map() && advancedUser() && $resource->randompick()) {
                    994:         $result .= '(randomly select ' . $resource->randompick() .')';
                    995:     }
1.210     bowersj2  996: 
1.213     bowersj2  997:     # Debugging code
                    998:     #$result .= " " . $resource->awarded($part) . '/' . $resource->weight($part) .
                    999:     #	' - Part: ' . $part;
                   1000: 
1.210     bowersj2 1001:     $result .= "</td>\n";
1.136     bowersj2 1002:     
                   1003:     return $result;
1.51      bowersj2 1004: }
                   1005: 
1.227     bowersj2 1006: # Colors obtained by taking the icons, matching the colors, and
                   1007: # possibly reducing the Value (HSV) of the color, if it's too bright
                   1008: # for text, generally by one third or so.
1.225     bowersj2 1009: my %statusColors = 
                   1010:     (
                   1011:      $resObj->CLOSED => '#000000',
1.227     bowersj2 1012:      $resObj->OPEN   => '#998b13',
                   1013:      $resObj->CORRECT => '#26933f',
                   1014:      $resObj->INCORRECT => '#c48207',
                   1015:      $resObj->ATTEMPTED => '#a87510',
1.225     bowersj2 1016:      $resObj->ERROR => '#000000'
                   1017:      );
                   1018: my %statusStrings = 
                   1019:     (
                   1020:      $resObj->CLOSED => 'Not yet open',
                   1021:      $resObj->OPEN   => 'Open',
                   1022:      $resObj->CORRECT => 'Correct',
                   1023:      $resObj->INCORRECT => 'Incorrect',
                   1024:      $resObj->ATTEMPTED => 'Attempted',
                   1025:      $resObj->ERROR => 'Network Error'
                   1026:      );
                   1027: my @statuses = ($resObj->CORRECT, $resObj->ATTEMPTED, $resObj->INCORRECT, $resObj->OPEN, $resObj->CLOSED, $resObj->ERROR);
                   1028: 
                   1029: use Data::Dumper;
                   1030: sub render_parts_summary_status {
                   1031:     my ($resource, $part, $params) = @_;
1.256     albertel 1032:     if (!$resource->is_problem() && !$resource->contains_problem) { return '<td></td>'; }
1.225     bowersj2 1033:     if ($params->{showParts}) { 
                   1034: 	return '<td></td>';
                   1035:     }
                   1036: 
                   1037:     my $td = "<td align='right'>\n";
                   1038:     my $endtd = "</td>\n";
1.256     albertel 1039:     my @probs;
1.225     bowersj2 1040: 
1.256     albertel 1041:     if ($resource->contains_problem) {
                   1042: 	@probs=$resource->retrieveResources($resource,sub { $_[0]->is_problem() },1,0);
                   1043:     } else {
                   1044: 	@probs=($resource);
                   1045:     }
                   1046:     my $return;
                   1047:     my %overallstatus;
                   1048:     my $totalParts;
                   1049:     foreach my $resource (@probs) {
                   1050: 	# If there is a single part, just show the simple status
                   1051: 	if ($resource->singlepart()) {
                   1052: 	    my $status = $resource->simpleStatus(${$resource->parts}[0]);
                   1053: 	    $overallstatus{$status}++;
                   1054: 	    $totalParts++;
                   1055: 	    next;
                   1056: 	}
                   1057: 	# Now we can be sure the $part doesn't really matter.
                   1058: 	my $statusCount = $resource->simpleStatusCount();
                   1059: 	my @counts;
                   1060: 	foreach my $status (@statuses) {
                   1061: 	    # decouple display order from the simpleStatusCount order
                   1062: 	    my $slot = Apache::lonnavmaps::resource::statusToSlot($status);
                   1063: 	    if ($statusCount->[$slot]) {
                   1064: 		$overallstatus{$status}+=$statusCount->[$slot];
                   1065: 		$totalParts+=$statusCount->[$slot];
                   1066: 	    }
                   1067: 	}
                   1068:     }
                   1069:     $return.= $td . $totalParts . ' parts: ';
                   1070:     foreach my $status (@statuses) {
                   1071: 	if ($overallstatus{$status}) {
                   1072: 	    $return.="<font color='" . $statusColors{$status} .
                   1073: 		"'>" . $overallstatus{$status} . ' '
1.225     bowersj2 1074: 		. $statusStrings{$status} . "</font>";
                   1075: 	}
                   1076:     }
1.256     albertel 1077:     $return.= $endtd;
                   1078:     return $return;
1.225     bowersj2 1079: }
                   1080: 
1.133     bowersj2 1081: my @preparedColumns = (\&render_resource, \&render_communication_status,
1.225     bowersj2 1082:                        \&render_quick_status, \&render_long_status,
                   1083: 		       \&render_parts_summary_status);
1.132     bowersj2 1084: 
                   1085: sub setDefault {
                   1086:     my ($val, $default) = @_;
                   1087:     if (!defined($val)) { return $default; }
                   1088:     return $val;
                   1089: }
                   1090: 
1.296     albertel 1091: sub cmp_title {
                   1092:     my ($atitle,$btitle) = (lc($_[0]->compTitle),lc($_[1]->compTitle));
                   1093:     $atitle=~s/^\s*//;
                   1094:     $btitle=~s/^\s*//;
                   1095:     return $atitle cmp $btitle;
                   1096: }
                   1097: 
1.132     bowersj2 1098: sub render {
                   1099:     my $args = shift;
1.140     bowersj2 1100:     &Apache::loncommon::get_unprocessed_cgi($ENV{QUERY_STRING});
                   1101:     my $result = '';
1.132     bowersj2 1102:     # Configure the renderer.
                   1103:     my $cols = $args->{'cols'};
                   1104:     if (!defined($cols)) {
                   1105:         # no columns, no nav maps.
                   1106:         return '';
                   1107:     }
1.140     bowersj2 1108:     my $navmap;
                   1109:     if (defined($args->{'navmap'})) {
                   1110:         $navmap = $args->{'navmap'};
                   1111:     }
                   1112: 
1.158     bowersj2 1113:     my $r = $args->{'r'};
1.140     bowersj2 1114:     my $queryString = $args->{'queryString'};
1.159     bowersj2 1115:     my $jump = $args->{'jump'};
1.158     bowersj2 1116:     my $here = $args->{'here'};
1.153     bowersj2 1117:     my $suppressNavmap = setDefault($args->{'suppressNavmap'}, 0);
1.256     albertel 1118:     my $closeAllPages = setDefault($args->{'closeAllPages'}, 0);
1.140     bowersj2 1119:     my $currentJumpDelta = 2; # change this to change how many resources are displayed
                   1120:                              # before the current resource when using #current
                   1121: 
                   1122:     # If we were passed 'here' information, we are not rendering
                   1123:     # after a folder manipulation, and we were not passed an
                   1124:     # iterator, make sure we open the folders to show the "here"
                   1125:     # marker
                   1126:     my $filterHash = {};
                   1127:     # Figure out what we're not displaying
1.394     raeburn  1128:     foreach my $item (split(/\,/, $env{"form.filter"})) {
                   1129:         if ($item) {
                   1130:             $filterHash->{$item} = "1";
1.140     bowersj2 1131:         }
                   1132:     }
                   1133: 
1.191     bowersj2 1134:     # Filter: Remember filter function and add our own filter: Refuse
                   1135:     # to show hidden resources unless the user can see them.
                   1136:     my $userCanSeeHidden = advancedUser();
                   1137:     my $filterFunc = setDefault($args->{'filterFunc'},
                   1138:                                 sub {return 1;});
                   1139:     if (!$userCanSeeHidden) {
                   1140:         # Without renaming the filterfunc, the server seems to go into
                   1141:         # an infinite loop
                   1142:         my $oldFilterFunc = $filterFunc;
                   1143:         $filterFunc = sub { my $res = shift; return !$res->randomout() && 
                   1144:                                 &$oldFilterFunc($res);};
                   1145:     }
                   1146: 
1.140     bowersj2 1147:     my $condition = 0;
1.320     albertel 1148:     if ($env{'form.condition'}) {
1.140     bowersj2 1149:         $condition = 1;
                   1150:     }
                   1151: 
1.320     albertel 1152:     if (!$env{'form.folderManip'} && !defined($args->{'iterator'})) {
1.140     bowersj2 1153:         # Step 1: Check to see if we have a navmap
                   1154:         if (!defined($navmap)) {
1.221     bowersj2 1155:             $navmap = Apache::lonnavmaps::navmap->new();
1.340     albertel 1156: 	    if (!defined($navmap)) {
                   1157: 		# no londer in course
1.388     albertel 1158: 		return '<span class="LC_error">'.&mt('No course selected').'</span><br />
1.362     www      1159:                         <a href="/adm/roles">'.&mt('Select a course').'</a><br />';
1.340     albertel 1160: 	    }
                   1161: 	}
1.140     bowersj2 1162: 
                   1163:         # Step two: Locate what kind of here marker is necessary
                   1164:         # Determine where the "here" marker is and where the screen jumps to.
                   1165: 
1.332     albertel 1166:         if ($env{'form.postsymb'} ne '') {
1.320     albertel 1167:             $here = $jump = &Apache::lonnet::symbclean($env{'form.postsymb'});
1.332     albertel 1168:         } elsif ($env{'form.postdata'} ne '') {
1.140     bowersj2 1169:             # couldn't find a symb, is there a URL?
1.320     albertel 1170:             my $currenturl = $env{'form.postdata'};
1.158     bowersj2 1171:             #$currenturl=~s/^http\:\/\///;
                   1172:             #$currenturl=~s/^[^\/]+//;
1.140     bowersj2 1173:             
1.158     bowersj2 1174:             $here = $jump = &Apache::lonnet::symbread($currenturl);
1.331     albertel 1175: 	}
                   1176: 	if ($here eq '') {
1.317     albertel 1177: 	    my $last;
1.320     albertel 1178: 	    if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db',
1.317     albertel 1179:                     &GDBM_READER(),0640)) {
                   1180: 		$last=$hash{'last_known'};
                   1181: 		untie(%hash);
                   1182: 	    }
                   1183: 	    if ($last) { $here = $jump = $last; }
                   1184: 	}
1.158     bowersj2 1185: 
1.140     bowersj2 1186:         # Step three: Ensure the folders are open
                   1187:         my $mapIterator = $navmap->getIterator(undef, undef, undef, 1);
1.222     bowersj2 1188:         my $curRes;
1.140     bowersj2 1189:         my $found = 0;
                   1190:         
                   1191:         # We only need to do this if we need to open the maps to show the
                   1192:         # current position. This will change the counter so we can't count
                   1193:         # for the jump marker with this loop.
1.294     albertel 1194:         while ($here && ($curRes = $mapIterator->next()) && !$found) {
1.158     bowersj2 1195:             if (ref($curRes) && $curRes->symb() eq $here) {
1.140     bowersj2 1196:                 my $mapStack = $mapIterator->getStack();
                   1197:                 
                   1198:                 # Ensure the parent maps are open
                   1199:                 for my $map (@{$mapStack}) {
                   1200:                     if ($condition) {
                   1201:                         undef $filterHash->{$map->map_pc()};
                   1202:                     } else {
                   1203:                         $filterHash->{$map->map_pc()} = 1;
                   1204:                     }
                   1205:                 }
                   1206:                 $found = 1;
                   1207:             }
                   1208:         }            
1.159     bowersj2 1209:     }        
1.140     bowersj2 1210: 
1.320     albertel 1211:     if ( !defined($args->{'iterator'}) && $env{'form.folderManip'} ) { # we came from a user's manipulation of the nav page
1.140     bowersj2 1212:         # If this is a click on a folder or something, we want to preserve the "here"
                   1213:         # from the querystring, and get the new "jump" marker
1.320     albertel 1214:         $here = $env{'form.here'};
                   1215:         $jump = $env{'form.jump'};
1.140     bowersj2 1216:     } 
                   1217:     
1.132     bowersj2 1218:     my $it = $args->{'iterator'};
                   1219:     if (!defined($it)) {
1.320     albertel 1220:         # Construct a default iterator based on $env{'form.'} information
1.140     bowersj2 1221:         
                   1222:         # Step 1: Check to see if we have a navmap
                   1223:         if (!defined($navmap)) {
1.221     bowersj2 1224:             $navmap = Apache::lonnavmaps::navmap->new();
1.140     bowersj2 1225:         }
                   1226: 
1.144     bowersj2 1227:         # See if we're being passed a specific map
                   1228:         if ($args->{'iterator_map'}) {
                   1229:             my $map = $args->{'iterator_map'};
1.145     bowersj2 1230:             $map = $navmap->getResourceByUrl($map);
1.144     bowersj2 1231:             my $firstResource = $map->map_start();
                   1232:             my $finishResource = $map->map_finish();
                   1233: 
                   1234:             $args->{'iterator'} = $it = $navmap->getIterator($firstResource, $finishResource, $filterHash, $condition);
                   1235:         } else {
                   1236:             $args->{'iterator'} = $it = $navmap->getIterator(undef, undef, $filterHash, $condition);
                   1237:         }
1.132     bowersj2 1238:     }
1.256     albertel 1239: 
1.159     bowersj2 1240:     # (re-)Locate the jump point, if any
1.191     bowersj2 1241:     # Note this does not take filtering or hidden into account... need
                   1242:     # to be fixed?
1.159     bowersj2 1243:     my $mapIterator = $navmap->getIterator(undef, undef, $filterHash, 0);
1.222     bowersj2 1244:     my $curRes;
1.159     bowersj2 1245:     my $foundJump = 0;
                   1246:     my $counter = 0;
                   1247:     
1.222     bowersj2 1248:     while (($curRes = $mapIterator->next()) && !$foundJump) {
1.159     bowersj2 1249:         if (ref($curRes)) { $counter++; }
                   1250:         
                   1251:         if (ref($curRes) && $jump eq $curRes->symb()) {
                   1252:             
                   1253:             # This is why we have to use the main iterator instead of the
                   1254:             # potentially faster DFS: The count has to be the same, so
                   1255:             # the order has to be the same, which DFS won't give us.
                   1256:             $args->{'currentJumpIndex'} = $counter;
                   1257:             $foundJump = 1;
                   1258:         }
                   1259:     }
                   1260: 
1.132     bowersj2 1261:     my $showParts = setDefault($args->{'showParts'}, 1);
                   1262:     my $condenseParts = setDefault($args->{'condenseParts'}, 1);
1.139     bowersj2 1263:     # keeps track of when the current resource is found,
                   1264:     # so we can back up a few and put the anchor above the
                   1265:     # current resource
1.140     bowersj2 1266:     my $printKey = $args->{'printKey'};
                   1267:     my $printCloseAll = $args->{'printCloseAll'};
                   1268:     if (!defined($printCloseAll)) { $printCloseAll = 1; }
1.298     albertel 1269: 
1.140     bowersj2 1270:     # Print key?
                   1271:     if ($printKey) {
                   1272:         $result .= '<table border="0" cellpadding="2" cellspacing="0">';
                   1273:         my $date=localtime;
                   1274:         $result.='<tr><td align="right" valign="bottom">Key:&nbsp;&nbsp;</td>';
1.298     albertel 1275: 	my $location=&Apache::loncommon::lonhttpdurl("/adm/lonMisc");
1.140     bowersj2 1276:         if ($navmap->{LAST_CHECK}) {
                   1277:             $result .= 
1.298     albertel 1278:                 '<img src="'.$location.'/chat.gif"> '.&mt('New discussion since').' '.
1.140     bowersj2 1279:                 strftime("%A, %b %e at %I:%M %P", localtime($navmap->{LAST_CHECK})).
                   1280:                 '</td><td align="center" valign="bottom">&nbsp;&nbsp;'.
1.298     albertel 1281:                 '<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').'<p>'.
1.140     bowersj2 1282:                 '</td>'; 
                   1283:         } else {
                   1284:             $result .= '<td align="center" valign="bottom">&nbsp;&nbsp;'.
1.298     albertel 1285:                 '<img src="'.$location.'/chat.gif"> '.&mt('Discussions').'</td><td align="center" valign="bottom">'.
                   1286:                 '&nbsp;&nbsp;<img src="'.$location.'/feedback.gif"> '.&mt('New message (click to open)').
1.140     bowersj2 1287:                 '</td>'; 
                   1288:         }
                   1289: 
                   1290:         $result .= '</tr></table>';
                   1291:     }
                   1292: 
1.172     bowersj2 1293:     if ($printCloseAll && !$args->{'resource_no_folder_link'}) {
1.281     albertel 1294: 	my ($link,$text);
1.140     bowersj2 1295:         if ($condition) {
1.392     albertel 1296: 	    $link='"navmaps?condition=0&amp;filter=&amp;'.$queryString.
1.384     www      1297: 		'&here='.&escape($here).'"';
1.377     www      1298: 	    $text='Close all folders';
1.140     bowersj2 1299:         } else {
1.392     albertel 1300: 	    $link='"navmaps?condition=1&amp;filter=&amp;'.$queryString.
1.384     www      1301: 		'&here='.&escape($here).'"';
1.377     www      1302: 	    $text='Open all folders';
1.281     albertel 1303:         }
                   1304: 	if ($args->{'caller'} eq 'navmapsdisplay') {
                   1305: 	    &add_linkitem($args->{'linkitems'},'changefolder',
                   1306: 			  'location.href='.$link,$text);
                   1307: 	} else {
                   1308: 	    $result.='<a href='.$link.'>'.&mt($text).'</a>';
                   1309: 	}
1.266     raeburn  1310:         $result .= "\n";
                   1311:     }
1.140     bowersj2 1312: 
1.266     raeburn  1313:     # Check for any unread discussions in all resources.
1.281     albertel 1314:     if ($args->{'caller'} eq 'navmapsdisplay') {
1.296     albertel 1315: 	&add_linkitem($args->{'linkitems'},'clearbubbles',
                   1316: 		      'document.clearbubbles.submit()',
                   1317: 		      'Mark all posts read');
1.297     albertel 1318: 	my $time=time;
1.296     albertel 1319: 	$result .= (<<END);
                   1320:     <form name="clearbubbles" method="post" action="/adm/feedback">
                   1321: 	<input type="hidden" name="navurl" value="$ENV{'QUERY_STRING'}" />
1.297     albertel 1322: 	<input type="hidden" name="navtime" value="$time" />
1.296     albertel 1323: END
                   1324:         if ($args->{'sort'} eq 'discussion') { 
                   1325: 	    my $totdisc = 0;
                   1326: 	    my $haveDisc = '';
                   1327: 	    my @allres=$navmap->retrieveResources();
                   1328: 	    foreach my $resource (@allres) {
                   1329: 		if ($resource->hasDiscussion()) {
1.323     albertel 1330: 		    $haveDisc .= $resource->wrap_symb().':';
1.296     albertel 1331: 		    $totdisc ++;
1.267     albertel 1332: 		}
                   1333: 	    }
1.296     albertel 1334: 	    if ($totdisc > 0) {
                   1335: 		$haveDisc =~ s/:$//;
                   1336: 		$result .= (<<END);
                   1337: 	<input type="hidden" name="navmaps" value="$haveDisc" />
                   1338:     </form>
                   1339: END
                   1340:             }
1.297     albertel 1341: 	}
                   1342: 	$result.='</form>';
1.266     raeburn  1343:     }
1.276     albertel 1344: 
1.281     albertel 1345:     if ($args->{'caller'} eq 'navmapsdisplay') {
1.283     raeburn  1346:         $result .= '<table><tr><td>'.
1.388     albertel 1347:                    &Apache::loncommon::help_open_menu('Navigation Screen','Navigation_Screen',undef,'RAT').'</td>';
1.320     albertel 1348: 	if ($env{'environment.remotenavmap'} ne 'on') {
1.283     raeburn  1349: 	    $result .= '<td>&nbsp;</td>'; 
                   1350:         } else {
                   1351: 	    $result .= '</tr><tr>'; 
                   1352:         }
1.281     albertel 1353: 	$result.=&show_linkitems($args->{'linkitems'});
                   1354:         if ($args->{'sort_html'}) {
1.320     albertel 1355: 	    if ($env{'environment.remotenavmap'} ne 'on') {
1.281     albertel 1356: 		$result.='<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td>'.
                   1357: 		    '<td align="right">'.$args->{'sort_html'}.'</td></tr>';
                   1358: 	    } else {
                   1359: 		$result.='</tr><tr><td align="left"><br />'.
                   1360: 		    $args->{'sort_html'}.'</td></tr>';
                   1361: 	    }
                   1362: 	}
                   1363:         $result .= '</table>';
                   1364:     } elsif ($args->{'sort_html'}) { 
                   1365:         $result.=$args->{'sort_html'}; 
                   1366:     }
1.276     albertel 1367: 
1.266     raeburn  1368:     $result .= "<br />\n";
1.140     bowersj2 1369:     if ($r) {
                   1370:         $r->print($result);
                   1371:         $r->rflush();
                   1372:         $result = "";
                   1373:     }
1.132     bowersj2 1374:     # End parameter setting
1.133     bowersj2 1375:             
1.132     bowersj2 1376:     # Data
1.140     bowersj2 1377:     $result .= '<table cellspacing="0" cellpadding="3" border="0" bgcolor="#FFFFFF">' ."\n";
1.132     bowersj2 1378:     my $res = "Apache::lonnavmaps::resource";
                   1379:     my %condenseStatuses =
                   1380:         ( $res->NETWORK_FAILURE    => 1,
                   1381:           $res->NOTHING_SET        => 1,
                   1382:           $res->CORRECT            => 1 );
                   1383:     my @backgroundColors = ("#FFFFFF", "#F6F6F6");
1.133     bowersj2 1384: 
                   1385:     # Shared variables
                   1386:     $args->{'counter'} = 0; # counts the rows
                   1387:     $args->{'indentLevel'} = 0;
                   1388:     $args->{'isNewBranch'} = 0;
                   1389:     $args->{'condensed'} = 0;    
1.298     albertel 1390:     my $location=
                   1391: 	&Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace1.gif");
1.329     albertel 1392:     $args->{'indentString'} = setDefault($args->{'indentString'}, "<img src='$location' width='25' height='1' alt='&nbsp;&nbsp;' border='0' />");
1.133     bowersj2 1393:     $args->{'displayedHereMarker'} = 0;
1.132     bowersj2 1394: 
1.190     bowersj2 1395:     # If we're suppressing empty sequences, look for them here. Use DFS for speed,
                   1396:     # since structure actually doesn't matter, except what map has what resources.
                   1397:     if ($args->{'suppressEmptySequences'}) {
                   1398:         my $dfsit = Apache::lonnavmaps::DFSiterator->new($navmap,
                   1399:                                                          $it->{FIRST_RESOURCE},
                   1400:                                                          $it->{FINISH_RESOURCE},
                   1401:                                                          {}, undef, 1);
1.222     bowersj2 1402:         my $depth = 0;
1.190     bowersj2 1403:         $dfsit->next();
                   1404:         my $curRes = $dfsit->next();
                   1405:         while ($depth > -1) {
                   1406:             if ($curRes == $dfsit->BEGIN_MAP()) { $depth++; }
                   1407:             if ($curRes == $dfsit->END_MAP()) { $depth--; }
                   1408: 
                   1409:             if (ref($curRes)) { 
                   1410:                 # Parallel pre-processing: Do sequences have non-filtered-out children?
1.201     bowersj2 1411:                 if ($curRes->is_map()) {
1.190     bowersj2 1412:                     $curRes->{DATA}->{HAS_VISIBLE_CHILDREN} = 0;
                   1413:                     # Sequences themselves do not count as visible children,
                   1414:                     # unless those sequences also have visible children.
                   1415:                     # This means if a sequence appears, there's a "promise"
                   1416:                     # that there's something under it if you open it, somewhere.
                   1417:                 } else {
                   1418:                     # Not a sequence: if it's filtered, ignore it, otherwise
                   1419:                     # rise up the stack and mark the sequences as having children
                   1420:                     if (&$filterFunc($curRes)) {
                   1421:                         for my $sequence (@{$dfsit->getStack()}) {
                   1422:                             $sequence->{DATA}->{HAS_VISIBLE_CHILDREN} = 1;
                   1423:                         }
                   1424:                     }
                   1425:                 }
                   1426:             }
                   1427:         } continue {
                   1428:             $curRes = $dfsit->next();
                   1429:         }
                   1430:     }
                   1431: 
1.133     bowersj2 1432:     my $displayedJumpMarker = 0;
1.132     bowersj2 1433:     # Set up iteration.
                   1434:     my $now = time();
                   1435:     my $in24Hours = $now + 24 * 60 * 60;
                   1436:     my $rownum = 0;
                   1437: 
1.141     bowersj2 1438:     # export "here" marker information
                   1439:     $args->{'here'} = $here;
                   1440: 
1.222     bowersj2 1441:     $args->{'indentLevel'} = -1; # first BEGIN_MAP takes this to 0
1.270     albertel 1442:     my @resources;
                   1443:     my $code='';# sub { !(shift->is_map();) };
                   1444:     if ($args->{'sort'} eq 'title') {
1.276     albertel 1445:         my $oldFilterFunc = $filterFunc;
                   1446: 	my $filterFunc= 
                   1447: 	    sub {
                   1448: 		my ($res)=@_;
                   1449: 		if ($res->is_map()) { return 0;}
                   1450: 		return &$oldFilterFunc($res);
                   1451: 	    };
                   1452: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
1.296     albertel 1453: 	@resources= sort { &cmp_title($a,$b) } @resources;
                   1454:     } elsif ($args->{'sort'} eq 'duedate') {
                   1455: 	my $oldFilterFunc = $filterFunc;
                   1456: 	my $filterFunc= 
                   1457: 	    sub {
                   1458: 		my ($res)=@_;
                   1459: 		if (!$res->is_problem()) { return 0;}
                   1460: 		return &$oldFilterFunc($res);
                   1461: 	    };
                   1462: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
1.289     raeburn  1463: 	@resources= sort {
1.270     albertel 1464: 	    if ($a->duedate ne $b->duedate) {
                   1465: 	        return $a->duedate cmp $b->duedate;
                   1466: 	    }
1.296     albertel 1467: 	    my $value=&cmp_title($a,$b);
                   1468: 	    return $value;
1.270     albertel 1469: 	} @resources;
1.296     albertel 1470:     } elsif ($args->{'sort'} eq 'discussion') {
                   1471: 	my $oldFilterFunc = $filterFunc;
                   1472: 	my $filterFunc= 
                   1473: 	    sub {
                   1474: 		my ($res)=@_;
                   1475: 		if (!$res->hasDiscussion() &&
                   1476: 		    !$res->getFeedback() &&
                   1477: 		    !$res->getErrors()) { return 0;}
                   1478: 		return &$oldFilterFunc($res);
                   1479: 	    };
                   1480: 	@resources=$navmap->retrieveResources(undef,$filterFunc);
                   1481: 	@resources= sort { &cmp_title($a,$b) } @resources;
1.276     albertel 1482:     } else {
                   1483: 	#unknow sort mechanism or default
                   1484: 	undef($args->{'sort'});
1.270     albertel 1485:     }
                   1486: 
1.276     albertel 1487: 
1.270     albertel 1488:     while (1) {
                   1489: 	if ($args->{'sort'}) {
                   1490: 	    $curRes = shift(@resources);
                   1491: 	} else {
                   1492: 	    $curRes = $it->next($closeAllPages);
                   1493: 	}
                   1494: 	if (!$curRes) { last; }
                   1495: 
1.132     bowersj2 1496:         # Maintain indentation level.
                   1497:         if ($curRes == $it->BEGIN_MAP() ||
                   1498:             $curRes == $it->BEGIN_BRANCH() ) {
1.133     bowersj2 1499:             $args->{'indentLevel'}++;
1.132     bowersj2 1500:         }
                   1501:         if ($curRes == $it->END_MAP() ||
                   1502:             $curRes == $it->END_BRANCH() ) {
1.133     bowersj2 1503:             $args->{'indentLevel'}--;
1.132     bowersj2 1504:         }
                   1505:         # Notice new branches
                   1506:         if ($curRes == $it->BEGIN_BRANCH()) {
1.133     bowersj2 1507:             $args->{'isNewBranch'} = 1;
                   1508:         }
                   1509: 
                   1510:         # If this isn't an actual resource, continue on
                   1511:         if (!ref($curRes)) {
                   1512:             next;
1.132     bowersj2 1513:         }
1.133     bowersj2 1514: 
1.143     bowersj2 1515:         # If this has been filtered out, continue on
                   1516:         if (!(&$filterFunc($curRes))) {
                   1517:             $args->{'isNewBranch'} = 0; # Don't falsely remember this
                   1518:             next;
                   1519:         } 
1.132     bowersj2 1520: 
1.190     bowersj2 1521:         # If this is an empty sequence and we're filtering them, continue on
1.201     bowersj2 1522:         if ($curRes->is_map() && $args->{'suppressEmptySequences'} &&
1.190     bowersj2 1523:             !$curRes->{DATA}->{HAS_VISIBLE_CHILDREN}) {
                   1524:             next;
                   1525:         }
                   1526: 
1.153     bowersj2 1527:         # If we're suppressing navmaps and this is a navmap, continue on
                   1528:         if ($suppressNavmap && $curRes->src() =~ /^\/adm\/navmaps/) {
                   1529:             next;
                   1530:         }
                   1531: 
1.191     bowersj2 1532:         $args->{'counter'}++;
                   1533: 
1.132     bowersj2 1534:         # Does it have multiple parts?
1.133     bowersj2 1535:         $args->{'multipart'} = 0;
                   1536:         $args->{'condensed'} = 0;
1.132     bowersj2 1537:         my @parts;
                   1538:             
                   1539:         # Decide what parts to show.
1.133     bowersj2 1540:         if ($curRes->is_problem() && $showParts) {
1.132     bowersj2 1541:             @parts = @{$curRes->parts()};
1.192     bowersj2 1542:             $args->{'multipart'} = $curRes->multipart();
1.132     bowersj2 1543:             
                   1544:             if ($condenseParts) { # do the condensation
1.133     bowersj2 1545:                 if (!$args->{'condensed'}) {
1.132     bowersj2 1546:                     # Decide whether to condense based on similarity
1.192     bowersj2 1547:                     my $status = $curRes->status($parts[0]);
                   1548:                     my $due = $curRes->duedate($parts[0]);
                   1549:                     my $open = $curRes->opendate($parts[0]);
1.132     bowersj2 1550:                     my $statusAllSame = 1;
                   1551:                     my $dueAllSame = 1;
                   1552:                     my $openAllSame = 1;
1.192     bowersj2 1553:                     for (my $i = 1; $i < scalar(@parts); $i++) {
1.132     bowersj2 1554:                         if ($curRes->status($parts[$i]) != $status){
                   1555:                             $statusAllSame = 0;
                   1556:                         }
                   1557:                         if ($curRes->duedate($parts[$i]) != $due ) {
                   1558:                             $dueAllSame = 0;
                   1559:                         }
                   1560:                         if ($curRes->opendate($parts[$i]) != $open) {
                   1561:                             $openAllSame = 0;
                   1562:                         }
                   1563:                     }
                   1564:                     # $*allSame is true if all the statuses were
                   1565:                     # the same. Now, if they are all the same and
                   1566:                     # match one of the statuses to condense, or they
                   1567:                     # are all open with the same due date, or they are
                   1568:                     # all OPEN_LATER with the same open date, display the
                   1569:                     # status of the first non-zero part (to get the 'correct'
                   1570:                     # status right, since 0 is never 'correct' or 'open').
                   1571:                     if (($statusAllSame && defined($condenseStatuses{$status})) ||
                   1572:                         ($dueAllSame && $status == $curRes->OPEN && $statusAllSame)||
                   1573:                         ($openAllSame && $status == $curRes->OPEN_LATER && $statusAllSame) ){
1.192     bowersj2 1574:                         @parts = ($parts[0]);
1.133     bowersj2 1575:                         $args->{'condensed'} = 1;
1.132     bowersj2 1576:                     }
                   1577:                 }
1.198     bowersj2 1578: 		# Multipart problem with one part: always "condense" (happens
                   1579: 		#  to match the desirable behavior)
                   1580: 		if ($curRes->countParts() == 1) {
                   1581: 		    @parts = ($parts[0]);
                   1582: 		    $args->{'condensed'} = 1;
                   1583: 		}
1.132     bowersj2 1584:             }
1.157     bowersj2 1585:         } 
1.132     bowersj2 1586:             
                   1587:         # If the multipart problem was condensed, "forget" it was multipart
                   1588:         if (scalar(@parts) == 1) {
1.133     bowersj2 1589:             $args->{'multipart'} = 0;
1.192     bowersj2 1590:         } else {
                   1591:             # Add part 0 so we display it correctly.
                   1592:             unshift @parts, '0';
1.132     bowersj2 1593:         }
1.310     albertel 1594: 	
                   1595: 	{
                   1596: 	    my ($src,$symb,$anchor,$stack);
                   1597: 	    if ($args->{'sort'}) {
                   1598: 		my $it = $navmap->getIterator(undef, undef, undef, 1);
                   1599: 		while ( my $res=$it->next()) {
                   1600: 		    if (ref($res) &&
                   1601: 			$res->symb() eq  $curRes->symb()) { last; }
                   1602: 		}
                   1603: 		$stack=$it->getStack();
                   1604: 	    } else {
                   1605: 		$stack=$it->getStack();
                   1606: 	    }
                   1607: 	    ($src,$symb,$anchor)=getLinkForResource($stack);
                   1608: 	    if (defined($anchor)) { $anchor='#'.$anchor; }
                   1609: 	    my $srcHasQuestion = $src =~ /\?/;
                   1610: 	    $args->{"resourceLink"} = $src.
                   1611: 		($srcHasQuestion?'&':'?') .
1.384     www      1612: 		'symb=' . &escape($symb).$anchor;
1.310     albertel 1613: 	}
1.132     bowersj2 1614:         # Now, we've decided what parts to show. Loop through them and
                   1615:         # show them.
1.192     bowersj2 1616:         foreach my $part (@parts) {
1.132     bowersj2 1617:             $rownum ++;
                   1618:             my $backgroundColor = $backgroundColors[$rownum % scalar(@backgroundColors)];
                   1619:             
                   1620:             $result .= "  <tr bgcolor='$backgroundColor'>\n";
1.134     bowersj2 1621: 
                   1622:             # Set up some data about the parts that the cols might want
                   1623:             my $filter = $it->{FILTER};
1.270     albertel 1624: 
1.132     bowersj2 1625:             # Now, display each column.
                   1626:             foreach my $col (@$cols) {
1.139     bowersj2 1627:                 my $colHTML = '';
                   1628:                 if (ref($col)) {
                   1629:                     $colHTML .= &$col($curRes, $part, $args);
                   1630:                 } else {
                   1631:                     $colHTML .= &{$preparedColumns[$col]}($curRes, $part, $args);
                   1632:                 }
1.132     bowersj2 1633: 
1.133     bowersj2 1634:                 # If this is the first column and it's time to print
                   1635:                 # the anchor, do so
                   1636:                 if ($col == $cols->[0] && 
                   1637:                     $args->{'counter'} == $args->{'currentJumpIndex'} - 
1.139     bowersj2 1638:                     $currentJumpDelta) {
                   1639:                     # Jam the anchor after the <td> tag;
                   1640:                     # necessary for valid HTML (which Mozilla requires)
                   1641:                     $colHTML =~ s/\>/\>\<a name="curloc" \/\>/;
1.133     bowersj2 1642:                     $displayedJumpMarker = 1;
                   1643:                 }
1.139     bowersj2 1644:                 $result .= $colHTML . "\n";
1.132     bowersj2 1645:             }
1.139     bowersj2 1646:             $result .= "    </tr>\n";
1.140     bowersj2 1647:             $args->{'isNewBranch'} = 0;
1.139     bowersj2 1648:         }
1.153     bowersj2 1649: 
1.139     bowersj2 1650:         if ($r && $rownum % 20 == 0) {
                   1651:             $r->print($result);
                   1652:             $result = "";
                   1653:             $r->rflush();
1.132     bowersj2 1654:         }
1.156     bowersj2 1655:     } continue {
1.208     bowersj2 1656: 	if ($r) {
                   1657: 	    # If we have the connection, make sure the user is still connected
                   1658: 	    my $c = $r->connection;
                   1659: 	    if ($c->aborted()) {
                   1660: 		# Who cares what we do, nobody will see it anyhow.
                   1661: 		return '';
                   1662: 	    }
                   1663: 	}
1.132     bowersj2 1664:     }
                   1665:     
1.134     bowersj2 1666:     # Print out the part that jumps to #curloc if it exists
1.159     bowersj2 1667:     # delay needed because the browser is processing the jump before
                   1668:     # it finishes rendering, so it goes to the wrong place!
                   1669:     # onload might be better, but this routine has no access to that.
                   1670:     # On mozilla, the 0-millisecond timeout seems to prevent this;
                   1671:     # it's quite likely this might fix other browsers, too, and 
                   1672:     # certainly won't hurt anything.
1.139     bowersj2 1673:     if ($displayedJumpMarker) {
1.247     albertel 1674:         $result .= "
                   1675: <script>
                   1676: if (location.href.indexOf('#curloc')==-1) {
                   1677:     setTimeout(\"location += '#curloc';\", 0)
                   1678: }
                   1679: </script>";
1.134     bowersj2 1680:     }
                   1681: 
1.132     bowersj2 1682:     $result .= "</table>";
1.140     bowersj2 1683:     
                   1684:     if ($r) {
                   1685:         $r->print($result);
                   1686:         $result = "";
                   1687:         $r->rflush();
                   1688:     }
                   1689:         
1.132     bowersj2 1690:     return $result;
                   1691: }
                   1692: 
1.281     albertel 1693: sub add_linkitem {
                   1694:     my ($linkitems,$name,$cmd,$text)=@_;
                   1695:     $$linkitems{$name}{'cmd'}=$cmd;
                   1696:     $$linkitems{$name}{'text'}=&mt($text);
                   1697: }
                   1698: 
                   1699: sub show_linkitems {
                   1700:     my ($linkitems)=@_;
1.312     albertel 1701:     my @linkorder = ("blank","launchnav","closenav","firsthomework",
                   1702: 		     "everything","uncompleted","changefolder","clearbubbles");
1.281     albertel 1703:     
                   1704:     my $result .= (<<ENDBLOCK);
1.283     raeburn  1705:               <td align="left">
1.281     albertel 1706: <script type="text/javascript">
                   1707:     function changeNavDisplay () {
                   1708: 	var navchoice = document.linkitems.toplink[document.linkitems.toplink.selectedIndex].value;
                   1709: ENDBLOCK
                   1710:     foreach my $link (@linkorder) {
                   1711: 	$result.= "if (navchoice == '$link') {".
                   1712: 	    $linkitems->{$link}{'cmd'}."}\n";
                   1713:     }
                   1714:     $result.='}
                   1715:               </script>
                   1716:                    <form name="linkitems" method="post">
                   1717:                        <nobr><select name="toplink">'."\n";
                   1718:     foreach my $link (@linkorder) {
                   1719: 	if (defined($linkitems->{$link})) {
                   1720: 	    if ($linkitems->{$link}{'text'} ne '') {
                   1721: 		$result .= ' <option value="'.$link.'">'.
                   1722: 		    $linkitems->{$link}{'text'}."</option>\n";
                   1723: 	    }
                   1724: 	}
                   1725:     }
                   1726:     $result .= '</select>&nbsp;<input type="button" name="chgnav"
                   1727:                    value="Go" onClick="javascript:changeNavDisplay()" />
                   1728:                 </nobr></form></td>'."\n";
1.298     albertel 1729: 	
1.281     albertel 1730:     return $result;
                   1731: }
                   1732: 
1.133     bowersj2 1733: 1;
                   1734: 
                   1735: package Apache::lonnavmaps::navmap;
                   1736: 
                   1737: =pod
                   1738: 
1.216     bowersj2 1739: =head1 Object: Apache::lonnavmaps::navmap
1.133     bowersj2 1740: 
1.217     bowersj2 1741: =head2 Overview
1.133     bowersj2 1742: 
1.217     bowersj2 1743: The navmap object's job is to provide access to the resources
                   1744: in the course as Apache::lonnavmaps::resource objects, and to
                   1745: query and manage the relationship between those resource objects.
                   1746: 
                   1747: Generally, you'll use the navmap object in one of three basic ways.
                   1748: In order of increasing complexity and power:
                   1749: 
                   1750: =over 4
                   1751: 
1.358     albertel 1752: =item * C<$navmap-E<gt>getByX>, where X is B<Id>, B<Symb> or B<MapPc> and getResourceByUrl. This provides
1.217     bowersj2 1753:     various ways to obtain resource objects, based on various identifiers.
                   1754:     Use this when you want to request information about one object or 
                   1755:     a handful of resources you already know the identities of, from some
                   1756:     other source. For more about Ids, Symbs, and MapPcs, see the
                   1757:     Resource documentation. Note that Url should be a B<last resort>,
1.358     albertel 1758:     not your first choice; it only really works when there is only one
1.217     bowersj2 1759:     instance of the resource in the course, which only applies to
1.358     albertel 1760:     maps, and even that may change in the future (see the B<getResourceByUrl>
                   1761:     documentation for more details.)
1.217     bowersj2 1762: 
                   1763: =item * C<my @resources = $navmap-E<gt>retrieveResources(args)>. This
                   1764:     retrieves resources matching some criterion and returns them
                   1765:     in a flat array, with no structure information. Use this when
                   1766:     you are manipulating a series of resources, based on what map
                   1767:     the are in, but do not care about branching, or exactly how
                   1768:     the maps and resources are related. This is the most common case.
                   1769: 
                   1770: =item * C<$it = $navmap-E<gt>getIterator(args)>. This allows you traverse
                   1771:     the course's navmap in various ways without writing the traversal
                   1772:     code yourself. See iterator documentation below. Use this when
                   1773:     you need to know absolutely everything about the course, including
                   1774:     branches and the precise relationship between maps and resources.
                   1775: 
                   1776: =back
                   1777: 
                   1778: =head2 Creation And Destruction
                   1779: 
                   1780: To create a navmap object, use the following function:
1.133     bowersj2 1781: 
                   1782: =over 4
                   1783: 
1.221     bowersj2 1784: =item * B<Apache::lonnavmaps::navmap-E<gt>new>():
1.133     bowersj2 1785: 
1.221     bowersj2 1786: Creates a new navmap object. Returns the navmap object if this is
                   1787: successful, or B<undef> if not.
1.174     albertel 1788: 
1.216     bowersj2 1789: =back
                   1790: 
                   1791: =head2 Methods
                   1792: 
                   1793: =over 4
                   1794: 
1.174     albertel 1795: =item * B<getIterator>(first, finish, filter, condition):
                   1796: 
                   1797: See iterator documentation below.
1.133     bowersj2 1798: 
                   1799: =cut
                   1800: 
                   1801: use strict;
                   1802: use GDBM_File;
1.320     albertel 1803: use Apache::lonnet;
1.397   ! albertel 1804: use LONCAPA;
1.133     bowersj2 1805: 
                   1806: sub new {
                   1807:     # magic invocation to create a class instance
                   1808:     my $proto = shift;
                   1809:     my $class = ref($proto) || $proto;
                   1810:     my $self = {};
                   1811: 
                   1812:     # Resource cache stores navmap resources as we reference them. We generate
                   1813:     # them on-demand so we don't pay for creating resources unless we use them.
                   1814:     $self->{RESOURCE_CACHE} = {};
                   1815: 
                   1816:     # Network failure flag, if we accessed the course or user opt and
                   1817:     # failed
                   1818:     $self->{NETWORK_FAILURE} = 0;
                   1819: 
                   1820:     # tie the nav hash
                   1821: 
1.168     bowersj2 1822:     my %navmaphash;
                   1823:     my %parmhash;
1.320     albertel 1824:     my $courseFn = $env{"request.course.fn"};
1.220     bowersj2 1825:     if (!(tie(%navmaphash, 'GDBM_File', "${courseFn}.db",
1.133     bowersj2 1826:               &GDBM_READER(), 0640))) {
                   1827:         return undef;
                   1828:     }
                   1829:     
1.220     bowersj2 1830:     if (!(tie(%parmhash, 'GDBM_File', "${courseFn}_parms.db",
1.133     bowersj2 1831:               &GDBM_READER(), 0640)))
                   1832:     {
1.170     matthew  1833:         untie %{$self->{PARM_HASH}};
1.133     bowersj2 1834:         return undef;
                   1835:     }
                   1836: 
1.199     bowersj2 1837:     $self->{NAV_HASH} = \%navmaphash;
1.168     bowersj2 1838:     $self->{PARM_HASH} = \%parmhash;
1.221     bowersj2 1839:     $self->{PARM_CACHE} = {};
1.133     bowersj2 1840: 
                   1841:     bless($self);
                   1842:         
                   1843:     return $self;
                   1844: }
                   1845: 
1.221     bowersj2 1846: sub generate_course_user_opt {
1.133     bowersj2 1847:     my $self = shift;
1.221     bowersj2 1848:     if ($self->{COURSE_USER_OPT_GENERATED}) { return; }
1.133     bowersj2 1849: 
1.320     albertel 1850:     my $uname=$env{'user.name'};
                   1851:     my $udom=$env{'user.domain'};
                   1852:     my $cid=$env{'request.course.id'};
1.326     albertel 1853:     my $cdom=$env{'course.'.$cid.'.domain'};
                   1854:     my $cnum=$env{'course.'.$cid.'.num'};
1.221     bowersj2 1855:     
1.133     bowersj2 1856: # ------------------------------------------------- Get coursedata (if present)
1.326     albertel 1857:     my $courseopt=&Apache::lonnet::get_courseresdata($cnum,$cdom);
                   1858:     # Check for network failure
                   1859:     if (!ref($courseopt)) {
                   1860: 	if ( $courseopt =~ /no.such.host/i || $courseopt =~ /con_lost/i) {
1.324     albertel 1861: 	    $self->{NETWORK_FAILURE} = 1;
1.221     bowersj2 1862: 	}
1.326     albertel 1863: 	undef($courseopt);
                   1864:     }
1.325     albertel 1865: 
1.133     bowersj2 1866: # --------------------------------------------------- Get userdata (if present)
1.325     albertel 1867: 	
1.326     albertel 1868:     my $useropt=&Apache::lonnet::get_userresdata($uname,$udom);
                   1869:     # Check for network failure
                   1870:     if (!ref($useropt)) {
                   1871: 	if ( $useropt =~ /no.such.host/i || $useropt =~ /con_lost/i) {
1.324     albertel 1872: 	    $self->{NETWORK_FAILURE} = 1;
1.221     bowersj2 1873: 	}
1.326     albertel 1874: 	undef($useropt);
1.221     bowersj2 1875:     }
                   1876: 
1.326     albertel 1877:     $self->{COURSE_OPT} = $courseopt;
                   1878:     $self->{USER_OPT} = $useropt;
                   1879: 
1.221     bowersj2 1880:     $self->{COURSE_USER_OPT_GENERATED} = 1;
                   1881:     
                   1882:     return;
                   1883: }
                   1884: 
                   1885: sub generate_email_discuss_status {
                   1886:     my $self = shift;
1.259     raeburn  1887:     my $symb = shift;
1.221     bowersj2 1888:     if ($self->{EMAIL_DISCUSS_GENERATED}) { return; }
1.133     bowersj2 1889: 
1.320     albertel 1890:     my $cid=$env{'request.course.id'};
1.326     albertel 1891:     my $cdom=$env{'course.'.$cid.'.domain'};
                   1892:     my $cnum=$env{'course.'.$cid.'.num'};
1.221     bowersj2 1893:     
                   1894:     my %emailstatus = &Apache::lonnet::dump('email_status');
                   1895:     my $logoutTime = $emailstatus{'logout'};
1.320     albertel 1896:     my $courseLeaveTime = $emailstatus{'logout_'.$env{'request.course.id'}};
1.221     bowersj2 1897:     $self->{LAST_CHECK} = (($courseLeaveTime > $logoutTime) ?
                   1898: 			   $courseLeaveTime : $logoutTime);
                   1899:     my %discussiontime = &Apache::lonnet::dump('discussiontimes', 
                   1900: 					       $cdom, $cnum);
1.259     raeburn  1901:     my %lastread = &Apache::lonnet::dump('nohist_'.$cid.'_discuss',
1.320     albertel 1902:                                         $env{'user.domain'},$env{'user.name'},'lastread');
1.259     raeburn  1903:     my %lastreadtime = ();
1.394     raeburn  1904:     foreach my $key (keys %lastread) {
                   1905:         my $shortkey = $key;
                   1906:         $shortkey =~ s/_lastread$//;
                   1907:         $lastreadtime{$shortkey} = $lastread{$key};
1.259     raeburn  1908:     }
                   1909: 
1.221     bowersj2 1910:     my %feedback=();
                   1911:     my %error=();
1.325     albertel 1912:     my @keys = &Apache::lonnet::getkeys('nohist_email',$env{'user.domain'},
                   1913: 					$env{'user.name'});
1.221     bowersj2 1914:     
1.325     albertel 1915:     foreach my $msgid (@keys) {
1.295     albertel 1916: 	if ((!$emailstatus{$msgid}) || ($emailstatus{$msgid} eq 'new')) {
1.395     raeburn  1917:             my ($sendtime,$shortsubj,$fromname,$fromdomain,$status,$fromcid,
                   1918:                 $symb,$error) = &Apache::lonmsg::unpackmsgid($msgid);
1.396     raeburn  1919:             &Apache::lonenc::check_decrypt(\$symb); 
                   1920:             if (($fromcid ne '') && ($fromcid ne $cid)) {
                   1921:                 next;
                   1922:             }
1.395     raeburn  1923:             if (defined($symb)) {
                   1924:                 if (defined($error) && $error == 1) {
                   1925:                     $error{$symb}.=','.$msgid;
                   1926:                 } else {
                   1927:                     $feedback{$symb}.=','.$msgid;
                   1928:                 }
                   1929:             } else {
                   1930:                 my $plain=
                   1931:                     &LONCAPA::unescape(&LONCAPA::unescape($msgid));
                   1932:                 if ($plain=~/ \[([^\]]+)\]\:/) {
                   1933:                     my $url=$1;
                   1934:                     if ($plain=~/\:Error \[/) {
                   1935:                         $error{$url}.=','.$msgid;
                   1936:                     } else {
                   1937:                         $feedback{$url}.=','.$msgid;
                   1938:                     }
                   1939:                 }
                   1940:             }
1.221     bowersj2 1941: 	}
1.211     bowersj2 1942:     }
1.221     bowersj2 1943:     
1.395     raeburn  1944:     #symbs of resources that have feedbacks (will be urls pre-2.3)
1.221     bowersj2 1945:     $self->{FEEDBACK} = \%feedback;
1.395     raeburn  1946:     #or errors (will be urls pre 2.3)
1.295     albertel 1947:     $self->{ERROR_MSG} = \%error;
1.221     bowersj2 1948:     $self->{DISCUSSION_TIME} = \%discussiontime;
                   1949:     $self->{EMAIL_STATUS} = \%emailstatus;
1.259     raeburn  1950:     $self->{LAST_READ} = \%lastreadtime;
1.221     bowersj2 1951:     
                   1952:     $self->{EMAIL_DISCUSS_GENERATED} = 1;
                   1953: }
                   1954: 
                   1955: sub get_user_data {
                   1956:     my $self = shift;
                   1957:     if ($self->{RETRIEVED_USER_DATA}) { return; }
1.211     bowersj2 1958: 
1.221     bowersj2 1959:     # Retrieve performance data on problems
1.320     albertel 1960:     my %student_data = Apache::lonnet::currentdump($env{'request.course.id'},
                   1961: 						   $env{'user.domain'},
                   1962: 						   $env{'user.name'});
1.221     bowersj2 1963:     $self->{STUDENT_DATA} = \%student_data;
1.133     bowersj2 1964: 
1.221     bowersj2 1965:     $self->{RETRIEVED_USER_DATA} = 1;
1.133     bowersj2 1966: }
                   1967: 
1.354     raeburn  1968: sub get_discussion_data {
                   1969:     my $self = shift;
                   1970:     if ($self->{RETRIEVED_DISCUSSION_DATA}) {
1.366     albertel 1971: 	return $self->{DISCUSSION_DATA};
1.354     raeburn  1972:     }
1.366     albertel 1973: 
                   1974:     $self->generate_email_discuss_status();    
                   1975: 
1.354     raeburn  1976:     my $cid=$env{'request.course.id'};
                   1977:     my $cdom=$env{'course.'.$cid.'.domain'};
                   1978:     my $cnum=$env{'course.'.$cid.'.num'};
                   1979:     # Retrieve discussion data for resources in course
1.367     albertel 1980:     my %discussion_data = &Apache::lonnet::dumpstore($cid,$cdom,$cnum);
1.366     albertel 1981: 
                   1982: 
1.354     raeburn  1983:     $self->{DISCUSSION_DATA} = \%discussion_data;
                   1984:     $self->{RETRIEVED_DISCUSSION_DATA} = 1;
                   1985:     return $self->{DISCUSSION_DATA};
                   1986: }
                   1987: 
                   1988: 
1.133     bowersj2 1989: # Internal function: Takes a key to look up in the nav hash and implements internal
                   1990: # memory caching of that key.
                   1991: sub navhash {
                   1992:     my $self = shift; my $key = shift;
                   1993:     return $self->{NAV_HASH}->{$key};
                   1994: }
                   1995: 
1.217     bowersj2 1996: =pod
                   1997: 
                   1998: =item * B<courseMapDefined>(): Returns true if the course map is defined, 
                   1999:     false otherwise. Undefined course maps indicate an error somewhere in
                   2000:     LON-CAPA, and you will not be able to proceed with using the navmap.
                   2001:     See the B<NAV> screen for an example of using this.
                   2002: 
                   2003: =cut
                   2004: 
1.133     bowersj2 2005: # Checks to see if coursemap is defined, matching test in old lonnavmaps
                   2006: sub courseMapDefined {
                   2007:     my $self = shift;
1.320     albertel 2008:     my $uri = &Apache::lonnet::clutter($env{'request.course.uri'});
1.133     bowersj2 2009: 
                   2010:     my $firstres = $self->navhash("map_start_$uri");
                   2011:     my $lastres = $self->navhash("map_finish_$uri");
                   2012:     return $firstres && $lastres;
                   2013: }
                   2014: 
                   2015: sub getIterator {
                   2016:     my $self = shift;
                   2017:     my $iterator = Apache::lonnavmaps::iterator->new($self, shift, shift,
1.314     albertel 2018:                                                      shift, undef, shift,
                   2019: 						     shift, shift);
1.133     bowersj2 2020:     return $iterator;
                   2021: }
                   2022: 
                   2023: # Private method: Does the given resource (as a symb string) have
                   2024: # current discussion? Returns 0 if chat/mail data not extracted.
                   2025: sub hasDiscussion {
                   2026:     my $self = shift;
                   2027:     my $symb = shift;
1.221     bowersj2 2028:     $self->generate_email_discuss_status();
                   2029: 
1.133     bowersj2 2030:     if (!defined($self->{DISCUSSION_TIME})) { return 0; }
                   2031: 
                   2032:     #return defined($self->{DISCUSSION_TIME}->{$symb});
1.259     raeburn  2033: 
1.323     albertel 2034:     # backward compatibility (bulletin boards used to be 'wrapped')
                   2035:     my $ressymb = $self->wrap_symb($symb);
1.259     raeburn  2036:     if ( defined ( $self->{LAST_READ}->{$ressymb} ) ) {
                   2037:         return $self->{DISCUSSION_TIME}->{$ressymb} > $self->{LAST_READ}->{$ressymb};
                   2038:     } else {
1.266     raeburn  2039: #        return $self->{DISCUSSION_TIME}->{$ressymb} >  $self->{LAST_CHECK}; # v.1.1 behavior 
                   2040:         return $self->{DISCUSSION_TIME}->{$ressymb} >  0; # in 1.2 will display speech bubble icons for all items with posts until marked as read (even if read in v 1.1).
1.259     raeburn  2041:     }
1.133     bowersj2 2042: }
                   2043: 
1.366     albertel 2044: sub last_post_time {
                   2045:     my $self = shift;
                   2046:     my $symb = shift;
                   2047:     my $ressymb = $self->wrap_symb($symb);
                   2048:     return $self->{DISCUSSION_TIME}->{$ressymb};
                   2049: }
                   2050: 
1.393     raeburn  2051: sub discussion_info {
1.366     albertel 2052:     my $self = shift;
                   2053:     my $symb = shift;
1.393     raeburn  2054:     my $filter = shift;
1.366     albertel 2055: 
                   2056:     $self->get_discussion_data();
1.372     raeburn  2057: 
1.366     albertel 2058:     my $ressymb = $self->wrap_symb($symb);
1.372     raeburn  2059:     # keys used to store bulletinboard postings use 'unwrapped' symb. 
1.397   ! albertel 2060:     my $discsymb = &escape($self->unwrap_symb($ressymb));
1.372     raeburn  2061:     my $version = $self->{DISCUSSION_DATA}{'version:'.$discsymb};
1.366     albertel 2062:     if (!$version) { return; }
                   2063: 
                   2064:     my $prevread = $self->{LAST_READ}{$ressymb};
                   2065: 
1.393     raeburn  2066:     my $count = 0;
1.366     albertel 2067:     my $hiddenflag = 0;
                   2068:     my $deletedflag = 0;
1.393     raeburn  2069:     my ($hidden,$deleted,%info);
1.366     albertel 2070: 
                   2071:     for (my $id=$version; $id>0; $id--) {
1.372     raeburn  2072: 	my $vkeys=$self->{DISCUSSION_DATA}{$id.':keys:'.$discsymb};
1.366     albertel 2073: 	my @keys=split(/:/,$vkeys);
                   2074: 	if (grep(/^hidden$/ ,@keys)) {
                   2075: 	    if (!$hiddenflag) {
1.372     raeburn  2076: 		$hidden = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':hidden'};
1.366     albertel 2077: 		$hiddenflag = 1;
                   2078: 	    }
                   2079: 	} elsif (grep(/^deleted$/,@keys)) {
                   2080: 	    if (!$deletedflag) {
1.372     raeburn  2081: 		$deleted = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':deleted'};
1.366     albertel 2082: 		$deletedflag = 1;
                   2083: 	    }
                   2084: 	} else {
1.393     raeburn  2085: 	    if (($hidden !~/\.$id\./) && ($deleted !~/\.$id\./)) {
                   2086:                 if ($filter eq 'unread') {
                   2087: 		    if ($prevread >= $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'}) {
                   2088:                         next;
                   2089:                     }
                   2090:                 }
                   2091: 		$count++;
                   2092: 		$info{$count}{'subject'} =
                   2093: 		    $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':subject'};
                   2094:                 $info{$count}{'id'} = $id;
                   2095:                 $info{$count}{'timestamp'} = $self->{DISCUSSION_DATA}{$id.':'.$discsymb.':timestamp'};
                   2096:             }
1.366     albertel 2097: 	}
                   2098:     }
                   2099:     if (wantarray) {
1.393     raeburn  2100: 	return ($count,%info);
1.366     albertel 2101:     }
1.393     raeburn  2102:     return $count;
1.366     albertel 2103: }
                   2104: 
1.321     raeburn  2105: sub wrap_symb {
1.322     albertel 2106:     my $self = shift;
1.321     raeburn  2107:     my $symb = shift;
1.373     albertel 2108:     if ($symb =~ m-___(adm/[^/]+/[^/]+/)(\d+)(/bulletinboard)$-) {
1.322     albertel 2109:         unless ($symb =~ m|adm/wrapper/adm|) {
                   2110:             $symb = 'bulletin___'.$2.'___adm/wrapper/'.$1.$2.$3;
1.321     raeburn  2111:         }
                   2112:     }
1.322     albertel 2113:     return $symb;
1.321     raeburn  2114: }
                   2115: 
1.372     raeburn  2116: sub unwrap_symb {
                   2117:     my $self = shift;
                   2118:     my $ressymb = shift;
                   2119:     my $discsymb = $ressymb;
1.373     albertel 2120:     if ($ressymb =~ m-^(bulletin___\d+___)adm/wrapper/(adm/[^/]+/[^/]+/\d+/bulletinboard)$-) {
1.372     raeburn  2121:          $discsymb = $1.$2;
                   2122:     }
                   2123:     return $discsymb;
                   2124: }
                   2125: 
1.322     albertel 2126: # Private method: Does the given resource (as a symb string) have
                   2127: # current feedback? Returns the string in the feedback hash, which
                   2128: # will be false if it does not exist.
                   2129: 
1.133     bowersj2 2130: sub getFeedback { 
                   2131:     my $self = shift;
                   2132:     my $symb = shift;
1.395     raeburn  2133:     my $source = shift;
1.133     bowersj2 2134: 
1.221     bowersj2 2135:     $self->generate_email_discuss_status();
                   2136: 
1.133     bowersj2 2137:     if (!defined($self->{FEEDBACK})) { return ""; }
                   2138:     
1.395     raeburn  2139:     my $feedback;
                   2140:     if ($self->{FEEDBACK}->{$symb}) {
                   2141:         $feedback = $self->{FEEDBACK}->{$symb};
                   2142:         if ($self->{FEEDBACK}->{$source}) {
                   2143:             $feedback .= ','.$self->{FEEDBACK}->{$source};
                   2144:         }
                   2145:     } else {
                   2146:         if ($self->{FEEDBACK}->{$source}) {
                   2147:             $feedback = $self->{FEEDBACK}->{$source};
                   2148:         }
                   2149:     }
                   2150:     return $feedback;
1.133     bowersj2 2151: }
                   2152: 
                   2153: # Private method: Get the errors for that resource (by source).
                   2154: sub getErrors { 
                   2155:     my $self = shift;
1.395     raeburn  2156:     my $symb = shift;
1.133     bowersj2 2157:     my $src = shift;
1.221     bowersj2 2158: 
                   2159:     $self->generate_email_discuss_status();
                   2160: 
1.133     bowersj2 2161:     if (!defined($self->{ERROR_MSG})) { return ""; }
1.395     raeburn  2162: 
                   2163:     my $errors;
                   2164:     if ($self->{ERROR_MSG}->{$symb}) {
                   2165:         $errors = $self->{ERROR_MSG}->{$symb};
                   2166:         if ($self->{ERROR_MSG}->{$src}) {
                   2167:             $errors .= ','.$self->{ERROR_MSG}->{$src};
                   2168:         }
                   2169:     } else {
                   2170:         if ($self->{ERROR_MSG}->{$src}) {
                   2171:             $errors = $self->{ERROR_MSG}->{$src};
                   2172:         }
                   2173:     }
                   2174:     return $errors;
1.133     bowersj2 2175: }
                   2176: 
                   2177: =pod
                   2178: 
1.174     albertel 2179: =item * B<getById>(id):
                   2180: 
                   2181: Based on the ID of the resource (1.1, 3.2, etc.), get a resource
                   2182: object for that resource. This method, or other methods that use it
                   2183: (as in the resource object) is the only proper way to obtain a
                   2184: resource object.
1.133     bowersj2 2185: 
1.194     bowersj2 2186: =item * B<getBySymb>(symb):
                   2187: 
                   2188: Based on the symb of the resource, get a resource object for that
                   2189: resource. This is one of the proper ways to get a resource object.
                   2190: 
                   2191: =item * B<getMapByMapPc>(map_pc):
                   2192: 
                   2193: Based on the map_pc of the resource, get a resource object for
                   2194: the given map. This is one of the proper ways to get a resource object.
                   2195: 
1.133     bowersj2 2196: =cut
                   2197: 
                   2198: # The strategy here is to cache the resource objects, and only construct them
                   2199: # as we use them. The real point is to prevent reading any more from the tied
                   2200: # hash then we have to, which should hopefully alleviate speed problems.
                   2201: 
                   2202: sub getById {
                   2203:     my $self = shift;
                   2204:     my $id = shift;
                   2205: 
                   2206:     if (defined ($self->{RESOURCE_CACHE}->{$id}))
                   2207:     {
                   2208:         return $self->{RESOURCE_CACHE}->{$id};
                   2209:     }
                   2210: 
                   2211:     # resource handles inserting itself into cache.
                   2212:     # Not clear why the quotes are necessary, but as of this
                   2213:     # writing it doesn't work without them.
                   2214:     return "Apache::lonnavmaps::resource"->new($self, $id);
1.132     bowersj2 2215: }
1.133     bowersj2 2216: 
1.172     bowersj2 2217: sub getBySymb {
                   2218:     my $self = shift;
                   2219:     my $symb = shift;
1.277     matthew  2220: 
1.228     albertel 2221:     my ($mapUrl, $id, $filename) = &Apache::lonnet::decode_symb($symb);
1.172     bowersj2 2222:     my $map = $self->getResourceByUrl($mapUrl);
1.277     matthew  2223:     my $returnvalue = undef;
                   2224:     if (ref($map)) {
                   2225:         $returnvalue = $self->getById($map->map_pc() .'.'.$id);
                   2226:     }
                   2227:     return $returnvalue;
1.194     bowersj2 2228: }
                   2229: 
                   2230: sub getByMapPc {
                   2231:     my $self = shift;
                   2232:     my $map_pc = shift;
                   2233:     my $map_id = $self->{NAV_HASH}->{'map_id_' . $map_pc};
                   2234:     $map_id = $self->{NAV_HASH}->{'ids_' . $map_id};
                   2235:     return $self->getById($map_id);
1.172     bowersj2 2236: }
                   2237: 
1.133     bowersj2 2238: =pod
                   2239: 
1.174     albertel 2240: =item * B<firstResource>():
                   2241: 
                   2242: Returns a resource object reference corresponding to the first
                   2243: resource in the navmap.
1.133     bowersj2 2244: 
                   2245: =cut
                   2246: 
                   2247: sub firstResource {
                   2248:     my $self = shift;
                   2249:     my $firstResource = $self->navhash('map_start_' .
1.320     albertel 2250:                      &Apache::lonnet::clutter($env{'request.course.uri'}));
1.133     bowersj2 2251:     return $self->getById($firstResource);
1.132     bowersj2 2252: }
1.133     bowersj2 2253: 
                   2254: =pod
                   2255: 
1.174     albertel 2256: =item * B<finishResource>():
                   2257: 
                   2258: Returns a resource object reference corresponding to the last resource
                   2259: in the navmap.
1.133     bowersj2 2260: 
                   2261: =cut
                   2262: 
                   2263: sub finishResource {
                   2264:     my $self = shift;
                   2265:     my $firstResource = $self->navhash('map_finish_' .
1.320     albertel 2266:                      &Apache::lonnet::clutter($env{'request.course.uri'}));
1.133     bowersj2 2267:     return $self->getById($firstResource);
1.132     bowersj2 2268: }
1.133     bowersj2 2269: 
                   2270: # Parmval reads the parm hash and cascades the lookups. parmval_real does
                   2271: # the actual lookup; parmval caches the results.
                   2272: sub parmval {
                   2273:     my $self = shift;
                   2274:     my ($what,$symb)=@_;
                   2275:     my $hashkey = $what."|||".$symb;
                   2276: 
                   2277:     if (defined($self->{PARM_CACHE}->{$hashkey})) {
                   2278:         return $self->{PARM_CACHE}->{$hashkey};
                   2279:     }
                   2280: 
                   2281:     my $result = $self->parmval_real($what, $symb);
                   2282:     $self->{PARM_CACHE}->{$hashkey} = $result;
                   2283:     return $result;
1.132     bowersj2 2284: }
                   2285: 
1.133     bowersj2 2286: sub parmval_real {
                   2287:     my $self = shift;
1.219     albertel 2288:     my ($what,$symb,$recurse) = @_;
1.133     bowersj2 2289: 
1.221     bowersj2 2290:     # Make sure the {USER_OPT} and {COURSE_OPT} hashes are populated
                   2291:     $self->generate_course_user_opt();
                   2292: 
1.320     albertel 2293:     my $cid=$env{'request.course.id'};
                   2294:     my $csec=$env{'request.course.sec'};
1.350     raeburn  2295:     my $cgroup='';
                   2296:     my @cgrps=split(/:/,$env{'request.course.groups'});
                   2297:     if (@cgrps > 0) {
                   2298:         @cgrps = sort(@cgrps);
                   2299:         $cgroup = $cgrps[0];
                   2300:     } 
1.320     albertel 2301:     my $uname=$env{'user.name'};
                   2302:     my $udom=$env{'user.domain'};
1.133     bowersj2 2303: 
                   2304:     unless ($symb) { return ''; }
                   2305:     my $result='';
                   2306: 
1.226     www      2307:     my ($mapname,$id,$fn)=&Apache::lonnet::decode_symb($symb);
1.383     albertel 2308:     $mapname = &Apache::lonnet::deversion($mapname);
1.133     bowersj2 2309: # ----------------------------------------------------- Cascading lookup scheme
                   2310:     my $rwhat=$what;
                   2311:     $what=~s/^parameter\_//;
                   2312:     $what=~s/\_/\./;
                   2313: 
                   2314:     my $symbparm=$symb.'.'.$what;
                   2315:     my $mapparm=$mapname.'___(all).'.$what;
1.325     albertel 2316:     my $usercourseprefix=$cid;
1.133     bowersj2 2317: 
1.350     raeburn  2318:     my $grplevel=$usercourseprefix.'.['.$cgroup.'].'.$what;
                   2319:     my $grplevelr=$usercourseprefix.'.['.$cgroup.'].'.$symbparm;
                   2320:     my $grplevelm=$usercourseprefix.'.['.$cgroup.'].'.$mapparm;
                   2321: 
1.133     bowersj2 2322:     my $seclevel= $usercourseprefix.'.['.$csec.'].'.$what;
                   2323:     my $seclevelr=$usercourseprefix.'.['.$csec.'].'.$symbparm;
                   2324:     my $seclevelm=$usercourseprefix.'.['.$csec.'].'.$mapparm;
                   2325: 
                   2326:     my $courselevel= $usercourseprefix.'.'.$what;
                   2327:     my $courselevelr=$usercourseprefix.'.'.$symbparm;
                   2328:     my $courselevelm=$usercourseprefix.'.'.$mapparm;
                   2329: 
                   2330:     my $useropt = $self->{USER_OPT};
                   2331:     my $courseopt = $self->{COURSE_OPT};
                   2332:     my $parmhash = $self->{PARM_HASH};
                   2333: 
                   2334: # ---------------------------------------------------------- first, check user
                   2335:     if ($uname and defined($useropt)) {
                   2336:         if (defined($$useropt{$courselevelr})) { return $$useropt{$courselevelr}; }
                   2337:         if (defined($$useropt{$courselevelm})) { return $$useropt{$courselevelm}; }
                   2338:         if (defined($$useropt{$courselevel})) { return $$useropt{$courselevel}; }
                   2339:     }
                   2340: 
                   2341: # ------------------------------------------------------- second, check course
1.350     raeburn  2342:     if ($cgroup ne '' and defined($courseopt)) {
                   2343:         if (defined($$courseopt{$grplevelr})) { return $$courseopt{$grplevelr}; }
                   2344:         if (defined($$courseopt{$grplevelm})) { return $$courseopt{$grplevelm}; }
                   2345:         if (defined($$courseopt{$grplevel})) { return $$courseopt{$grplevel}; }
                   2346:     }
                   2347: 
1.133     bowersj2 2348:     if ($csec and defined($courseopt)) {
                   2349:         if (defined($$courseopt{$seclevelr})) { return $$courseopt{$seclevelr}; }
                   2350:         if (defined($$courseopt{$seclevelm})) { return $$courseopt{$seclevelm}; }
                   2351:         if (defined($$courseopt{$seclevel})) { return $$courseopt{$seclevel}; }
                   2352:     }
                   2353: 
                   2354:     if (defined($courseopt)) {
                   2355:         if (defined($$courseopt{$courselevelr})) { return $$courseopt{$courselevelr}; }
                   2356:     }
                   2357: 
                   2358: # ----------------------------------------------------- third, check map parms
                   2359: 
                   2360:     my $thisparm=$$parmhash{$symbparm};
                   2361:     if (defined($thisparm)) { return $thisparm; }
                   2362: 
                   2363: # ----------------------------------------------------- fourth , check default
                   2364: 
1.246     albertel 2365:     my $meta_rwhat=$rwhat;
                   2366:     $meta_rwhat=~s/\./_/g;
                   2367:     my $default=&Apache::lonnet::metadata($fn,$meta_rwhat);
                   2368:     if (defined($default)) { return $default}
                   2369:     $default=&Apache::lonnet::metadata($fn,'parameter_'.$meta_rwhat);
1.133     bowersj2 2370:     if (defined($default)) { return $default}
                   2371: 
1.315     albertel 2372: # --------------------------------------------------- fifth, check more course
                   2373:     if (defined($courseopt)) {
                   2374:         if (defined($$courseopt{$courselevelm})) { return $$courseopt{$courselevelm}; }
                   2375:         if (defined($$courseopt{$courselevel})) { return $$courseopt{$courselevel}; }
                   2376:     }
                   2377: 
                   2378: # --------------------------------------------------- sixth , cascade up parts
1.133     bowersj2 2379: 
                   2380:     my ($space,@qualifier)=split(/\./,$rwhat);
                   2381:     my $qualifier=join('.',@qualifier);
                   2382:     unless ($space eq '0') {
1.160     albertel 2383: 	my @parts=split(/_/,$space);
                   2384: 	my $id=pop(@parts);
                   2385: 	my $part=join('_',@parts);
                   2386: 	if ($part eq '') { $part='0'; }
1.219     albertel 2387: 	my $partgeneral=$self->parmval($part.".$qualifier",$symb,1);
1.160     albertel 2388: 	if (defined($partgeneral)) { return $partgeneral; }
1.133     bowersj2 2389:     }
1.219     albertel 2390:     if ($recurse) { return undef; }
                   2391:     my $pack_def=&Apache::lonnet::packages_tab_default($fn,'resource.'.$what);
                   2392:     if (defined($pack_def)) { return $pack_def; }
1.133     bowersj2 2393:     return '';
1.145     bowersj2 2394: }
                   2395: 
1.174     albertel 2396: =pod
1.145     bowersj2 2397: 
1.352     raeburn  2398: =item * B<getResourceByUrl>(url,multiple):
1.145     bowersj2 2399: 
1.352     raeburn  2400: Retrieves a resource object by URL of the resource, unless the optional
                   2401: multiple parameter is included in wahich caes an array of resource 
                   2402: objects is returned. If passed a resource object, it will simply return  
                   2403: it, so it is safe to use this method in code like
                   2404: "$res = $navmap->getResourceByUrl($res)"
                   2405: if you're not sure if $res is already an object, or just a URL. If the
                   2406: resource appears multiple times in the course, only the first instance 
                   2407: will be returned (useful for maps), unless the multiple parameter has
                   2408: been included, in which case all instances are returned in an array.
1.174     albertel 2409: 
1.314     albertel 2410: =item * B<retrieveResources>(map, filterFunc, recursive, bailout, showall):
1.174     albertel 2411: 
                   2412: The map is a specification of a map to retreive the resources from,
                   2413: either as a url or as an object. The filterFunc is a reference to a
                   2414: function that takes a resource object as its one argument and returns
                   2415: true if the resource should be included, or false if it should not
                   2416: be. If recursive is true, the map will be recursively examined,
                   2417: otherwise it will not be. If bailout is true, the function will return
1.314     albertel 2418: as soon as it finds a resource, if false it will finish. If showall is
                   2419: true it will not hide maps that contain nothing but one other map. By
                   2420: default, the map is the top-level map of the course, filterFunc is a
                   2421: function that always returns 1, recursive is true, bailout is false,
                   2422: showall is false. The resources will be returned in a list containing
                   2423: the resource objects for the corresponding resources, with B<no
                   2424: structure information> in the list; regardless of branching,
                   2425: recursion, etc., it will be a flat list.
1.174     albertel 2426: 
                   2427: Thus, this is suitable for cases where you don't want the structure,
                   2428: just a list of all resources. It is also suitable for finding out how
                   2429: many resources match a given description; for this use, if all you
                   2430: want to know is if I<any> resources match the description, the bailout
                   2431: parameter will allow you to avoid potentially expensive enumeration of
                   2432: all matching resources.
1.145     bowersj2 2433: 
1.318     albertel 2434: =item * B<hasResource>(map, filterFunc, recursive, showall):
1.145     bowersj2 2435: 
1.174     albertel 2436: Convience method for
1.146     bowersj2 2437: 
1.318     albertel 2438:  scalar(retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0
1.146     bowersj2 2439: 
1.174     albertel 2440: which will tell whether the map has resources matching the description
                   2441: in the filter function.
1.146     bowersj2 2442: 
1.352     raeburn  2443: =item * B<usedVersion>(url):
                   2444: 
                   2445: Retrieves version infomation for a url. Returns the version (a number, or 
                   2446: the string "mostrecent") for resources which have version information in  
                   2447: the big hash.
                   2448:     
1.145     bowersj2 2449: =cut
                   2450: 
1.277     matthew  2451: 
1.145     bowersj2 2452: sub getResourceByUrl {
                   2453:     my $self = shift;
                   2454:     my $resUrl = shift;
1.352     raeburn  2455:     my $multiple = shift;
1.145     bowersj2 2456: 
                   2457:     if (ref($resUrl)) { return $resUrl; }
                   2458: 
                   2459:     $resUrl = &Apache::lonnet::clutter($resUrl);
                   2460:     my $resId = $self->{NAV_HASH}->{'ids_' . $resUrl};
1.352     raeburn  2461:     if (!$resId) { return ''; }
                   2462:     if ($multiple) {
                   2463:         my @resources = ();
                   2464:         my @resIds = split (/,/, $resId);
                   2465:         foreach my $id (@resIds) {
1.353     raeburn  2466:             my $resourceId = $self->getById($id);
                   2467:             if ($resourceId) { 
                   2468:                 push(@resources,$resourceId);
1.352     raeburn  2469:             }
                   2470:         }
                   2471:         return @resources;
                   2472:     } else {
                   2473:         if ($resId =~ /,/) {
                   2474:             $resId = (split (/,/, $resId))[0];
                   2475:         }
                   2476:         return $self->getById($resId);
1.145     bowersj2 2477:     }
                   2478: }
                   2479: 
                   2480: sub retrieveResources {
                   2481:     my $self = shift;
                   2482:     my $map = shift;
                   2483:     my $filterFunc = shift;
                   2484:     if (!defined ($filterFunc)) {
                   2485:         $filterFunc = sub {return 1;};
                   2486:     }
                   2487:     my $recursive = shift;
                   2488:     if (!defined($recursive)) { $recursive = 1; }
                   2489:     my $bailout = shift;
                   2490:     if (!defined($bailout)) { $bailout = 0; }
1.314     albertel 2491:     my $showall = shift;
1.145     bowersj2 2492:     # Create the necessary iterator.
                   2493:     if (!ref($map)) { # assume it's a url of a map.
1.172     bowersj2 2494:         $map = $self->getResourceByUrl($map);
1.145     bowersj2 2495:     }
                   2496: 
1.213     bowersj2 2497:     # If nothing was passed, assume top-level map
                   2498:     if (!$map) {
                   2499: 	$map = $self->getById('0.0');
                   2500:     }
                   2501: 
1.145     bowersj2 2502:     # Check the map's validity.
1.213     bowersj2 2503:     if (!$map->is_map()) {
1.145     bowersj2 2504:         # Oh, to throw an exception.... how I'd love that!
                   2505:         return ();
                   2506:     }
                   2507: 
1.146     bowersj2 2508:     # Get an iterator.
                   2509:     my $it = $self->getIterator($map->map_start(), $map->map_finish(),
1.314     albertel 2510:                                 undef, $recursive, $showall);
1.146     bowersj2 2511: 
                   2512:     my @resources = ();
                   2513: 
                   2514:     # Run down the iterator and collect the resources.
1.222     bowersj2 2515:     my $curRes;
                   2516: 
                   2517:     while ($curRes = $it->next()) {
1.146     bowersj2 2518:         if (ref($curRes)) {
                   2519:             if (!&$filterFunc($curRes)) {
                   2520:                 next;
                   2521:             }
                   2522: 
                   2523:             push @resources, $curRes;
                   2524: 
                   2525:             if ($bailout) {
                   2526:                 return @resources;
                   2527:             }
                   2528:         }
                   2529: 
                   2530:     }
                   2531: 
                   2532:     return @resources;
                   2533: }
                   2534: 
                   2535: sub hasResource {
                   2536:     my $self = shift;
                   2537:     my $map = shift;
                   2538:     my $filterFunc = shift;
                   2539:     my $recursive = shift;
1.318     albertel 2540:     my $showall = shift;
1.146     bowersj2 2541:     
1.318     albertel 2542:     return scalar($self->retrieveResources($map, $filterFunc, $recursive, 1, $showall)) > 0;
1.133     bowersj2 2543: }
1.51      bowersj2 2544: 
1.352     raeburn  2545: sub usedVersion {
                   2546:     my $self = shift;
                   2547:     my $linkurl = shift;
                   2548:     return $self->navhash("version_$linkurl");
                   2549: }
                   2550: 
1.51      bowersj2 2551: 1;
                   2552: 
                   2553: package Apache::lonnavmaps::iterator;
1.365     albertel 2554: use Scalar::Util qw(weaken);
1.320     albertel 2555: use Apache::lonnet;
                   2556: 
1.51      bowersj2 2557: =pod
                   2558: 
                   2559: =back
                   2560: 
1.174     albertel 2561: =head1 Object: navmap Iterator
1.51      bowersj2 2562: 
1.174     albertel 2563: An I<iterator> encapsulates the logic required to traverse a data
                   2564: structure. navmap uses an iterator to traverse the course map
                   2565: according to the criteria you wish to use.
                   2566: 
                   2567: To obtain an iterator, call the B<getIterator>() function of a
                   2568: B<navmap> object. (Do not instantiate Apache::lonnavmaps::iterator
                   2569: directly.) This will return a reference to the iterator:
1.51      bowersj2 2570: 
                   2571: C<my $resourceIterator = $navmap-E<gt>getIterator();>
                   2572: 
                   2573: To get the next thing from the iterator, call B<next>:
                   2574: 
                   2575: C<my $nextThing = $resourceIterator-E<gt>next()>
                   2576: 
                   2577: getIterator behaves as follows:
                   2578: 
                   2579: =over 4
                   2580: 
1.174     albertel 2581: =item * B<getIterator>(firstResource, finishResource, filterHash, condition, forceTop, returnTopMap):
                   2582: 
                   2583: All parameters are optional. firstResource is a resource reference
                   2584: corresponding to where the iterator should start. It defaults to
                   2585: navmap->firstResource() for the corresponding nav map. finishResource
                   2586: corresponds to where you want the iterator to end, defaulting to
                   2587: navmap->finishResource(). filterHash is a hash used as a set
                   2588: containing strings representing the resource IDs, defaulting to
                   2589: empty. Condition is a 1 or 0 that sets what to do with the filter
1.205     bowersj2 2590: hash: If a 0, then only resources that exist IN the filterHash will be
1.174     albertel 2591: recursed on. If it is a 1, only resources NOT in the filterHash will
                   2592: be recursed on. Defaults to 0. forceTop is a boolean value. If it is
                   2593: false (default), the iterator will only return the first level of map
                   2594: that is not just a single, 'redirecting' map. If true, the iterator
                   2595: will return all information, starting with the top-level map,
                   2596: regardless of content. returnTopMap, if true (default false), will
                   2597: cause the iterator to return the top-level map object (resource 0.0)
                   2598: before anything else.
                   2599: 
                   2600: Thus, by default, only top-level resources will be shown. Change the
                   2601: condition to a 1 without changing the hash, and all resources will be
                   2602: shown. Changing the condition to 1 and including some values in the
                   2603: hash will allow you to selectively suppress parts of the navmap, while
                   2604: leaving it on 0 and adding things to the hash will allow you to
                   2605: selectively add parts of the nav map. See the handler code for
                   2606: examples.
                   2607: 
                   2608: The iterator will return either a reference to a resource object, or a
                   2609: token representing something in the map, such as the beginning of a
                   2610: new branch. The possible tokens are:
                   2611: 
                   2612: =over 4
1.51      bowersj2 2613: 
1.222     bowersj2 2614: =item * B<END_ITERATOR>:
                   2615: 
                   2616: The iterator has returned all that it's going to. Further calls to the
                   2617: iterator will just produce more of these. This is a "false" value, and
                   2618: is the only false value the iterator which will be returned, so it can
                   2619: be used as a loop sentinel.
                   2620: 
1.217     bowersj2 2621: =item * B<BEGIN_MAP>:
1.51      bowersj2 2622: 
1.174     albertel 2623: A new map is being recursed into. This is returned I<after> the map
                   2624: resource itself is returned.
1.51      bowersj2 2625: 
1.217     bowersj2 2626: =item * B<END_MAP>:
1.174     albertel 2627: 
                   2628: The map is now done.
1.51      bowersj2 2629: 
1.217     bowersj2 2630: =item * B<BEGIN_BRANCH>:
1.70      bowersj2 2631: 
1.174     albertel 2632: A branch is now starting. The next resource returned will be the first
                   2633: in that branch.
1.70      bowersj2 2634: 
1.217     bowersj2 2635: =item * B<END_BRANCH>:
1.70      bowersj2 2636: 
1.174     albertel 2637: The branch is now done.
1.51      bowersj2 2638: 
                   2639: =back
                   2640: 
1.174     albertel 2641: The tokens are retreivable via methods on the iterator object, i.e.,
                   2642: $iterator->END_MAP.
1.70      bowersj2 2643: 
1.174     albertel 2644: Maps can contain empty resources. The iterator will automatically skip
                   2645: over such resources, but will still treat the structure
                   2646: correctly. Thus, a complicated map with several branches, but
                   2647: consisting entirely of empty resources except for one beginning or
                   2648: ending resource, will cause a lot of BRANCH_STARTs and BRANCH_ENDs,
                   2649: but only one resource will be returned.
1.116     bowersj2 2650: 
1.242     matthew  2651: =back
                   2652: 
1.222     bowersj2 2653: =head2 Normal Usage
                   2654: 
                   2655: Normal usage of the iterator object is to do the following:
                   2656: 
                   2657:  my $it = $navmap->getIterator([your params here]);
                   2658:  my $curRes;
                   2659:  while ($curRes = $it->next()) {
                   2660:    [your logic here]
                   2661:  }
                   2662: 
                   2663: Note that inside of the loop, it's frequently useful to check if
                   2664: "$curRes" is a reference or not with the reference function; only
                   2665: resource objects will be references, and any non-references will 
                   2666: be the tokens described above.
                   2667: 
                   2668: Also note there is some old code floating around that trys to track
                   2669: the depth of the iterator to see when it's done; do not copy that 
                   2670: code. It is difficult to get right and harder to understand then
                   2671: this. They should be migrated to this new style.
1.51      bowersj2 2672: 
                   2673: =cut
                   2674: 
                   2675: # Here are the tokens for the iterator:
                   2676: 
1.222     bowersj2 2677: sub END_ITERATOR { return 0; }
1.51      bowersj2 2678: sub BEGIN_MAP { return 1; }    # begining of a new map
                   2679: sub END_MAP { return 2; }      # end of the map
                   2680: sub BEGIN_BRANCH { return 3; } # beginning of a branch
                   2681: sub END_BRANCH { return 4; }   # end of a branch
1.89      bowersj2 2682: sub FORWARD { return 1; }      # go forward
                   2683: sub BACKWARD { return 2; }
1.51      bowersj2 2684: 
1.96      bowersj2 2685: sub min {
                   2686:     (my $a, my $b) = @_;
                   2687:     if ($a < $b) { return $a; } else { return $b; }
                   2688: }
                   2689: 
1.94      bowersj2 2690: sub new {
                   2691:     # magic invocation to create a class instance
                   2692:     my $proto = shift;
                   2693:     my $class = ref($proto) || $proto;
                   2694:     my $self = {};
                   2695: 
1.300     albertel 2696:     weaken($self->{NAV_MAP} = shift);
1.94      bowersj2 2697:     return undef unless ($self->{NAV_MAP});
                   2698: 
                   2699:     # Handle the parameters
                   2700:     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
                   2701:     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
                   2702: 
                   2703:     # If the given resources are just the ID of the resource, get the
                   2704:     # objects
                   2705:     if (!ref($self->{FIRST_RESOURCE})) { $self->{FIRST_RESOURCE} = 
                   2706:              $self->{NAV_MAP}->getById($self->{FIRST_RESOURCE}); }
                   2707:     if (!ref($self->{FINISH_RESOURCE})) { $self->{FINISH_RESOURCE} = 
                   2708:              $self->{NAV_MAP}->getById($self->{FINISH_RESOURCE}); }
                   2709: 
                   2710:     $self->{FILTER} = shift;
                   2711: 
                   2712:     # A hash, used as a set, of resource already seen
                   2713:     $self->{ALREADY_SEEN} = shift;
                   2714:     if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };
                   2715:     $self->{CONDITION} = shift;
                   2716: 
1.116     bowersj2 2717:     # Do we want to automatically follow "redirection" maps?
                   2718:     $self->{FORCE_TOP} = shift;
                   2719: 
1.162     bowersj2 2720:     # Do we want to return the top-level map object (resource 0.0)?
                   2721:     $self->{RETURN_0} = shift;
                   2722:     # have we done that yet?
                   2723:     $self->{HAVE_RETURNED_0} = 0;
                   2724: 
1.94      bowersj2 2725:     # Now, we need to pre-process the map, by walking forward and backward
                   2726:     # over the parts of the map we're going to look at.
1.96      bowersj2 2727: 
1.97      bowersj2 2728:     # The processing steps are exactly the same, except for a few small 
                   2729:     # changes, so I bundle those up in the following list of two elements:
                   2730:     # (direction_to_iterate, VAL_name, next_resource_method_to_call,
                   2731:     # first_resource).
                   2732:     # This prevents writing nearly-identical code twice.
                   2733:     my @iterations = ( [FORWARD(), 'TOP_DOWN_VAL', 'getNext', 
                   2734:                         'FIRST_RESOURCE'],
                   2735:                        [BACKWARD(), 'BOT_UP_VAL', 'getPrevious', 
                   2736:                         'FINISH_RESOURCE'] );
                   2737: 
1.98      bowersj2 2738:     my $maxDepth = 0; # tracks max depth
                   2739: 
1.116     bowersj2 2740:     # If there is only one resource in this map, and it's a map, we
                   2741:     # want to remember that, so the user can ask for the first map
                   2742:     # that isn't just a redirector.
                   2743:     my $resource; my $resourceCount = 0;
                   2744: 
1.209     bowersj2 2745:     # Documentation on this algorithm can be found in the CVS repository at 
                   2746:     # /docs/lonnavdocs; these "**#**" markers correspond to documentation
                   2747:     # in that file.
1.107     bowersj2 2748:     # **1**
                   2749: 
1.97      bowersj2 2750:     foreach my $pass (@iterations) {
                   2751:         my $direction = $pass->[0];
                   2752:         my $valName = $pass->[1];
                   2753:         my $nextResourceMethod = $pass->[2];
                   2754:         my $firstResourceName = $pass->[3];
                   2755: 
                   2756:         my $iterator = Apache::lonnavmaps::DFSiterator->new($self->{NAV_MAP}, 
                   2757:                                                             $self->{FIRST_RESOURCE},
                   2758:                                                             $self->{FINISH_RESOURCE},
                   2759:                                                             {}, undef, 0, $direction);
1.96      bowersj2 2760:     
1.97      bowersj2 2761:         # prime the recursion
                   2762:         $self->{$firstResourceName}->{DATA}->{$valName} = 0;
1.222     bowersj2 2763: 	$iterator->next();
1.97      bowersj2 2764:         my $curRes = $iterator->next();
1.222     bowersj2 2765: 	my $depth = 1;
                   2766:         while ($depth > 0) {
                   2767: 	    if ($curRes == $iterator->BEGIN_MAP()) { $depth++; }
                   2768: 	    if ($curRes == $iterator->END_MAP()) { $depth--; }
                   2769: 
1.97      bowersj2 2770:             if (ref($curRes)) {
1.116     bowersj2 2771:                 # If there's only one resource, this will save it
1.117     bowersj2 2772:                 # we have to filter empty resources from consideration here,
                   2773:                 # or even "empty", redirecting maps have two (start & finish)
                   2774:                 # or three (start, finish, plus redirector)
                   2775:                 if($direction == FORWARD && $curRes->src()) { 
                   2776:                     $resource = $curRes; $resourceCount++; 
                   2777:                 }
1.97      bowersj2 2778:                 my $resultingVal = $curRes->{DATA}->{$valName};
                   2779:                 my $nextResources = $curRes->$nextResourceMethod();
1.116     bowersj2 2780:                 my $nextCount = scalar(@{$nextResources});
1.104     bowersj2 2781: 
1.116     bowersj2 2782:                 if ($nextCount == 1) { # **3**
1.97      bowersj2 2783:                     my $current = $nextResources->[0]->{DATA}->{$valName} || 999999999;
                   2784:                     $nextResources->[0]->{DATA}->{$valName} = min($resultingVal, $current);
                   2785:                 }
                   2786:                 
1.116     bowersj2 2787:                 if ($nextCount > 1) { # **4**
1.97      bowersj2 2788:                     foreach my $res (@{$nextResources}) {
                   2789:                         my $current = $res->{DATA}->{$valName} || 999999999;
                   2790:                         $res->{DATA}->{$valName} = min($current, $resultingVal + 1);
                   2791:                     }
                   2792:                 }
                   2793:             }
1.96      bowersj2 2794:             
1.107     bowersj2 2795:             # Assign the final val (**2**)
1.97      bowersj2 2796:             if (ref($curRes) && $direction == BACKWARD()) {
1.98      bowersj2 2797:                 my $finalDepth = min($curRes->{DATA}->{TOP_DOWN_VAL},
                   2798:                                      $curRes->{DATA}->{BOT_UP_VAL});
                   2799:                 
                   2800:                 $curRes->{DATA}->{DISPLAY_DEPTH} = $finalDepth;
                   2801:                 if ($finalDepth > $maxDepth) {$maxDepth = $finalDepth;}
1.190     bowersj2 2802:             }
1.222     bowersj2 2803: 
                   2804: 	    $curRes = $iterator->next();
1.96      bowersj2 2805:         }
                   2806:     }
1.94      bowersj2 2807: 
1.116     bowersj2 2808:     # Check: Was this only one resource, a map?
1.255     albertel 2809:     if ($resourceCount == 1 && $resource->is_sequence() && !$self->{FORCE_TOP}) { 
1.116     bowersj2 2810:         my $firstResource = $resource->map_start();
                   2811:         my $finishResource = $resource->map_finish();
                   2812:         return 
                   2813:             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                   2814:                                               $finishResource, $self->{FILTER},
                   2815:                                               $self->{ALREADY_SEEN}, 
1.314     albertel 2816:                                               $self->{CONDITION},
                   2817: 					      $self->{FORCE_TOP});
1.116     bowersj2 2818:         
                   2819:     }
                   2820: 
1.98      bowersj2 2821:     # Set up some bookkeeping information.
                   2822:     $self->{CURRENT_DEPTH} = 0;
                   2823:     $self->{MAX_DEPTH} = $maxDepth;
                   2824:     $self->{STACK} = [];
                   2825:     $self->{RECURSIVE_ITERATOR_FLAG} = 0;
1.222     bowersj2 2826:     $self->{FINISHED} = 0; # When true, the iterator has finished
1.98      bowersj2 2827: 
                   2828:     for (my $i = 0; $i <= $self->{MAX_DEPTH}; $i++) {
                   2829:         push @{$self->{STACK}}, [];
                   2830:     }
                   2831: 
1.107     bowersj2 2832:     # Prime the recursion w/ the first resource **5**
1.98      bowersj2 2833:     push @{$self->{STACK}->[0]}, $self->{FIRST_RESOURCE};
                   2834:     $self->{ALREADY_SEEN}->{$self->{FIRST_RESOURCE}->{ID}} = 1;
                   2835: 
                   2836:     bless ($self);
                   2837: 
                   2838:     return $self;
                   2839: }
                   2840: 
                   2841: sub next {
                   2842:     my $self = shift;
1.256     albertel 2843:     my $closeAllPages=shift;
1.222     bowersj2 2844:     if ($self->{FINISHED}) {
                   2845: 	return END_ITERATOR();
                   2846:     }
                   2847: 
1.162     bowersj2 2848:     # If we want to return the top-level map object, and haven't yet,
                   2849:     # do so.
                   2850:     if ($self->{RETURN_0} && !$self->{HAVE_RETURNED_0}) {
                   2851:         $self->{HAVE_RETURNED_0} = 1;
                   2852:         return $self->{NAV_MAP}->getById('0.0');
                   2853:     }
1.98      bowersj2 2854: 
                   2855:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   2856:         # grab the next from the recursive iterator 
1.256     albertel 2857:         my $next = $self->{RECURSIVE_ITERATOR}->next($closeAllPages);
1.98      bowersj2 2858: 
                   2859:         # is it a begin or end map? If so, update the depth
                   2860:         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
                   2861:         if ($next == END_MAP() ) { $self->{RECURSIVE_DEPTH}--; }
                   2862: 
                   2863:         # Are we back at depth 0? If so, stop recursing
                   2864:         if ($self->{RECURSIVE_DEPTH} == 0) {
                   2865:             $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   2866:         }
                   2867: 
                   2868:         return $next;
                   2869:     }
                   2870: 
                   2871:     if (defined($self->{FORCE_NEXT})) {
                   2872:         my $tmp = $self->{FORCE_NEXT};
                   2873:         $self->{FORCE_NEXT} = undef;
                   2874:         return $tmp;
                   2875:     }
                   2876: 
                   2877:     # Have we not yet begun? If not, return BEGIN_MAP and
                   2878:     # remember we've started.
                   2879:     if ( !$self->{STARTED} ) { 
                   2880:         $self->{STARTED} = 1;
                   2881:         return $self->BEGIN_MAP();
                   2882:     }
                   2883: 
                   2884:     # Here's the guts of the iterator.
                   2885:     
                   2886:     # Find the next resource, if any.
                   2887:     my $found = 0;
                   2888:     my $i = $self->{MAX_DEPTH};
                   2889:     my $newDepth;
                   2890:     my $here;
                   2891:     while ( $i >= 0 && !$found ) {
1.107     bowersj2 2892:         if ( scalar(@{$self->{STACK}->[$i]}) > 0 ) { # **6**
                   2893:             $here = pop @{$self->{STACK}->[$i]}; # **7**
1.98      bowersj2 2894:             $found = 1;
                   2895:             $newDepth = $i;
                   2896:         }
                   2897:         $i--;
                   2898:     }
                   2899: 
                   2900:     # If we still didn't find anything, we're done.
                   2901:     if ( !$found ) {
                   2902:         # We need to get back down to the correct branch depth
                   2903:         if ( $self->{CURRENT_DEPTH} > 0 ) {
                   2904:             $self->{CURRENT_DEPTH}--;
                   2905:             return END_BRANCH();
                   2906:         } else {
1.222     bowersj2 2907: 	    $self->{FINISHED} = 1;
1.98      bowersj2 2908:             return END_MAP();
                   2909:         }
                   2910:     }
                   2911: 
1.104     bowersj2 2912:     # If this is not a resource, it must be an END_BRANCH marker we want
                   2913:     # to return directly.
1.107     bowersj2 2914:     if (!ref($here)) { # **8**
1.104     bowersj2 2915:         if ($here == END_BRANCH()) { # paranoia, in case of later extension
                   2916:             $self->{CURRENT_DEPTH}--;
                   2917:             return $here;
                   2918:         }
                   2919:     }
                   2920: 
                   2921:     # Otherwise, it is a resource and it's safe to store in $self->{HERE}
                   2922:     $self->{HERE} = $here;
                   2923: 
1.98      bowersj2 2924:     # Get to the right level
                   2925:     if ( $self->{CURRENT_DEPTH} > $newDepth ) {
                   2926:         push @{$self->{STACK}->[$newDepth]}, $here;
                   2927:         $self->{CURRENT_DEPTH}--;
                   2928:         return END_BRANCH();
                   2929:     }
                   2930:     if ( $self->{CURRENT_DEPTH} < $newDepth) {
                   2931:         push @{$self->{STACK}->[$newDepth]}, $here;
                   2932:         $self->{CURRENT_DEPTH}++;
                   2933:         return BEGIN_BRANCH();
                   2934:     }
                   2935: 
                   2936:     # If we made it here, we have the next resource, and we're at the
                   2937:     # right branch level. So let's examine the resource for where
                   2938:     # we can get to from here.
                   2939: 
                   2940:     # So we need to look at all the resources we can get to from here,
                   2941:     # categorize them if we haven't seen them, remember if we have a new
                   2942:     my $nextUnfiltered = $here->getNext();
1.104     bowersj2 2943:     my $maxDepthAdded = -1;
                   2944:     
1.98      bowersj2 2945:     for (@$nextUnfiltered) {
                   2946:         if (!defined($self->{ALREADY_SEEN}->{$_->{ID}})) {
1.104     bowersj2 2947:             my $depth = $_->{DATA}->{DISPLAY_DEPTH};
                   2948:             push @{$self->{STACK}->[$depth]}, $_;
1.98      bowersj2 2949:             $self->{ALREADY_SEEN}->{$_->{ID}} = 1;
1.104     bowersj2 2950:             if ($maxDepthAdded < $depth) { $maxDepthAdded = $depth; }
1.98      bowersj2 2951:         }
                   2952:     }
1.104     bowersj2 2953: 
                   2954:     # Is this the end of a branch? If so, all of the resources examined above
                   2955:     # led to lower levels then the one we are currently at, so we push a END_BRANCH
                   2956:     # marker onto the stack so we don't forget.
                   2957:     # Example: For the usual A(BC)(DE)F case, when the iterator goes down the
                   2958:     # BC branch and gets to C, it will see F as the only next resource, but it's
                   2959:     # one level lower. Thus, this is the end of the branch, since there are no
                   2960:     # more resources added to this level or above.
1.111     bowersj2 2961:     # We don't do this if the examined resource is the finish resource,
                   2962:     # because the condition given above is true, but the "END_MAP" will
                   2963:     # take care of things and we should already be at depth 0.
1.104     bowersj2 2964:     my $isEndOfBranch = $maxDepthAdded < $self->{CURRENT_DEPTH};
1.111     bowersj2 2965:     if ($isEndOfBranch && $here != $self->{FINISH_RESOURCE}) { # **9**
1.104     bowersj2 2966:         push @{$self->{STACK}->[$self->{CURRENT_DEPTH}]}, END_BRANCH();
                   2967:     }
                   2968: 
1.98      bowersj2 2969:     # That ends the main iterator logic. Now, do we want to recurse
                   2970:     # down this map (if this resource is a map)?
1.256     albertel 2971:     if ( ($self->{HERE}->is_sequence() || (!$closeAllPages && $self->{HERE}->is_page())) &&
1.98      bowersj2 2972:         (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) {
                   2973:         $self->{RECURSIVE_ITERATOR_FLAG} = 1;
                   2974:         my $firstResource = $self->{HERE}->map_start();
                   2975:         my $finishResource = $self->{HERE}->map_finish();
                   2976: 
                   2977:         $self->{RECURSIVE_ITERATOR} = 
                   2978:             Apache::lonnavmaps::iterator->new($self->{NAV_MAP}, $firstResource,
                   2979:                                               $finishResource, $self->{FILTER},
1.314     albertel 2980:                                               $self->{ALREADY_SEEN},
                   2981: 					      $self->{CONDITION},
                   2982: 					      $self->{FORCE_TOP});
1.98      bowersj2 2983:     }
                   2984: 
1.116     bowersj2 2985:     # If this is a blank resource, don't actually return it.
1.117     bowersj2 2986:     # Should you ever find you need it, make sure to add an option to the code
                   2987:     #  that you can use; other things depend on this behavior.
1.138     bowersj2 2988:     my $browsePriv = $self->{HERE}->browsePriv();
                   2989:     if (!$self->{HERE}->src() || 
                   2990:         (!($browsePriv eq 'F') && !($browsePriv eq '2')) ) {
1.256     albertel 2991:         return $self->next($closeAllPages);
1.116     bowersj2 2992:     }
                   2993: 
1.98      bowersj2 2994:     return $self->{HERE};
                   2995: 
                   2996: }
                   2997: 
                   2998: =pod
                   2999: 
1.174     albertel 3000: The other method available on the iterator is B<getStack>, which
                   3001: returns an array populated with the current 'stack' of maps, as
                   3002: references to the resource objects. Example: This is useful when
                   3003: making the navigation map, as we need to check whether we are under a
                   3004: page map to see if we need to link directly to the resource, or to the
                   3005: page. The first elements in the array will correspond to the top of
                   3006: the stack (most inclusive map).
1.98      bowersj2 3007: 
                   3008: =cut
                   3009: 
                   3010: sub getStack {
                   3011:     my $self=shift;
                   3012: 
                   3013:     my @stack;
                   3014: 
                   3015:     $self->populateStack(\@stack);
                   3016: 
                   3017:     return \@stack;
                   3018: }
                   3019: 
                   3020: # Private method: Calls the iterators recursively to populate the stack.
                   3021: sub populateStack {
                   3022:     my $self=shift;
                   3023:     my $stack = shift;
                   3024: 
                   3025:     push @$stack, $self->{HERE} if ($self->{HERE});
                   3026: 
                   3027:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   3028:         $self->{RECURSIVE_ITERATOR}->populateStack($stack);
                   3029:     }
1.94      bowersj2 3030: }
                   3031: 
                   3032: 1;
                   3033: 
                   3034: package Apache::lonnavmaps::DFSiterator;
1.365     albertel 3035: use Scalar::Util qw(weaken);
1.320     albertel 3036: use Apache::lonnet;
                   3037: 
1.100     bowersj2 3038: # Not documented in the perldoc: This is a simple iterator that just walks
                   3039: #  through the nav map and presents the resources in a depth-first search
                   3040: #  fashion, ignorant of conditionals, randomized resources, etc. It presents
                   3041: #  BEGIN_MAP and END_MAP, but does not understand branches at all. It is
                   3042: #  useful for pre-processing of some kind, and is in fact used by the main
                   3043: #  iterator that way, but that's about it.
                   3044: # One could imagine merging this into the init routine of the main iterator,
1.251     www      3045: #  but this might as well be left separate, since it is possible some other
1.100     bowersj2 3046: #  use might be found for it. - Jeremy
1.94      bowersj2 3047: 
1.117     bowersj2 3048: # Unlike the main iterator, this DOES return all resources, even blank ones.
                   3049: #  The main iterator needs them to correctly preprocess the map.
                   3050: 
1.94      bowersj2 3051: sub BEGIN_MAP { return 1; }    # begining of a new map
                   3052: sub END_MAP { return 2; }      # end of the map
                   3053: sub FORWARD { return 1; }      # go forward
                   3054: sub BACKWARD { return 2; }
                   3055: 
1.100     bowersj2 3056: # Params: Nav map ref, first resource id/ref, finish resource id/ref,
                   3057: #         filter hash ref (or undef), already seen hash or undef, condition
                   3058: #         (as in main iterator), direction FORWARD or BACKWARD (undef->forward).
1.51      bowersj2 3059: sub new {
                   3060:     # magic invocation to create a class instance
                   3061:     my $proto = shift;
                   3062:     my $class = ref($proto) || $proto;
                   3063:     my $self = {};
                   3064: 
1.300     albertel 3065:     weaken($self->{NAV_MAP} = shift);
1.51      bowersj2 3066:     return undef unless ($self->{NAV_MAP});
                   3067: 
                   3068:     $self->{FIRST_RESOURCE} = shift || $self->{NAV_MAP}->firstResource();
                   3069:     $self->{FINISH_RESOURCE} = shift || $self->{NAV_MAP}->finishResource();
                   3070: 
                   3071:     # If the given resources are just the ID of the resource, get the
                   3072:     # objects
                   3073:     if (!ref($self->{FIRST_RESOURCE})) { $self->{FIRST_RESOURCE} = 
                   3074:              $self->{NAV_MAP}->getById($self->{FIRST_RESOURCE}); }
                   3075:     if (!ref($self->{FINISH_RESOURCE})) { $self->{FINISH_RESOURCE} = 
                   3076:              $self->{NAV_MAP}->getById($self->{FINISH_RESOURCE}); }
                   3077: 
                   3078:     $self->{FILTER} = shift;
                   3079: 
                   3080:     # A hash, used as a set, of resource already seen
                   3081:     $self->{ALREADY_SEEN} = shift;
1.140     bowersj2 3082:      if (!defined($self->{ALREADY_SEEN})) { $self->{ALREADY_SEEN} = {} };
1.63      bowersj2 3083:     $self->{CONDITION} = shift;
1.89      bowersj2 3084:     $self->{DIRECTION} = shift || FORWARD();
1.51      bowersj2 3085: 
1.100     bowersj2 3086:     # Flag: Have we started yet?
1.51      bowersj2 3087:     $self->{STARTED} = 0;
                   3088: 
                   3089:     # Should we continue calling the recursive iterator, if any?
                   3090:     $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   3091:     # The recursive iterator, if any
                   3092:     $self->{RECURSIVE_ITERATOR} = undef;
                   3093:     # Are we recursing on a map, or a branch?
                   3094:     $self->{RECURSIVE_MAP} = 1; # we'll manually unset this when recursing on branches
                   3095:     # And the count of how deep it is, so that this iterator can keep track of
                   3096:     # when to pick back up again.
                   3097:     $self->{RECURSIVE_DEPTH} = 0;
                   3098: 
                   3099:     # For keeping track of our branches, we maintain our own stack
1.100     bowersj2 3100:     $self->{STACK} = [];
1.51      bowersj2 3101: 
                   3102:     # Start with the first resource
1.89      bowersj2 3103:     if ($self->{DIRECTION} == FORWARD) {
1.100     bowersj2 3104:         push @{$self->{STACK}}, $self->{FIRST_RESOURCE};
1.89      bowersj2 3105:     } else {
1.100     bowersj2 3106:         push @{$self->{STACK}}, $self->{FINISH_RESOURCE};
1.89      bowersj2 3107:     }
1.51      bowersj2 3108: 
                   3109:     bless($self);
                   3110:     return $self;
                   3111: }
                   3112: 
                   3113: sub next {
                   3114:     my $self = shift;
                   3115:     
                   3116:     # Are we using a recursive iterator? If so, pull from that and
                   3117:     # watch the depth; we want to resume our level at the correct time.
1.98      bowersj2 3118:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
1.51      bowersj2 3119:         # grab the next from the recursive iterator
                   3120:         my $next = $self->{RECURSIVE_ITERATOR}->next();
                   3121:         
                   3122:         # is it a begin or end map? Update depth if so
                   3123:         if ($next == BEGIN_MAP() ) { $self->{RECURSIVE_DEPTH}++; }
                   3124:         if ($next == END_MAP() ) { $self->{RECURSIVE_DEPTH}--; }
                   3125: 
                   3126:         # Are we back at depth 0? If so, stop recursing.
                   3127:         if ($self->{RECURSIVE_DEPTH} == 0) {
                   3128:             $self->{RECURSIVE_ITERATOR_FLAG} = 0;
                   3129:         }
                   3130:         
                   3131:         return $next;
                   3132:     }
                   3133: 
                   3134:     # Is there a current resource to grab? If not, then return
1.100     bowersj2 3135:     # END_MAP, which will end the iterator.
                   3136:     if (scalar(@{$self->{STACK}}) == 0) {
                   3137:         return $self->END_MAP();
1.51      bowersj2 3138:     }
                   3139: 
                   3140:     # Have we not yet begun? If not, return BEGIN_MAP and 
                   3141:     # remember that we've started.
                   3142:     if ( !$self->{STARTED} ) {
                   3143:         $self->{STARTED} = 1;
                   3144:         return $self->BEGIN_MAP;
                   3145:     }
                   3146: 
                   3147:     # Get the next resource in the branch
1.100     bowersj2 3148:     $self->{HERE} = pop @{$self->{STACK}};
1.52      bowersj2 3149: 
1.100     bowersj2 3150:     # remember that we've seen this, so we don't return it again later
1.51      bowersj2 3151:     $self->{ALREADY_SEEN}->{$self->{HERE}->{ID}} = 1;
                   3152:     
                   3153:     # Get the next possible resources
1.90      bowersj2 3154:     my $nextUnfiltered;
1.89      bowersj2 3155:     if ($self->{DIRECTION} == FORWARD()) {
1.90      bowersj2 3156:         $nextUnfiltered = $self->{HERE}->getNext();
1.89      bowersj2 3157:     } else {
1.90      bowersj2 3158:         $nextUnfiltered = $self->{HERE}->getPrevious();
1.89      bowersj2 3159:     }
1.51      bowersj2 3160:     my $next = [];
                   3161: 
                   3162:     # filter the next possibilities to remove things we've 
1.100     bowersj2 3163:     # already seen.
1.394     raeburn  3164:     foreach my $item (@$nextUnfiltered) {
                   3165:         if (!defined($self->{ALREADY_SEEN}->{$item->{ID}})) {
                   3166:             push @$next, $item;
1.52      bowersj2 3167:         }
1.51      bowersj2 3168:     }
                   3169: 
                   3170:     while (@$next) {
1.100     bowersj2 3171:         # copy the next possibilities over to the stack
                   3172:         push @{$self->{STACK}}, shift @$next;
1.51      bowersj2 3173:     }
                   3174: 
                   3175:     # If this is a map and we want to recurse down it... (not filtered out)
1.70      bowersj2 3176:     if ($self->{HERE}->is_map() && 
1.63      bowersj2 3177:          (defined($self->{FILTER}->{$self->{HERE}->map_pc()}) xor $self->{CONDITION})) { 
1.51      bowersj2 3178:         $self->{RECURSIVE_ITERATOR_FLAG} = 1;
                   3179:         my $firstResource = $self->{HERE}->map_start();
                   3180:         my $finishResource = $self->{HERE}->map_finish();
                   3181: 
                   3182:         $self->{RECURSIVE_ITERATOR} =
1.94      bowersj2 3183:           Apache::lonnavmaps::DFSiterator->new ($self->{NAV_MAP}, $firstResource, 
1.63      bowersj2 3184:                      $finishResource, $self->{FILTER}, $self->{ALREADY_SEEN},
1.91      bowersj2 3185:                                              $self->{CONDITION}, $self->{DIRECTION});
1.51      bowersj2 3186:     }
                   3187: 
                   3188:     return $self->{HERE};
1.190     bowersj2 3189: }
                   3190: 
                   3191: # Identical to the full iterator methods of the same name. Hate to copy/paste
                   3192: # but I also hate to "inherit" either iterator from the other.
                   3193: 
                   3194: sub getStack {
                   3195:     my $self=shift;
                   3196: 
                   3197:     my @stack;
                   3198: 
                   3199:     $self->populateStack(\@stack);
                   3200: 
                   3201:     return \@stack;
                   3202: }
                   3203: 
                   3204: # Private method: Calls the iterators recursively to populate the stack.
                   3205: sub populateStack {
                   3206:     my $self=shift;
                   3207:     my $stack = shift;
                   3208: 
                   3209:     push @$stack, $self->{HERE} if ($self->{HERE});
                   3210: 
                   3211:     if ($self->{RECURSIVE_ITERATOR_FLAG}) {
                   3212:         $self->{RECURSIVE_ITERATOR}->populateStack($stack);
                   3213:     }
1.51      bowersj2 3214: }
                   3215: 
1.1       www      3216: 1;
1.2       www      3217: 
1.51      bowersj2 3218: package Apache::lonnavmaps::resource;
1.365     albertel 3219: use Scalar::Util qw(weaken);
1.51      bowersj2 3220: use Apache::lonnet;
                   3221: 
                   3222: =pod
                   3223: 
1.217     bowersj2 3224: =head1 Object: resource 
1.51      bowersj2 3225: 
1.217     bowersj2 3226: X<resource, navmap object>
1.174     albertel 3227: A resource object encapsulates a resource in a resource map, allowing
                   3228: easy manipulation of the resource, querying the properties of the
                   3229: resource (including user properties), and represents a reference that
                   3230: can be used as the canonical representation of the resource by
                   3231: lonnavmap clients like renderers.
                   3232: 
                   3233: A resource only makes sense in the context of a navmap, as some of the
                   3234: data is stored in the navmap object.
                   3235: 
                   3236: You will probably never need to instantiate this object directly. Use
                   3237: Apache::lonnavmaps::navmap, and use the "start" method to obtain the
                   3238: starting resource.
1.51      bowersj2 3239: 
1.188     bowersj2 3240: Resource objects respect the parameter_hiddenparts, which suppresses 
                   3241: various parts according to the wishes of the map author. As of this
                   3242: writing, there is no way to override this parameter, and suppressed
                   3243: parts will never be returned, nor will their response types or ids be
                   3244: stored.
                   3245: 
1.217     bowersj2 3246: =head2 Overview
1.51      bowersj2 3247: 
1.217     bowersj2 3248: A B<Resource> is the most granular type of object in LON-CAPA that can
                   3249: be included in a course. It can either be a particular resource, like
                   3250: an HTML page, external resource, problem, etc., or it can be a
                   3251: container sequence, such as a "page" or a "map".
                   3252: 
                   3253: To see a sequence from the user's point of view, please see the
                   3254: B<Creating a Course: Maps and Sequences> chapter of the Author's
                   3255: Manual.
                   3256: 
                   3257: A Resource Object, once obtained from a navmap object via a B<getBy*>
                   3258: method of the navmap, or from an iterator, allows you to query
                   3259: information about that resource.
                   3260: 
                   3261: Generally, you do not ever want to create a resource object yourself,
                   3262: so creation has been left undocumented. Always retrieve resources
                   3263: from navmap objects.
                   3264: 
                   3265: =head3 Identifying Resources
                   3266: 
                   3267: X<big hash>Every resource is identified by a Resource ID in the big hash that is
                   3268: unique to that resource for a given course. X<resource ID, in big hash>
                   3269: The Resource ID has the form #.#, where the first number is the same
                   3270: for every resource in a map, and the second is unique. For instance,
                   3271: for a course laid out like this:
                   3272: 
                   3273:  * Problem 1
                   3274:  * Map
                   3275:    * Resource 2
                   3276:    * Resource 3
                   3277: 
                   3278: C<Problem 1> and C<Map> will share a first number, and C<Resource 2>
                   3279: C<Resource 3> will share a first number. The second number may end up
                   3280: re-used between the two groups.
                   3281: 
                   3282: The resource ID is only used in the big hash, but can be used in the
                   3283: context of a course to identify a resource easily. (For instance, the
                   3284: printing system uses it to record which resources from a sequence you 
                   3285: wish to print.)
                   3286: 
                   3287: X<symb> X<resource, symb>
                   3288: All resources also have B<symb>s, which uniquely identify a resource
                   3289: in a course. Many internal LON-CAPA functions expect a symb. A symb
                   3290: carries along with it the URL of the resource, and the map it appears
                   3291: in. Symbs are much larger then resource IDs.
1.51      bowersj2 3292: 
                   3293: =cut
                   3294: 
                   3295: sub new {
                   3296:     # magic invocation to create a class instance
                   3297:     my $proto = shift;
                   3298:     my $class = ref($proto) || $proto;
                   3299:     my $self = {};
                   3300: 
1.300     albertel 3301:     weaken($self->{NAV_MAP} = shift);
1.51      bowersj2 3302:     $self->{ID} = shift;
                   3303: 
                   3304:     # Store this new resource in the parent nav map's cache.
                   3305:     $self->{NAV_MAP}->{RESOURCE_CACHE}->{$self->{ID}} = $self;
1.66      bowersj2 3306:     $self->{RESOURCE_ERROR} = 0;
1.51      bowersj2 3307: 
                   3308:     # A hash that can be used by two-pass algorithms to store data
                   3309:     # about this resource in. Not used by the resource object
                   3310:     # directly.
                   3311:     $self->{DATA} = {};
                   3312:    
                   3313:     bless($self);
                   3314:     
                   3315:     return $self;
                   3316: }
                   3317: 
1.70      bowersj2 3318: # private function: simplify the NAV_HASH lookups we keep doing
                   3319: # pass the name, and to automatically append my ID, pass a true val on the
                   3320: # second param
                   3321: sub navHash {
                   3322:     my $self = shift;
                   3323:     my $param = shift;
                   3324:     my $id = shift;
1.115     bowersj2 3325:     return $self->{NAV_MAP}->navhash($param . ($id?$self->{ID}:""));
1.70      bowersj2 3326: }
                   3327: 
1.51      bowersj2 3328: =pod
                   3329: 
1.217     bowersj2 3330: =head2 Methods
1.70      bowersj2 3331: 
1.217     bowersj2 3332: Once you have a resource object, here's what you can do with it:
                   3333: 
                   3334: =head3 Attribute Retrieval
                   3335: 
                   3336: Every resource has certain attributes that can be retrieved and used:
1.70      bowersj2 3337: 
                   3338: =over 4
                   3339: 
1.217     bowersj2 3340: =item * B<ID>: Every resource has an ID that is unique for that
                   3341:     resource in the course it is in. The ID is actually in the hash
                   3342:     representing the resource, so for a resource object $res, obtain
                   3343:     it via C<$res->{ID}).
                   3344: 
1.174     albertel 3345: =item * B<compTitle>:
                   3346: 
                   3347: Returns a "composite title", that is equal to $res->title() if the
                   3348: resource has a title, and is otherwise the last part of the URL (e.g.,
                   3349: "problem.problem").
                   3350: 
                   3351: =item * B<ext>:
                   3352: 
                   3353: Returns true if the resource is external.
                   3354: 
                   3355: =item * B<kind>:
                   3356: 
                   3357: Returns the kind of the resource from the compiled nav map.
                   3358: 
                   3359: =item * B<randomout>:
1.106     bowersj2 3360: 
1.174     albertel 3361: Returns true if this resource was chosen to NOT be shown to the user
                   3362: by the random map selection feature. In other words, this is usually
                   3363: false.
1.70      bowersj2 3364: 
1.174     albertel 3365: =item * B<randompick>:
1.70      bowersj2 3366: 
1.174     albertel 3367: Returns true for a map if the randompick feature is being used on the
                   3368: map. (?)
1.70      bowersj2 3369: 
1.174     albertel 3370: =item * B<src>:
1.51      bowersj2 3371: 
1.174     albertel 3372: Returns the source for the resource.
1.70      bowersj2 3373: 
1.174     albertel 3374: =item * B<symb>:
1.70      bowersj2 3375: 
1.174     albertel 3376: Returns the symb for the resource.
1.70      bowersj2 3377: 
1.174     albertel 3378: =item * B<title>:
1.70      bowersj2 3379: 
1.174     albertel 3380: Returns the title of the resource.
                   3381: 
1.51      bowersj2 3382: =back
                   3383: 
                   3384: =cut
                   3385: 
                   3386: # These info functions can be used directly, as they don't return
                   3387: # resource information.
1.85      bowersj2 3388: sub comesfrom { my $self=shift; return $self->navHash("comesfrom_", 1); }
1.303     albertel 3389: sub encrypted { my $self=shift; return $self->navHash("encrypted_", 1); }
1.70      bowersj2 3390: sub ext { my $self=shift; return $self->navHash("ext_", 1) eq 'true:'; }
1.85      bowersj2 3391: sub from { my $self=shift; return $self->navHash("from_", 1); }
1.217     bowersj2 3392: # considered private and undocumented
1.51      bowersj2 3393: sub goesto { my $self=shift; return $self->navHash("goesto_", 1); }
                   3394: sub kind { my $self=shift; return $self->navHash("kind_", 1); }
1.68      bowersj2 3395: sub randomout { my $self=shift; return $self->navHash("randomout_", 1); }
                   3396: sub randompick { 
                   3397:     my $self = shift;
1.370     albertel 3398:     return $self->parmval('randompick');
1.68      bowersj2 3399: }
1.303     albertel 3400: sub link {
                   3401:     my $self=shift;
                   3402:     if ($self->encrypted()) { return &Apache::lonenc::encrypted($self->src); }
                   3403:     return $self->src;
                   3404: }
1.51      bowersj2 3405: sub src { 
                   3406:     my $self=shift;
                   3407:     return $self->navHash("src_", 1);
                   3408: }
1.303     albertel 3409: sub shown_symb {
                   3410:     my $self=shift;
                   3411:     if ($self->encrypted()) {return &Apache::lonenc::encrypted($self->symb());}
                   3412:     return $self->symb();
                   3413: }
1.328     www      3414: sub id {
                   3415:     my $self=shift;
                   3416:     return $self->{ID};
                   3417: }
                   3418: sub enclosing_map_src {
                   3419:     my $self=shift;
                   3420:     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
                   3421:     return $self->navHash('map_id_'.$first);
                   3422: }
1.51      bowersj2 3423: sub symb {
                   3424:     my $self=shift;
                   3425:     (my $first, my $second) = $self->{ID} =~ /(\d+).(\d+)/;
                   3426:     my $symbSrc = &Apache::lonnet::declutter($self->src());
1.223     albertel 3427:     my $symb = &Apache::lonnet::declutter($self->navHash('map_id_'.$first)) 
1.51      bowersj2 3428:         . '___' . $second . '___' . $symbSrc;
1.223     albertel 3429:     return &Apache::lonnet::symbclean($symb);
1.51      bowersj2 3430: }
1.321     raeburn  3431: sub wrap_symb {
                   3432:     my $self = shift;
                   3433:     return $self->{NAV_MAP}->wrap_symb($self->symb());
                   3434: }
1.213     bowersj2 3435: sub title { 
                   3436:     my $self=shift; 
                   3437:     if ($self->{ID} eq '0.0') {
                   3438: 	# If this is the top-level map, return the title of the course
                   3439: 	# since this map can not be titled otherwise.
1.320     albertel 3440: 	return $env{'course.'.$env{'request.course.id'}.'.description'};
1.213     bowersj2 3441:     }
                   3442:     return $self->navHash("title_", 1); }
1.217     bowersj2 3443: # considered private and undocumented
1.70      bowersj2 3444: sub to { my $self=shift; return $self->navHash("to_", 1); }
1.301     albertel 3445: sub condition {
                   3446:     my $self=shift;
                   3447:     my $undercond=$self->navHash("undercond_", 1);
                   3448:     if (!defined($undercond)) { return 1; };
                   3449:     my $condid=$self->navHash("condid_$undercond");
                   3450:     if (!defined($condid)) { return 1; };
                   3451:     my $condition=&Apache::lonnet::directcondval($condid);
                   3452:     return $condition;
                   3453: }
1.342     albertel 3454: sub condval {
                   3455:     my $self=shift;
1.359     albertel 3456:     my ($pathname,$filename) = 
                   3457: 	&Apache::lonnet::split_uri_for_cond($self->src());
1.342     albertel 3458: 
                   3459:     my $match=($env{'acc.res.'.$env{'request.course.id'}.'.'.$pathname}=~
                   3460: 	       /\&\Q$filename\E\:([\d\|]+)\&/);
                   3461:     if ($match) {
                   3462: 	return &Apache::lonnet::condval($1);
                   3463:     }
                   3464:     return 0;
                   3465: }
1.106     bowersj2 3466: sub compTitle {
                   3467:     my $self = shift;
                   3468:     my $title = $self->title();
1.176     www      3469:     $title=~s/\&colon\;/\:/gs;
1.106     bowersj2 3470:     if (!$title) {
                   3471:         $title = $self->src();
                   3472:         $title = substr($title, rindex($title, '/') + 1);
                   3473:     }
                   3474:     return $title;
                   3475: }
1.70      bowersj2 3476: =pod
                   3477: 
                   3478: B<Predicate Testing the Resource>
                   3479: 
                   3480: These methods are shortcuts to deciding if a given resource has a given property.
                   3481: 
                   3482: =over 4
                   3483: 
1.174     albertel 3484: =item * B<is_map>:
                   3485: 
                   3486: Returns true if the resource is a map type.
                   3487: 
                   3488: =item * B<is_problem>:
1.70      bowersj2 3489: 
1.174     albertel 3490: Returns true if the resource is a problem type, false
                   3491: otherwise. (Looks at the extension on the src field; might need more
                   3492: to work correctly.)
1.70      bowersj2 3493: 
1.174     albertel 3494: =item * B<is_page>:
1.70      bowersj2 3495: 
1.174     albertel 3496: Returns true if the resource is a page.
                   3497: 
                   3498: =item * B<is_sequence>:
                   3499: 
                   3500: Returns true if the resource is a sequence.
1.70      bowersj2 3501: 
                   3502: =back
                   3503: 
                   3504: =cut
                   3505: 
1.256     albertel 3506: sub hasResource {
                   3507:    my $self = shift;
                   3508:    return $self->{NAV_MAP}->hasResource(@_);
                   3509: }
                   3510: 
                   3511: sub retrieveResources {
                   3512:    my $self = shift;
                   3513:    return $self->{NAV_MAP}->retrieveResources(@_);
                   3514: }
1.70      bowersj2 3515: 
1.369     albertel 3516: sub is_exam {
                   3517:     my ($self,$part) = @_;
                   3518:     if ($self->parmval('type',$part) eq 'exam') {
                   3519:         return 1;
                   3520:     }
                   3521:     if ($self->src() =~ /\.(exam)$/) {
                   3522:         return 1;
                   3523:     }
                   3524:     return 0;
                   3525: }
1.70      bowersj2 3526: sub is_html {
1.51      bowersj2 3527:     my $self=shift;
                   3528:     my $src = $self->src();
1.70      bowersj2 3529:     return ($src =~ /html$/);
1.51      bowersj2 3530: }
1.70      bowersj2 3531: sub is_map { my $self=shift; return defined($self->navHash("is_map_", 1)); }
                   3532: sub is_page {
1.51      bowersj2 3533:     my $self=shift;
                   3534:     my $src = $self->src();
1.205     bowersj2 3535:     return $self->navHash("is_map_", 1) && 
                   3536: 	$self->navHash("map_type_" . $self->map_pc()) eq 'page';
1.51      bowersj2 3537: }
1.361     albertel 3538: sub is_practice {
                   3539:     my $self=shift;
                   3540:     my ($part) = @_;
                   3541:     if ($self->parmval('type',$part) eq 'practice') {
                   3542:         return 1;
                   3543:     }
                   3544:     return 0;
                   3545: }
1.70      bowersj2 3546: sub is_problem {
1.51      bowersj2 3547:     my $self=shift;
                   3548:     my $src = $self->src();
1.361     albertel 3549:     if ($src =~ /\.(problem|exam|quiz|assess|survey|form|library|task)$/) {
                   3550: 	return !($self->is_practice());
                   3551:     }
                   3552:     return 0;
1.256     albertel 3553: }
                   3554: sub contains_problem {
                   3555:     my $self=shift;
                   3556:     if ($self->is_page()) {
                   3557: 	my $hasProblem=$self->hasResource($self,sub { $_[0]->is_problem() },1);
                   3558: 	return $hasProblem;
                   3559:     }
                   3560:     return 0;
1.51      bowersj2 3561: }
1.70      bowersj2 3562: sub is_sequence {
1.51      bowersj2 3563:     my $self=shift;
1.205     bowersj2 3564:     return $self->navHash("is_map_", 1) && 
                   3565: 	$self->navHash("map_type_" . $self->map_pc()) eq 'sequence';
1.51      bowersj2 3566: }
1.261     matthew  3567: sub is_survey {
                   3568:     my $self = shift();
                   3569:     my $part = shift();
1.263     albertel 3570:     if ($self->parmval('type',$part) eq 'survey') {
1.261     matthew  3571:         return 1;
                   3572:     }
1.263     albertel 3573:     if ($self->src() =~ /\.(survey)$/) {
1.261     matthew  3574:         return 1;
                   3575:     }
                   3576:     return 0;
                   3577: }
1.360     albertel 3578: sub is_task {
                   3579:     my $self=shift;
                   3580:     my $src = $self->src();
                   3581:     return ($src =~ /\.(task)$/)
                   3582: }
1.51      bowersj2 3583: 
1.266     raeburn  3584: sub is_empty_sequence {
                   3585:     my $self=shift;
                   3586:     my $src = $self->src();
                   3587:     return !$self->is_page() && $self->navHash("is_map_", 1) && !$self->navHash("map_type_" . $self->map_pc());
                   3588: }
                   3589: 
1.101     bowersj2 3590: # Private method: Shells out to the parmval in the nav map, handler parts.
1.51      bowersj2 3591: sub parmval {
                   3592:     my $self = shift;
                   3593:     my $what = shift;
1.185     bowersj2 3594:     my $part = shift;
                   3595:     if (!defined($part)) { 
                   3596:         $part = '0'; 
                   3597:     }
1.51      bowersj2 3598:     return $self->{NAV_MAP}->parmval($part.'.'.$what, $self->symb());
                   3599: }
                   3600: 
1.70      bowersj2 3601: =pod
                   3602: 
                   3603: B<Map Methods>
                   3604: 
1.174     albertel 3605: These methods are useful for getting information about the map
                   3606: properties of the resource, if the resource is a map (B<is_map>).
1.70      bowersj2 3607: 
                   3608: =over 4
                   3609: 
1.174     albertel 3610: =item * B<map_finish>:
                   3611: 
                   3612: Returns a reference to a resource object corresponding to the finish
                   3613: resource of the map.
1.70      bowersj2 3614: 
1.174     albertel 3615: =item * B<map_pc>:
1.70      bowersj2 3616: 
1.174     albertel 3617: Returns the pc value of the map, which is the first number that
                   3618: appears in the resource ID of the resources in the map, and is the
                   3619: number that appears around the middle of the symbs of the resources in
                   3620: that map.
1.70      bowersj2 3621: 
1.174     albertel 3622: =item * B<map_start>:
                   3623: 
                   3624: Returns a reference to a resource object corresponding to the start
                   3625: resource of the map.
                   3626: 
                   3627: =item * B<map_type>:
                   3628: 
                   3629: Returns a string with the type of the map in it.
1.70      bowersj2 3630: 
                   3631: =back
1.51      bowersj2 3632: 
1.70      bowersj2 3633: =cut
1.51      bowersj2 3634: 
                   3635: sub map_finish {
                   3636:     my $self = shift;
                   3637:     my $src = $self->src();
1.381     albertel 3638:     $src = &Apache::lonnet::clutter($src);
1.51      bowersj2 3639:     my $res = $self->navHash("map_finish_$src", 0);
                   3640:     $res = $self->{NAV_MAP}->getById($res);
                   3641:     return $res;
                   3642: }
1.70      bowersj2 3643: sub map_pc {
                   3644:     my $self = shift;
1.381     albertel 3645:     my $src = $self->src();
1.70      bowersj2 3646:     return $self->navHash("map_pc_$src", 0);
                   3647: }
1.51      bowersj2 3648: sub map_start {
                   3649:     my $self = shift;
                   3650:     my $src = $self->src();
1.381     albertel 3651:     $src = &Apache::lonnet::clutter($src);
1.51      bowersj2 3652:     my $res = $self->navHash("map_start_$src", 0);
                   3653:     $res = $self->{NAV_MAP}->getById($res);
                   3654:     return $res;
                   3655: }
                   3656: sub map_type {
                   3657:     my $self = shift;
                   3658:     my $pc = $self->map_pc();
                   3659:     return $self->navHash("map_type_$pc", 0);
                   3660: }
                   3661: 
                   3662: #####
                   3663: # Property queries
                   3664: #####
                   3665: 
                   3666: # These functions will be responsible for returning the CORRECT
                   3667: # VALUE for the parameter, no matter what. So while they may look
                   3668: # like direct calls to parmval, they can be more then that.
                   3669: # So, for instance, the duedate function should use the "duedatetype"
                   3670: # information, rather then the resource object user.
                   3671: 
                   3672: =pod
                   3673: 
                   3674: =head2 Resource Parameters
                   3675: 
1.174     albertel 3676: In order to use the resource parameters correctly, the nav map must
                   3677: have been instantiated with genCourseAndUserOptions set to true, so
                   3678: the courseopt and useropt is read correctly. Then, you can call these
                   3679: functions to get the relevant parameters for the resource. Each
                   3680: function defaults to part "0", but can be directed to another part by
                   3681: passing the part as the parameter.
                   3682: 
                   3683: These methods are responsible for getting the parameter correct, not
                   3684: merely reflecting the contents of the GDBM hashes. As we move towards
                   3685: dates relative to other dates, these methods should be updated to
                   3686: reflect that. (Then, anybody using these methods will not have to update
                   3687: their code.)
                   3688: 
                   3689: =over 4
                   3690: 
                   3691: =item * B<acc>:
                   3692: 
                   3693: Get the Client IP/Name Access Control information.
1.51      bowersj2 3694: 
1.174     albertel 3695: =item * B<answerdate>:
1.51      bowersj2 3696: 
1.174     albertel 3697: Get the answer-reveal date for the problem.
                   3698: 
1.211     bowersj2 3699: =item * B<awarded>: 
                   3700: 
                   3701: Gets the awarded value for the problem part. Requires genUserData set to
                   3702: true when the navmap object was created.
                   3703: 
1.174     albertel 3704: =item * B<duedate>:
                   3705: 
                   3706: Get the due date for the problem.
                   3707: 
                   3708: =item * B<tries>:
                   3709: 
                   3710: Get the number of tries the student has used on the problem.
                   3711: 
                   3712: =item * B<maxtries>:
                   3713: 
                   3714: Get the number of max tries allowed.
                   3715: 
                   3716: =item * B<opendate>:
1.51      bowersj2 3717: 
1.174     albertel 3718: Get the open date for the problem.
1.51      bowersj2 3719: 
1.174     albertel 3720: =item * B<sig>:
1.51      bowersj2 3721: 
1.174     albertel 3722: Get the significant figures setting.
1.51      bowersj2 3723: 
1.174     albertel 3724: =item * B<tol>:
1.51      bowersj2 3725: 
1.174     albertel 3726: Get the tolerance for the problem.
1.51      bowersj2 3727: 
1.174     albertel 3728: =item * B<tries>:
1.51      bowersj2 3729: 
1.174     albertel 3730: Get the number of tries the user has already used on the problem.
1.51      bowersj2 3731: 
1.174     albertel 3732: =item * B<type>:
1.51      bowersj2 3733: 
1.174     albertel 3734: Get the question type for the problem.
1.70      bowersj2 3735: 
1.174     albertel 3736: =item * B<weight>:
1.51      bowersj2 3737: 
1.174     albertel 3738: Get the weight for the problem.
1.51      bowersj2 3739: 
                   3740: =back
                   3741: 
                   3742: =cut
                   3743: 
                   3744: sub acc {
                   3745:     (my $self, my $part) = @_;
                   3746:     return $self->parmval("acc", $part);
                   3747: }
                   3748: sub answerdate {
                   3749:     (my $self, my $part) = @_;
                   3750:     # Handle intervals
                   3751:     if ($self->parmval("answerdate.type", $part) eq 'date_interval') {
                   3752:         return $self->duedate($part) + 
                   3753:             $self->parmval("answerdate", $part);
                   3754:     }
                   3755:     return $self->parmval("answerdate", $part);
1.106     bowersj2 3756: }
1.211     bowersj2 3757: sub awarded { 
                   3758:     my $self = shift; my $part = shift;
1.221     bowersj2 3759:     $self->{NAV_MAP}->get_user_data();
1.211     bowersj2 3760:     if (!defined($part)) { $part = '0'; }
                   3761:     return $self->{NAV_MAP}->{STUDENT_DATA}->{$self->symb()}->{'resource.'.$part.'.awarded'};
                   3762: }
1.382     albertel 3763: # this should work exactly like the copy in lonhomework.pm
1.51      bowersj2 3764: sub duedate {
                   3765:     (my $self, my $part) = @_;
1.382     albertel 3766:     my $date;
1.260     albertel 3767:     my $interval=$self->parmval("interval", $part);
1.382     albertel 3768:     my $due_date=$self->parmval("duedate", $part);
1.390     albertel 3769:     if ($interval =~ /\d+/) {
1.260     albertel 3770: 	my $first_access=&Apache::lonnet::get_first_access('map',$self->symb);
1.382     albertel 3771: 	if (defined($first_access)) {
                   3772: 	    $interval = $first_access+$interval;
                   3773: 	    $date = ($interval < $due_date)? $interval : $due_date;
                   3774: 	} else {
                   3775: 	    $date = $due_date;
                   3776: 	}
                   3777:     } else {
                   3778: 	$date = $due_date;
1.260     albertel 3779:     }
1.382     albertel 3780:     return $date;
1.51      bowersj2 3781: }
1.334     albertel 3782: sub handgrade {
                   3783:     (my $self, my $part) = @_;
                   3784:     return $self->parmval("handgrade", $part);
                   3785: }
1.51      bowersj2 3786: sub maxtries {
                   3787:     (my $self, my $part) = @_;
                   3788:     return $self->parmval("maxtries", $part);
                   3789: }
                   3790: sub opendate {
                   3791:     (my $self, my $part) = @_;
                   3792:     if ($self->parmval("opendate.type", $part) eq 'date_interval') {
                   3793:         return $self->duedate($part) -
                   3794:             $self->parmval("opendate", $part);
                   3795:     }
                   3796:     return $self->parmval("opendate");
                   3797: }
1.185     bowersj2 3798: sub problemstatus {
                   3799:     (my $self, my $part) = @_;
1.236     bowersj2 3800:     return lc $self->parmval("problemstatus", $part);
1.185     bowersj2 3801: }
1.51      bowersj2 3802: sub sig {
                   3803:     (my $self, my $part) = @_;
                   3804:     return $self->parmval("sig", $part);
                   3805: }
                   3806: sub tol {
                   3807:     (my $self, my $part) = @_;
                   3808:     return $self->parmval("tol", $part);
                   3809: }
1.108     bowersj2 3810: sub tries { 
                   3811:     my $self = shift; 
                   3812:     my $tries = $self->queryRestoreHash('tries', shift);
                   3813:     if (!defined($tries)) { return '0';}
1.51      bowersj2 3814:     return $tries;
                   3815: }
1.70      bowersj2 3816: sub type {
                   3817:     (my $self, my $part) = @_;
                   3818:     return $self->parmval("type", $part);
                   3819: }
1.109     bowersj2 3820: sub weight { 
                   3821:     my $self = shift; my $part = shift;
1.211     bowersj2 3822:     if (!defined($part)) { $part = '0'; }
                   3823:     return &Apache::lonnet::EXT('resource.'.$part.'.weight',
1.320     albertel 3824: 				$self->symb(), $env{'user.domain'},
                   3825: 				$env{'user.name'}, 
                   3826: 				$env{'request.course.sec'});
1.274     matthew  3827: }
                   3828: sub part_display {
                   3829:     my $self= shift(); my $partID = shift();
                   3830:     if (! defined($partID)) { $partID = '0'; }
                   3831:     my $display=&Apache::lonnet::EXT('resource.'.$partID.'.display',
                   3832:                                      $self->symb);
                   3833:     if (! defined($display) || $display eq '') {
                   3834:         $display = $partID;
                   3835:     }
                   3836:     return $display;
1.70      bowersj2 3837: }
1.51      bowersj2 3838: 
                   3839: # Multiple things need this
                   3840: sub getReturnHash {
                   3841:     my $self = shift;
                   3842:     
                   3843:     if (!defined($self->{RETURN_HASH})) {
1.84      bowersj2 3844:         my %tmpHash  = &Apache::lonnet::restore($self->symb());
1.51      bowersj2 3845:         $self->{RETURN_HASH} = \%tmpHash;
                   3846:     }
                   3847: }       
                   3848: 
                   3849: ######
                   3850: # Status queries
                   3851: ######
                   3852: 
                   3853: # These methods query the status of problems.
                   3854: 
                   3855: # If we need to count parts, this function determines the number of
                   3856: # parts from the metadata. When called, it returns a reference to a list
                   3857: # of strings corresponding to the parts. (Thus, using it in a scalar context
                   3858: # tells you how many parts you have in the problem:
                   3859: # $partcount = scalar($resource->countParts());
                   3860: # Don't use $self->{PARTS} directly because you don't know if it's been
                   3861: # computed yet.
                   3862: 
                   3863: =pod
                   3864: 
                   3865: =head2 Resource misc
                   3866: 
                   3867: Misc. functions for the resource.
                   3868: 
                   3869: =over 4
                   3870: 
1.174     albertel 3871: =item * B<hasDiscussion>:
1.59      bowersj2 3872: 
1.174     albertel 3873: Returns a false value if there has been discussion since the user last
                   3874: logged in, true if there has. Always returns false if the discussion
                   3875: data was not extracted when the nav map was constructed.
                   3876: 
1.366     albertel 3877: =item * B<last_post_time>:
                   3878: 
                   3879: Returns a false value if there hasn't been discussion otherwise returns
                   3880: unix timestamp of last time a discussion posting (or edit) was made.
                   3881: 
1.393     raeburn  3882: =item * B<discussion_info>:
1.366     albertel 3883: 
1.393     raeburn  3884: optional argument is a filter (currently can be 'unread');
                   3885: returns in scalar context the count of the number of discussion postings.
1.366     albertel 3886: 
                   3887: returns in list context both the count of postings and a hash ref
1.393     raeburn  3888: containing information about the postings (subject, id, timestamp) in a hash.
                   3889: 
                   3890: Default is to return counts for all postings.  However if called with a second argument set to 'unread', will return information about only unread postings.
1.366     albertel 3891: 
1.174     albertel 3892: =item * B<getFeedback>:
                   3893: 
                   3894: Gets the feedback for the resource and returns the raw feedback string
                   3895: for the resource, or the null string if there is no feedback or the
                   3896: email data was not extracted when the nav map was constructed. Usually
                   3897: used like this:
1.59      bowersj2 3898: 
1.394     raeburn  3899:  for my $url (split(/\,/, $res->getFeedback())) {
                   3900:     my $link = &escape($url);
1.59      bowersj2 3901:     ...
                   3902: 
                   3903: and use the link as appropriate.
                   3904: 
                   3905: =cut
                   3906: 
                   3907: sub hasDiscussion {
                   3908:     my $self = shift;
                   3909:     return $self->{NAV_MAP}->hasDiscussion($self->symb());
                   3910: }
                   3911: 
1.366     albertel 3912: sub last_post_time {
                   3913:     my $self = shift;
                   3914:     return $self->{NAV_MAP}->last_post_time($self->symb());
                   3915: }
                   3916: 
1.393     raeburn  3917: sub discussion_info {
                   3918:     my ($self,$filter) = @_;
                   3919:     return $self->{NAV_MAP}->discussion_info($self->symb(),$filter);
1.366     albertel 3920: }
                   3921: 
1.59      bowersj2 3922: sub getFeedback {
                   3923:     my $self = shift;
1.124     bowersj2 3924:     my $source = $self->src();
1.395     raeburn  3925:     my $symb = $self->symb();
1.125     bowersj2 3926:     if ($source =~ /^\/res\//) { $source = substr $source, 5; }
1.395     raeburn  3927:     return $self->{NAV_MAP}->getFeedback($symb,$source);
1.124     bowersj2 3928: }
                   3929: 
                   3930: sub getErrors {
                   3931:     my $self = shift;
                   3932:     my $source = $self->src();
1.395     raeburn  3933:     my $symb = $self->symb();
1.124     bowersj2 3934:     if ($source =~ /^\/res\//) { $source = substr $source, 5; }
1.395     raeburn  3935:     return $self->{NAV_MAP}->getErrors($symb,$source);
1.59      bowersj2 3936: }
                   3937: 
                   3938: =pod
                   3939: 
1.174     albertel 3940: =item * B<parts>():
1.51      bowersj2 3941: 
1.174     albertel 3942: Returns a list reference containing sorted strings corresponding to
1.193     bowersj2 3943: each part of the problem. Single part problems have only a part '0'.
                   3944: Multipart problems do not return their part '0', since they typically
                   3945: do not really matter. 
1.174     albertel 3946: 
                   3947: =item * B<countParts>():
                   3948: 
                   3949: Returns the number of parts of the problem a student can answer. Thus,
                   3950: for single part problems, returns 1. For multipart, it returns the
1.193     bowersj2 3951: number of parts in the problem, not including psuedo-part 0. 
1.51      bowersj2 3952: 
1.290     matthew  3953: =item * B<countResponses>():
                   3954: 
                   3955: Returns the total number of responses in the problem a student can answer.
                   3956: 
                   3957: =item * B<responseTypes>():
                   3958: 
                   3959: Returns a hash whose keys are the response types.  The values are the number 
                   3960: of times each response type is used.  This is for the I<entire> problem, not 
                   3961: just a single part.
                   3962: 
1.192     bowersj2 3963: =item * B<multipart>():
                   3964: 
1.193     bowersj2 3965: Returns true if the problem is multipart, false otherwise. Use this instead
                   3966: of countParts if all you want is multipart/not multipart.
1.192     bowersj2 3967: 
1.187     bowersj2 3968: =item * B<responseType>($part):
                   3969: 
                   3970: Returns the response type of the part, without the word "response" on the
                   3971: end. Example return values: 'string', 'essay', 'numeric', etc.
                   3972: 
1.193     bowersj2 3973: =item * B<responseIds>($part):
1.187     bowersj2 3974: 
1.193     bowersj2 3975: Retreives the response IDs for the given part as an array reference containing
                   3976: strings naming the response IDs. This may be empty.
1.187     bowersj2 3977: 
1.51      bowersj2 3978: =back
                   3979: 
                   3980: =cut
                   3981: 
                   3982: sub parts {
                   3983:     my $self = shift;
                   3984: 
1.192     bowersj2 3985:     if ($self->ext) { return []; }
1.67      bowersj2 3986: 
1.51      bowersj2 3987:     $self->extractParts();
                   3988:     return $self->{PARTS};
                   3989: }
                   3990: 
                   3991: sub countParts {
                   3992:     my $self = shift;
                   3993:     
                   3994:     my $parts = $self->parts();
1.191     bowersj2 3995: 
                   3996:     # If I left this here, then it's not necessary.
                   3997:     #my $delta = 0;
                   3998:     #for my $part (@$parts) {
                   3999:     #    if ($part eq '0') { $delta--; }
                   4000:     #}
1.66      bowersj2 4001: 
                   4002:     if ($self->{RESOURCE_ERROR}) {
                   4003:         return 0;
                   4004:     }
                   4005: 
1.191     bowersj2 4006:     return scalar(@{$parts}); # + $delta;
1.192     bowersj2 4007: }
                   4008: 
1.290     matthew  4009: sub countResponses {
                   4010:     my $self = shift;
                   4011:     my $count;
1.293     matthew  4012:     foreach my $part (@{$self->parts()}) {
                   4013:         $count+= scalar($self->responseIds($part));
1.290     matthew  4014:     }
                   4015:     return $count;
                   4016: }
                   4017: 
                   4018: sub responseTypes {
                   4019:     my $self = shift;
1.291     albertel 4020:     my %responses;
1.368     raeburn  4021:     foreach my $part (@{$self->parts()}) {
1.290     matthew  4022:         foreach my $responsetype ($self->responseType($part)) {
1.291     albertel 4023:             $responses{$responsetype}++ if (defined($responsetype));
1.290     matthew  4024:         }
                   4025:     }
1.291     albertel 4026:     return %responses;
1.290     matthew  4027: }
                   4028: 
1.192     bowersj2 4029: sub multipart {
                   4030:     my $self = shift;
                   4031:     return $self->countParts() > 1;
1.51      bowersj2 4032: }
                   4033: 
1.225     bowersj2 4034: sub singlepart {
                   4035:     my $self = shift;
                   4036:     return $self->countParts() == 1;
                   4037: }
                   4038: 
1.187     bowersj2 4039: sub responseType {
1.184     bowersj2 4040:     my $self = shift;
                   4041:     my $part = shift;
                   4042: 
                   4043:     $self->extractParts();
1.235     albertel 4044:     if (defined($self->{RESPONSE_TYPES}->{$part})) {
                   4045: 	return @{$self->{RESPONSE_TYPES}->{$part}};
                   4046:     } else {
                   4047: 	return undef;
                   4048:     }
1.187     bowersj2 4049: }
                   4050: 
1.193     bowersj2 4051: sub responseIds {
1.187     bowersj2 4052:     my $self = shift;
                   4053:     my $part = shift;
                   4054: 
                   4055:     $self->extractParts();
1.235     albertel 4056:     if (defined($self->{RESPONSE_IDS}->{$part})) {
                   4057: 	return @{$self->{RESPONSE_IDS}->{$part}};
                   4058:     } else {
                   4059: 	return undef;
                   4060:     }
1.184     bowersj2 4061: }
                   4062: 
                   4063: # Private function: Extracts the parts information, both part names and
1.187     bowersj2 4064: # part types, and saves it. 
1.51      bowersj2 4065: sub extractParts { 
                   4066:     my $self = shift;
                   4067:     
1.153     bowersj2 4068:     return if (defined($self->{PARTS}));
1.67      bowersj2 4069:     return if ($self->ext);
1.51      bowersj2 4070: 
                   4071:     $self->{PARTS} = [];
                   4072: 
1.181     bowersj2 4073:     my %parts;
                   4074: 
1.82      bowersj2 4075:     # Retrieve part count, if this is a problem
                   4076:     if ($self->is_problem()) {
1.240     albertel 4077: 	my $partorder = &Apache::lonnet::metadata($self->src(), 'partorder');
1.152     matthew  4078:         my $metadata = &Apache::lonnet::metadata($self->src(), 'packages');
1.181     bowersj2 4079: 
1.239     bowersj2 4080: 	if ($partorder) {
                   4081: 	    my @parts;
                   4082: 	    for my $part (split (/,/,$partorder)) {
                   4083: 		if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
                   4084: 		    push @parts, $part;
1.241     albertel 4085: 		    $parts{$part} = 1;
1.239     bowersj2 4086: 		}
                   4087: 	    }
                   4088: 	    $self->{PARTS} = \@parts;
                   4089: 	} else {
                   4090: 	    if (!$metadata) {
                   4091: 		$self->{RESOURCE_ERROR} = 1;
                   4092: 		$self->{PARTS} = [];
                   4093: 		$self->{PART_TYPE} = {};
                   4094: 		return;
                   4095: 	    }
1.394     raeburn  4096: 	    foreach my $entry (split(/\,/,$metadata)) {
                   4097: 		if ($entry =~ /^(?:part|Task)_(.*)$/) {
1.239     bowersj2 4098: 		    my $part = $1;
                   4099: 		    # This floods the logs if it blows up
                   4100: 		    if (defined($parts{$part})) {
1.241     albertel 4101: 			&Apache::lonnet::logthis("$part multiply defined in metadata for " . $self->symb());
                   4102: 		    }
1.239     bowersj2 4103: 		    
                   4104: 		    # check to see if part is turned off.
                   4105: 		    
                   4106: 		    if (!Apache::loncommon::check_if_partid_hidden($part, $self->symb())) {
                   4107: 			$parts{$part} = 1;
                   4108: 		    }
                   4109: 		}
                   4110: 	    }
                   4111: 	    my @sortedParts = sort keys %parts;
                   4112: 	    $self->{PARTS} = \@sortedParts;
1.51      bowersj2 4113:         }
1.82      bowersj2 4114:         
1.187     bowersj2 4115: 
1.284     matthew  4116:         # These hashes probably do not need names that end with "Hash"....
1.187     bowersj2 4117:         my %responseIdHash;
                   4118:         my %responseTypeHash;
                   4119: 
1.189     bowersj2 4120: 
                   4121:         # Init the responseIdHash
1.394     raeburn  4122:         foreach my $part (@{$self->{PARTS}}) {
                   4123:             $responseIdHash{$part} = [];
1.189     bowersj2 4124:         }
                   4125: 
1.187     bowersj2 4126:         # Now, the unfortunate thing about this is that parts, part name, and
1.239     bowersj2 4127:         # response id are delimited by underscores, but both the part
1.187     bowersj2 4128:         # name and response id can themselves have underscores in them.
                   4129:         # So we have to use our knowlege of part names to figure out 
                   4130:         # where the part names begin and end, and even then, it is possible
                   4131:         # to construct ambiguous situations.
1.379     albertel 4132:         foreach my $data (split /,/, $metadata) {
                   4133:             if ($data =~ /^([a-zA-Z]+)response_(.*)/
                   4134: 		|| $data =~ /^(Task)_(.*)/) {
1.187     bowersj2 4135:                 my $responseType = $1;
                   4136:                 my $partStuff = $2;
                   4137:                 my $partIdSoFar = '';
                   4138:                 my @partChunks = split /_/, $partStuff;
                   4139:                 my $i = 0;
                   4140:                 for ($i = 0; $i < scalar(@partChunks); $i++) {
                   4141:                     if ($partIdSoFar) { $partIdSoFar .= '_'; }
                   4142:                     $partIdSoFar .= $partChunks[$i];
                   4143:                     if ($parts{$partIdSoFar}) {
                   4144:                         my @otherChunks = @partChunks[$i+1..$#partChunks];
                   4145:                         my $responseId = join('_', @otherChunks);
1.379     albertel 4146: 			if ($self->is_task()) {
                   4147: 			    push(@{$responseIdHash{$partIdSoFar}},
                   4148: 				 $partIdSoFar);
                   4149: 			} else {
                   4150: 			    push(@{$responseIdHash{$partIdSoFar}},
                   4151: 				 $responseId);
                   4152: 			}
                   4153:                         push(@{$responseTypeHash{$partIdSoFar}},
                   4154: 			     $responseType);
1.187     bowersj2 4155:                     }
                   4156:                 }
                   4157:             }
                   4158:         }
1.264     albertel 4159: 	my $resorder = &Apache::lonnet::metadata($self->src(),'responseorder');
1.284     matthew  4160:         #
                   4161:         # Reorder the arrays in the %responseIdHash and %responseTypeHash
1.264     albertel 4162: 	if ($resorder) {
                   4163: 	    my @resorder=split(/,/,$resorder);
                   4164: 	    foreach my $part (keys(%responseIdHash)) {
1.286     albertel 4165: 		my $i=0;
                   4166: 		my %resids = map { ($_,$i++) } @{ $responseIdHash{$part} };
1.264     albertel 4167: 		my @neworder;
                   4168: 		foreach my $possibleid (@resorder) {
                   4169: 		    if (exists($resids{$possibleid})) {
1.286     albertel 4170: 			push(@neworder,$resids{$possibleid});
1.264     albertel 4171: 		    }
                   4172: 		}
1.286     albertel 4173: 		my @ids;
                   4174: 		my @type;
                   4175: 		foreach my $element (@neworder) {
                   4176: 		    push (@ids,$responseIdHash{$part}->[$element]);
                   4177: 		    push (@type,$responseTypeHash{$part}->[$element]);
                   4178: 		}
                   4179: 		$responseIdHash{$part}=\@ids;
                   4180: 		$responseTypeHash{$part}=\@type;
1.264     albertel 4181: 	    }
                   4182: 	}
1.187     bowersj2 4183:         $self->{RESPONSE_IDS} = \%responseIdHash;
                   4184:         $self->{RESPONSE_TYPES} = \%responseTypeHash;
1.154     bowersj2 4185:     }
                   4186: 
1.51      bowersj2 4187:     return;
                   4188: }
                   4189: 
                   4190: =pod
                   4191: 
                   4192: =head2 Resource Status
                   4193: 
1.174     albertel 4194: Problem resources have status information, reflecting their various
                   4195: dates and completion statuses.
1.51      bowersj2 4196: 
1.174     albertel 4197: There are two aspects to the status: the date-related information and
                   4198: the completion information.
1.51      bowersj2 4199: 
1.174     albertel 4200: Idiomatic usage of these two methods would probably look something
                   4201: like
1.51      bowersj2 4202: 
1.394     raeburn  4203:  foreach my $part ($resource->parts()) {
                   4204:     my $dateStatus = $resource->getDateStatus($part);
                   4205:     my $completionStatus = $resource->getCompletionStatus($part);
1.51      bowersj2 4206: 
1.70      bowersj2 4207:     or
                   4208: 
1.394     raeburn  4209:     my $status = $resource->status($part);
1.70      bowersj2 4210: 
1.51      bowersj2 4211:     ... use it here ...
                   4212:  }
                   4213: 
1.174     albertel 4214: Which you use depends on exactly what you are looking for. The
                   4215: status() function has been optimized for the nav maps display and may
                   4216: not precisely match what you need elsewhere.
1.101     bowersj2 4217: 
1.174     albertel 4218: The symbolic constants shown below can be accessed through the
                   4219: resource object: C<$res->OPEN>.
1.101     bowersj2 4220: 
1.51      bowersj2 4221: =over 4
                   4222: 
1.174     albertel 4223: =item * B<getDateStatus>($part):
                   4224: 
                   4225: ($part defaults to 0). A convenience function that returns a symbolic
                   4226: constant telling you about the date status of the part. The possible
                   4227: return values are:
1.51      bowersj2 4228: 
                   4229: =back
                   4230: 
                   4231: B<Date Codes>
                   4232: 
                   4233: =over 4
                   4234: 
1.174     albertel 4235: =item * B<OPEN_LATER>:
                   4236: 
                   4237: The problem will be opened later.
                   4238: 
                   4239: =item * B<OPEN>:
                   4240: 
                   4241: Open and not yet due.
                   4242: 
1.51      bowersj2 4243: 
1.174     albertel 4244: =item * B<PAST_DUE_ANSWER_LATER>:
1.51      bowersj2 4245: 
1.174     albertel 4246: The due date has passed, but the answer date has not yet arrived.
1.59      bowersj2 4247: 
1.174     albertel 4248: =item * B<PAST_DUE_NO_ANSWER>:
1.54      bowersj2 4249: 
1.174     albertel 4250: The due date has passed and there is no answer opening date set.
1.51      bowersj2 4251: 
1.174     albertel 4252: =item * B<ANSWER_OPEN>:
1.51      bowersj2 4253: 
1.174     albertel 4254: The answer date is here.
                   4255: 
                   4256: =item * B<NETWORK_FAILURE>:
                   4257: 
                   4258: The information is unknown due to network failure.
1.51      bowersj2 4259: 
                   4260: =back
                   4261: 
                   4262: =cut
                   4263: 
                   4264: # Apparently the compiler optimizes these into constants automatically
1.54      bowersj2 4265: sub OPEN_LATER             { return 0; }
                   4266: sub OPEN                   { return 1; }
                   4267: sub PAST_DUE_NO_ANSWER     { return 2; }
                   4268: sub PAST_DUE_ANSWER_LATER  { return 3; }
                   4269: sub ANSWER_OPEN            { return 4; }
                   4270: sub NOTHING_SET            { return 5; } 
                   4271: sub NETWORK_FAILURE        { return 100; }
                   4272: 
                   4273: # getDateStatus gets the date status for a given problem part. 
                   4274: # Because answer date, due date, and open date are fully independent
                   4275: # (i.e., it is perfectly possible to *only* have an answer date), 
                   4276: # we have to completely cover the 3x3 maxtrix of (answer, due, open) x
                   4277: # (past, future, none given). This function handles this with a decision
                   4278: # tree. Read the comments to follow the decision tree.
1.51      bowersj2 4279: 
                   4280: sub getDateStatus {
                   4281:     my $self = shift;
                   4282:     my $part = shift;
                   4283:     $part = "0" if (!defined($part));
1.54      bowersj2 4284: 
                   4285:     # Always return network failure if there was one.
1.51      bowersj2 4286:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   4287: 
                   4288:     my $now = time();
                   4289: 
1.53      bowersj2 4290:     my $open = $self->opendate($part);
                   4291:     my $due = $self->duedate($part);
                   4292:     my $answer = $self->answerdate($part);
                   4293: 
                   4294:     if (!$open && !$due && !$answer) {
                   4295:         # no data on the problem at all
                   4296:         # should this be the same as "open later"? think multipart.
                   4297:         return $self->NOTHING_SET;
                   4298:     }
1.59      bowersj2 4299:     if (!$open || $now < $open) {return $self->OPEN_LATER}
                   4300:     if (!$due || $now < $due) {return $self->OPEN}
                   4301:     if ($answer && $now < $answer) {return $self->PAST_DUE_ANSWER_LATER}
                   4302:     if ($answer) { return $self->ANSWER_OPEN; }
1.54      bowersj2 4303:     return PAST_DUE_NO_ANSWER;
1.51      bowersj2 4304: }
                   4305: 
                   4306: =pod
                   4307: 
                   4308: B<>
                   4309: 
                   4310: =over 4
                   4311: 
1.174     albertel 4312: =item * B<getCompletionStatus>($part):
1.51      bowersj2 4313: 
1.174     albertel 4314: ($part defaults to 0.) A convenience function that returns a symbolic
                   4315: constant telling you about the completion status of the part, with the
                   4316: following possible results:
                   4317: 
                   4318: =back
1.51      bowersj2 4319: 
                   4320: B<Completion Codes>
                   4321: 
                   4322: =over 4
                   4323: 
1.174     albertel 4324: =item * B<NOT_ATTEMPTED>:
                   4325: 
                   4326: Has not been attempted at all.
                   4327: 
                   4328: =item * B<INCORRECT>:
                   4329: 
                   4330: Attempted, but wrong by student.
1.51      bowersj2 4331: 
1.174     albertel 4332: =item * B<INCORRECT_BY_OVERRIDE>:
1.51      bowersj2 4333: 
1.174     albertel 4334: Attempted, but wrong by instructor override.
1.51      bowersj2 4335: 
1.174     albertel 4336: =item * B<CORRECT>:
1.51      bowersj2 4337: 
1.174     albertel 4338: Correct or correct by instructor.
1.51      bowersj2 4339: 
1.174     albertel 4340: =item * B<CORRECT_BY_OVERRIDE>:
1.51      bowersj2 4341: 
1.174     albertel 4342: Correct by instructor override.
1.51      bowersj2 4343: 
1.174     albertel 4344: =item * B<EXCUSED>:
                   4345: 
                   4346: Excused. Not yet implemented.
                   4347: 
                   4348: =item * B<NETWORK_FAILURE>:
                   4349: 
                   4350: Information not available due to network failure.
                   4351: 
                   4352: =item * B<ATTEMPTED>:
                   4353: 
                   4354: Attempted, and not yet graded.
1.69      bowersj2 4355: 
1.51      bowersj2 4356: =back
                   4357: 
                   4358: =cut
                   4359: 
1.53      bowersj2 4360: sub NOT_ATTEMPTED         { return 10; }
                   4361: sub INCORRECT             { return 11; }
                   4362: sub INCORRECT_BY_OVERRIDE { return 12; }
                   4363: sub CORRECT               { return 13; }
                   4364: sub CORRECT_BY_OVERRIDE   { return 14; }
                   4365: sub EXCUSED               { return 15; }
1.69      bowersj2 4366: sub ATTEMPTED             { return 16; }
1.51      bowersj2 4367: 
                   4368: sub getCompletionStatus {
                   4369:     my $self = shift;
1.332     albertel 4370:     my $part = shift;
1.51      bowersj2 4371:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   4372: 
1.332     albertel 4373:     my $status = $self->queryRestoreHash('solved', $part);
1.51      bowersj2 4374: 
1.251     www      4375:     # Left as separate if statements in case we ever do more with this
1.51      bowersj2 4376:     if ($status eq 'correct_by_student') {return $self->CORRECT;}
1.288     albertel 4377:     if ($status eq 'correct_by_scantron') {return $self->CORRECT;}
1.332     albertel 4378:     if ($status eq 'correct_by_override') {
                   4379: 	return $self->CORRECT_BY_OVERRIDE;
                   4380:     }
1.51      bowersj2 4381:     if ($status eq 'incorrect_attempted') {return $self->INCORRECT; }
                   4382:     if ($status eq 'incorrect_by_override') {return $self->INCORRECT_BY_OVERRIDE; }
                   4383:     if ($status eq 'excused') {return $self->EXCUSED; }
1.69      bowersj2 4384:     if ($status eq 'ungraded_attempted') {return $self->ATTEMPTED; }
1.51      bowersj2 4385:     return $self->NOT_ATTEMPTED;
1.108     bowersj2 4386: }
                   4387: 
                   4388: sub queryRestoreHash {
                   4389:     my $self = shift;
                   4390:     my $hashentry = shift;
                   4391:     my $part = shift;
1.185     bowersj2 4392:     $part = "0" if (!defined($part) || $part eq '');
1.108     bowersj2 4393:     return $self->NETWORK_FAILURE if ($self->{NAV_MAP}->{NETWORK_FAILURE});
                   4394: 
                   4395:     $self->getReturnHash();
                   4396: 
                   4397:     return $self->{RETURN_HASH}->{'resource.'.$part.'.'.$hashentry};
1.51      bowersj2 4398: }
                   4399: 
                   4400: =pod
                   4401: 
                   4402: B<Composite Status>
                   4403: 
1.174     albertel 4404: Along with directly returning the date or completion status, the
                   4405: resource object includes a convenience function B<status>() that will
                   4406: combine the two status tidbits into one composite status that can
1.191     bowersj2 4407: represent the status of the resource as a whole. This method represents
                   4408: the concept of the thing we want to display to the user on the nav maps
                   4409: screen, which is a combination of completion and open status. The precise logic is
1.174     albertel 4410: documented in the comments of the status method. The following results
                   4411: may be returned, all available as methods on the resource object
1.185     bowersj2 4412: ($res->NETWORK_FAILURE): In addition to the return values that match
                   4413: the date or completion status, this function can return "ANSWER_SUBMITTED"
                   4414: if that problemstatus parameter value is set to No, suppressing the
                   4415: incorrect/correct feedback.
1.51      bowersj2 4416: 
                   4417: =over 4
                   4418: 
1.174     albertel 4419: =item * B<NETWORK_FAILURE>:
                   4420: 
                   4421: The network has failed and the information is not available.
1.51      bowersj2 4422: 
1.174     albertel 4423: =item * B<NOTHING_SET>:
1.53      bowersj2 4424: 
1.174     albertel 4425: No dates have been set for this problem (part) at all. (Because only
                   4426: certain parts of a multi-part problem may be assigned, this can not be
                   4427: collapsed into "open later", as we do not know a given part will EVER
                   4428: be opened. For single part, this is the same as "OPEN_LATER".)
1.51      bowersj2 4429: 
1.174     albertel 4430: =item * B<CORRECT>:
1.51      bowersj2 4431: 
1.174     albertel 4432: For any reason at all, the part is considered correct.
1.54      bowersj2 4433: 
1.174     albertel 4434: =item * B<EXCUSED>:
1.51      bowersj2 4435: 
1.174     albertel 4436: For any reason at all, the problem is excused.
1.51      bowersj2 4437: 
1.174     albertel 4438: =item * B<PAST_DUE_NO_ANSWER>:
1.51      bowersj2 4439: 
1.174     albertel 4440: The problem is past due, not considered correct, and no answer date is
                   4441: set.
1.51      bowersj2 4442: 
1.174     albertel 4443: =item * B<PAST_DUE_ANSWER_LATER>:
1.51      bowersj2 4444: 
1.174     albertel 4445: The problem is past due, not considered correct, and an answer date in
                   4446: the future is set.
1.51      bowersj2 4447: 
1.174     albertel 4448: =item * B<ANSWER_OPEN>:
                   4449: 
                   4450: The problem is past due, not correct, and the answer is now available.
                   4451: 
                   4452: =item * B<OPEN_LATER>:
                   4453: 
                   4454: The problem is not yet open.
                   4455: 
                   4456: =item * B<TRIES_LEFT>:
                   4457: 
                   4458: The problem is open, has been tried, is not correct, but there are
                   4459: tries left.
                   4460: 
                   4461: =item * B<INCORRECT>:
                   4462: 
                   4463: The problem is open, and all tries have been used without getting the
                   4464: correct answer.
                   4465: 
                   4466: =item * B<OPEN>:
                   4467: 
                   4468: The item is open and not yet tried.
                   4469: 
                   4470: =item * B<ATTEMPTED>:
                   4471: 
                   4472: The problem has been attempted.
1.69      bowersj2 4473: 
1.185     bowersj2 4474: =item * B<ANSWER_SUBMITTED>:
                   4475: 
                   4476: An answer has been submitted, but the student should not see it.
                   4477: 
1.51      bowersj2 4478: =back
                   4479: 
                   4480: =cut
                   4481: 
1.185     bowersj2 4482: sub TRIES_LEFT       { return 20; }
                   4483: sub ANSWER_SUBMITTED { return 21; }
1.332     albertel 4484: sub PARTIALLY_CORRECT{ return 22; }
1.51      bowersj2 4485: 
                   4486: sub status {
                   4487:     my $self = shift;
                   4488:     my $part = shift;
                   4489:     if (!defined($part)) { $part = "0"; }
                   4490:     my $completionStatus = $self->getCompletionStatus($part);
                   4491:     my $dateStatus = $self->getDateStatus($part);
                   4492: 
                   4493:     # What we have is a two-dimensional matrix with 4 entries on one
                   4494:     # dimension and 5 entries on the other, which we want to colorize,
1.54      bowersj2 4495:     # plus network failure and "no date data at all".
1.51      bowersj2 4496: 
1.222     bowersj2 4497:     #if ($self->{RESOURCE_ERROR}) { return NETWORK_FAILURE; }
1.53      bowersj2 4498:     if ($completionStatus == NETWORK_FAILURE) { return NETWORK_FAILURE; }
1.51      bowersj2 4499: 
1.236     bowersj2 4500:     my $suppressFeedback = $self->problemstatus($part) eq 'no';
                   4501:     # If there's an answer date and we're past it, don't
                   4502:     # suppress the feedback; student should know
1.330     albertel 4503:     if ($self->duedate($part) && $self->duedate($part) < time() &&
                   4504: 	$self->answerdate($part) && $self->answerdate($part) < time()) {
1.236     bowersj2 4505: 	$suppressFeedback = 0;
                   4506:     }
1.185     bowersj2 4507: 
1.51      bowersj2 4508:     # There are a few whole rows we can dispose of:
1.53      bowersj2 4509:     if ($completionStatus == CORRECT ||
                   4510:         $completionStatus == CORRECT_BY_OVERRIDE ) {
1.332     albertel 4511: 	if ( $suppressFeedback ) { return ANSWER_SUBMITTED }
                   4512: 	my $awarded=$self->awarded($part);
                   4513: 	if ($awarded < 1 && $awarded > 0) {
                   4514:             return PARTIALLY_CORRECT;
                   4515: 	} elsif ($awarded<1) {
                   4516: 	    return INCORRECT;
                   4517: 	}
                   4518: 	return CORRECT; 
1.69      bowersj2 4519:     }
                   4520: 
1.343     albertel 4521:     # If it's WRONG... and not open
                   4522:     if ( ($completionStatus == INCORRECT || 
                   4523: 	  $completionStatus == INCORRECT_BY_OVERRIDE)
                   4524: 	 && (!$self->opendate($part) ||  $self->opendate($part) > time()) ) {
                   4525: 	return INCORRECT;
                   4526:     }
                   4527: 
1.69      bowersj2 4528:     if ($completionStatus == ATTEMPTED) {
                   4529:         return ATTEMPTED;
1.53      bowersj2 4530:     }
                   4531: 
                   4532:     # If it's EXCUSED, then return that no matter what
                   4533:     if ($completionStatus == EXCUSED) {
                   4534:         return EXCUSED; 
1.51      bowersj2 4535:     }
                   4536: 
1.53      bowersj2 4537:     if ($dateStatus == NOTHING_SET) {
                   4538:         return NOTHING_SET;
1.51      bowersj2 4539:     }
                   4540: 
1.69      bowersj2 4541:     # Now we're down to a 4 (incorrect, incorrect_override, not_attempted)
                   4542:     # by 4 matrix (date statuses).
1.51      bowersj2 4543: 
1.54      bowersj2 4544:     if ($dateStatus == PAST_DUE_ANSWER_LATER ||
1.59      bowersj2 4545:         $dateStatus == PAST_DUE_NO_ANSWER ) {
1.272     albertel 4546:         return $suppressFeedback ? ANSWER_SUBMITTED : $dateStatus; 
1.51      bowersj2 4547:     }
                   4548: 
1.53      bowersj2 4549:     if ($dateStatus == ANSWER_OPEN) {
                   4550:         return ANSWER_OPEN;
1.51      bowersj2 4551:     }
                   4552: 
                   4553:     # Now: (incorrect, incorrect_override, not_attempted) x 
                   4554:     # (open_later), (open)
                   4555:     
1.53      bowersj2 4556:     if ($dateStatus == OPEN_LATER) {
                   4557:         return OPEN_LATER;
1.51      bowersj2 4558:     }
                   4559: 
                   4560:     # If it's WRONG...
1.53      bowersj2 4561:     if ($completionStatus == INCORRECT || $completionStatus == INCORRECT_BY_OVERRIDE) {
1.51      bowersj2 4562:         # and there are TRIES LEFT:
1.79      bowersj2 4563:         if ($self->tries($part) < $self->maxtries($part) || !$self->maxtries($part)) {
1.222     bowersj2 4564:             return $suppressFeedback ? ANSWER_SUBMITTED : TRIES_LEFT;
1.51      bowersj2 4565:         }
1.185     bowersj2 4566:         return $suppressFeedback ? ANSWER_SUBMITTED : INCORRECT; # otherwise, return orange; student can't fix this
1.51      bowersj2 4567:     }
                   4568: 
                   4569:     # Otherwise, it's untried and open
1.53      bowersj2 4570:     return OPEN; 
1.224     bowersj2 4571: }
                   4572: 
                   4573: sub CLOSED { return 23; }
                   4574: sub ERROR { return 24; }
                   4575: 
                   4576: =pod
                   4577: 
                   4578: B<Simple Status>
                   4579: 
                   4580: Convenience method B<simpleStatus> provides a "simple status" for the resource.
                   4581: "Simple status" corresponds to "which icon is shown on the
                   4582: Navmaps". There are six "simple" statuses:
                   4583: 
                   4584: =over 4
                   4585: 
                   4586: =item * B<CLOSED>: The problem is currently closed. (No icon shown.)
                   4587: 
                   4588: =item * B<OPEN>: The problem is open and unattempted.
                   4589: 
                   4590: =item * B<CORRECT>: The problem is correct for any reason.
                   4591: 
                   4592: =item * B<INCORRECT>: The problem is incorrect and can still be
                   4593: completed successfully.
                   4594: 
                   4595: =item * B<ATTEMPTED>: The problem has been attempted, but the student
                   4596: does not know if they are correct. (The ellipsis icon.)
                   4597: 
                   4598: =item * B<ERROR>: There is an error retrieving information about this
                   4599: problem.
                   4600: 
                   4601: =back
                   4602: 
                   4603: =cut
                   4604: 
                   4605: # This hash maps the composite status to this simple status, and
                   4606: # can be used directly, if you like
                   4607: my %compositeToSimple = 
                   4608:     (
                   4609:       NETWORK_FAILURE()       => ERROR,
                   4610:       NOTHING_SET()           => CLOSED,
                   4611:       CORRECT()               => CORRECT,
1.332     albertel 4612:       PARTIALLY_CORRECT()     => PARTIALLY_CORRECT,
1.224     bowersj2 4613:       EXCUSED()               => CORRECT,
                   4614:       PAST_DUE_NO_ANSWER()    => INCORRECT,
                   4615:       PAST_DUE_ANSWER_LATER() => INCORRECT,
                   4616:       ANSWER_OPEN()           => INCORRECT,
                   4617:       OPEN_LATER()            => CLOSED,
                   4618:       TRIES_LEFT()            => OPEN,
                   4619:       INCORRECT()             => INCORRECT,
                   4620:       OPEN()                  => OPEN,
                   4621:       ATTEMPTED()             => ATTEMPTED,
                   4622:       ANSWER_SUBMITTED()      => ATTEMPTED
                   4623:      );
                   4624: 
                   4625: sub simpleStatus {
                   4626:     my $self = shift;
                   4627:     my $part = shift;
                   4628:     my $status = $self->status($part);
                   4629:     return $compositeToSimple{$status};
1.225     bowersj2 4630: }
                   4631: 
                   4632: =pod
                   4633: 
                   4634: B<simpleStatusCount> will return an array reference containing, in
                   4635: this order, the number of OPEN, CLOSED, CORRECT, INCORRECT, ATTEMPTED,
                   4636: and ERROR parts the given problem has.
                   4637: 
                   4638: =cut
                   4639:     
                   4640: # This maps the status to the slot we want to increment
                   4641: my %statusToSlotMap = 
                   4642:     (
                   4643:      OPEN()      => 0,
                   4644:      CLOSED()    => 1,
                   4645:      CORRECT()   => 2,
                   4646:      INCORRECT() => 3,
                   4647:      ATTEMPTED() => 4,
                   4648:      ERROR()     => 5
                   4649:      );
                   4650: 
                   4651: sub statusToSlot { return $statusToSlotMap{shift()}; }
                   4652: 
                   4653: sub simpleStatusCount {
                   4654:     my $self = shift;
                   4655: 
                   4656:     my @counts = (0, 0, 0, 0, 0, 0, 0);
                   4657:     foreach my $part (@{$self->parts()}) {
                   4658: 	$counts[$statusToSlotMap{$self->simpleStatus($part)}]++;
                   4659:     }
                   4660: 
                   4661:     return \@counts;
1.191     bowersj2 4662: }
                   4663: 
                   4664: =pod
                   4665: 
                   4666: B<Completable>
                   4667: 
                   4668: The completable method represents the concept of I<whether the student can
                   4669: currently do the problem>. If the student can do the problem, which means
                   4670: that it is open, there are tries left, and if the problem is manually graded
                   4671: or the grade is suppressed via problemstatus, the student has not tried it
                   4672: yet, then the method returns 1. Otherwise, it returns 0, to indicate that 
                   4673: either the student has tried it and there is no feedback, or that for
                   4674: some reason it is no longer completable (not open yet, successfully completed,
                   4675: out of tries, etc.). As an example, this is used as the filter for the
                   4676: "Uncompleted Homework" option for the nav maps.
                   4677: 
                   4678: If this does not quite meet your needs, do not fiddle with it (unless you are
                   4679: fixing it to better match the student's conception of "completable" because
                   4680: it's broken somehow)... make a new method.
                   4681: 
                   4682: =cut
                   4683: 
                   4684: sub completable {
                   4685:     my $self = shift;
                   4686:     if (!$self->is_problem()) { return 0; }
                   4687:     my $partCount = $self->countParts();
                   4688: 
                   4689:     foreach my $part (@{$self->parts()}) {
                   4690:         if ($part eq '0' && $partCount != 1) { next; }
                   4691:         my $status = $self->status($part);
                   4692:         # "If any of the parts are open, or have tries left (implies open),
                   4693:         # and it is not "attempted" (manually graded problem), it is
                   4694:         # not "complete"
1.216     bowersj2 4695: 	if ($self->getCompletionStatus($part) == ATTEMPTED() ||
                   4696: 	    $status == ANSWER_SUBMITTED() ) {
                   4697: 	    # did this part already, as well as we can
                   4698: 	    next;
                   4699: 	}
                   4700: 	if ($status == OPEN() || $status == TRIES_LEFT()) {
                   4701: 	    return 1;
                   4702: 	}
1.191     bowersj2 4703:     }
                   4704:         
                   4705:     # If all the parts were complete, so was this problem.
1.216     bowersj2 4706:     return 0;
1.51      bowersj2 4707: }
                   4708: 
                   4709: =pod
                   4710: 
                   4711: =head2 Resource/Nav Map Navigation
                   4712: 
                   4713: =over 4
                   4714: 
1.174     albertel 4715: =item * B<getNext>():
                   4716: 
                   4717: Retreive an array of the possible next resources after this
                   4718: one. Always returns an array, even in the one- or zero-element case.
                   4719: 
                   4720: =item * B<getPrevious>():
1.85      bowersj2 4721: 
1.174     albertel 4722: Retreive an array of the possible previous resources from this
                   4723: one. Always returns an array, even in the one- or zero-element case.
1.51      bowersj2 4724: 
                   4725: =cut
                   4726: 
                   4727: sub getNext {
                   4728:     my $self = shift;
                   4729:     my @branches;
                   4730:     my $to = $self->to();
1.85      bowersj2 4731:     foreach my $branch ( split(/,/, $to) ) {
1.51      bowersj2 4732:         my $choice = $self->{NAV_MAP}->getById($branch);
1.342     albertel 4733:         #if (!$choice->condition()) { next; }
1.51      bowersj2 4734:         my $next = $choice->goesto();
                   4735:         $next = $self->{NAV_MAP}->getById($next);
                   4736: 
1.131     bowersj2 4737:         push @branches, $next;
1.85      bowersj2 4738:     }
                   4739:     return \@branches;
                   4740: }
                   4741: 
                   4742: sub getPrevious {
                   4743:     my $self = shift;
                   4744:     my @branches;
                   4745:     my $from = $self->from();
                   4746:     foreach my $branch ( split /,/, $from) {
                   4747:         my $choice = $self->{NAV_MAP}->getById($branch);
                   4748:         my $prev = $choice->comesfrom();
                   4749:         $prev = $self->{NAV_MAP}->getById($prev);
                   4750: 
1.131     bowersj2 4751:         push @branches, $prev;
1.51      bowersj2 4752:     }
                   4753:     return \@branches;
1.131     bowersj2 4754: }
                   4755: 
                   4756: sub browsePriv {
                   4757:     my $self = shift;
                   4758:     if (defined($self->{BROWSE_PRIV})) {
                   4759:         return $self->{BROWSE_PRIV};
                   4760:     }
                   4761: 
1.311     albertel 4762:     $self->{BROWSE_PRIV} = &Apache::lonnet::allowed('bre',$self->src(),
                   4763: 						    $self->symb());
1.51      bowersj2 4764: }
                   4765: 
                   4766: =pod
1.2       www      4767: 
1.51      bowersj2 4768: =back
1.2       www      4769: 
1.51      bowersj2 4770: =cut
1.2       www      4771: 
1.51      bowersj2 4772: 1;
1.2       www      4773: 
1.51      bowersj2 4774: __END__
1.2       www      4775: 
                   4776: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>
500 Internal Server Error

Internal Server Error

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

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

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