File:  [LON-CAPA] / loncom / interface / loncourserespicker.pm
Revision 1.2: download - view: text, annotated - select for diffs
Mon May 7 02:12:47 2012 UTC (12 years, 1 month ago) by raeburn
Branches: MAIN
CVS tags: HEAD
- Content Editor interface.
  - "Tools" sub-tab removed, and contents split between:
    (a) "Undo Delete" and "History" sub-tabs when Content Editor main tab is active.
    (b) Additional main tab "Content Utilities"
       - contains Administration and Export sub-menus.
  - Main tabs displayed for items from "Content Utilities".
  - Dump Content to Authoring Space (Export sub-menu) detects if switchserver
    needed. Pop-up to confirm box if only one possible destination; pop-up
    window to choose which, if more than one.
  - Wording changes: Construction Space -> Authoring Space; Documents -> Content
                     Community Resources -> Collaboration (to avoid conflict with
                     "Community" as term used for one type of course container.

    1: # The LearningOnline Network
    2: #
    3: # $Id: loncourserespicker.pm,v 1.2 2012/05/07 02:12:47 raeburn Exp $
    4: #
    5: # Copyright Michigan State University Board of Trustees
    6: #
    7: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    8: #
    9: # LON-CAPA is free software; you can redistribute it and/or modify
   10: # it under the terms of the GNU General Public License as published by
   11: # the Free Software Foundation; either version 2 of the License, or
   12: # (at your option) any later version.
   13: #
   14: # LON-CAPA is distributed in the hope that it will be useful,
   15: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   16: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   17: # GNU General Public License for more details.
   18: #
   19: # You should have received a copy of the GNU General Public License
   20: # along with LON-CAPA; if not, write to the Free Software
   21: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   22: #
   23: # /home/httpd/html/adm/gpl.txt
   24: #
   25: # http://www.lon-capa.org/
   26: #
   27: 
   28: =pod
   29: 
   30: =head1 NAME
   31: 
   32: loncourserespicker - Utilities to choose folders and resources in a course.
   33: 
   34: =head1 SYNOPSIS
   35: 
   36: loncourserespicker provides an interface for selecting which folders and/or
   37: resources are to be either:
   38: 
   39: (a) exported to an IMS Content Package
   40: (b) subject to access blocking for the duriation of an exam/quiz.  
   41: 
   42: =head1 DESCRIPTION
   43: 
   44: This module provides routines to generate a hierarchical display of folders
   45: and resources in a course which can be selected for specific actions.
   46: 
   47: The choice of items is copied back to the main window from which the pop-up
   48: window used to display the Course Contents was opened.
   49: 
   50: =head1 OVERVIEW
   51: 
   52: The main subroutine: &create_picker() will display the hierarchy of folders,
   53: sub-folders, and resources in the Main Course Documents area.  Items can be
   54: selected using checkboxes, and/or a "Check All" button.  Selection of a folder
   55: causes the contents of the folder to also be selected automatically. The
   56: propagation of check status is recursive into sub-folders.  Likewise, if an
   57: item deep in a nested set of folders and sub-folders is unchecked, the 
   58: uncheck will propagate up through the hierarchy causing any folders at
   59: a higher level to become unchecked.
   60: 
   61: There is a submit button, which will be named differently according to the 
   62: content in which resource/folder selection is being made.
   63: 
   64: The two contexts currently supported are: IMS export and selection of
   65: content to be subject to access restructions for the duration of an
   66: exam.  
   67: 
   68: =head1 INTERNAL SUBROUTINES
   69: 
   70: =item &create_picker()
   71: 
   72: Created HTML mark up to display contents of course with checkboxes to
   73: select items.  Checking a folder causes recursive checking of items
   74: within the folder. Unchecking a resource causing unchecking of folders
   75: containing the item back up to the top level.
   76: 
   77: Inputs: 7.
   78:    - $navmap  -- Reference to LON-CAPA navmap object 
   79:                 (encapsulates information about resources in the course). 
   80: 
   81:    - $context -- Context in which course resource selection is being made.
   82:                  Currently imsexport and examblock are supported.
   83: 
   84:    - $formname  -- Name of the form in the window from which the pop-up
   85:                    used to select course items was launched. 
   86: 
   87:    - $crstype  -- Course or Community
   88: 
   89:    - $blockedmaps -- Reference to hash of previously selected maps
   90:                      (e.g., for a live exam block).   
   91: 
   92:    - $blockedresources  -- Reference to hash of resources selected
   93:                            previously (e.g., for an exam block).  
   94: 
   95:    - $block  -- An internal ID (integer) used to track which exam
   96:                 block currently being configured.
   97: 
   98: 
   99: Output: $output is the HTML mark-up for display/selection of content
  100:         items in the pop-up window.
  101: 
  102: =item &respicker_javascript()
  103: 
  104: Creates javascript functions for checking/unchecking all items, and
  105: for recursive checking triggered by checking a folder, or recursive
  106: (upeards) unchecking of an item within a folder. 
  107: 
  108: Inputs: 7. 
  109:    - $startcount -- Starting offset of form element numbering for items  
  110: 
  111:    - $numcount -- Total numer of folders and resources in course.
  112: 
  113:    - $context -- Context in which resources are being displayed
  114:                  (imsexport or examblock). 
  115: 
  116:    - $formname --  Name of form.
  117: 
  118:    - $children -- Reference to hash of items contained within a folder. 
  119: 
  120:    - $hierarchy -- Reference to hierarchy of folders containing an item.
  121: 
  122:    - $checked_maps -- Reference to array of folders currently checked.
  123: 
  124: Output: 1. Javascript (witthin <script></script> tags.
  125: 
  126: 
  127: =item &get_navmap_object() 
  128: 
  129: Instantiates a navmaps object, and generates an error message if
  130: no object instantiated.
  131: 
  132: Inputs: 2.
  133:    - $crstype -- Container type: Course or Community
  134: 
  135:    - $context -- Context: imsexport or examblock
  136: 
  137: =over
  138: 
  139: =back
  140: 
  141: =cut
  142: 
  143: 
  144: package Apache::loncourserespicker;
  145: 
  146: use strict;
  147: use Apache::lonnet;
  148: use Apache::loncommon;
  149: use Apache::lonhtmlcommon;
  150: use Apache::lonnavmaps;
  151: use Apache::londocs;
  152: use Apache::lonlocal;
  153: use LONCAPA qw(:DEFAULT :match);
  154: 
  155: sub create_picker {
  156:     my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block) = @_;
  157:     return unless (ref($navmap));
  158:     my ($it,$output,$numdisc,%maps,%resources,%discussiontime,%currmaps,%currresources);
  159:     $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
  160:     if (ref($blockedmaps) eq 'HASH') {
  161:         %currmaps = %{$blockedmaps};
  162:     }
  163:     if (ref($blockedresources) eq 'HASH') {
  164:         %currresources = %{$blockedresources};
  165:     }
  166:     my @checked_maps;
  167:     my $curRes;
  168:     my $numprobs = 0;
  169:     my $depth = 0;
  170:     my $count = 0;
  171:     my $boards = 0;
  172:     my $startcount = 1;
  173:     my %parent = ();
  174:     my %children = ();
  175:     my %hierarchy = ();
  176:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
  177:     my $whitespace = 
  178:         '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />';
  179: 
  180:     my $onsubmit;
  181:     if ($context eq 'examblock') {
  182:         my $maps_elem = 'docs_maps_'.$block;
  183:         my $res_elem = 'docs_resources_'.$block;
  184:         $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"';
  185:     }
  186:     my $display =
  187:        '<form name="'.$formname.'" action="" method="post"'.$onsubmit.'>'."\n".
  188:        '<p>';
  189:     if ($context eq 'imsexport') {
  190:         $display .= &mt('Choose which items you wish to export from your '.
  191:                         $crstype.'.');
  192:         $startcount = 5;
  193:     } elsif ($context eq 'examblock') {
  194:         $display .= &mt('Items in '.lc($crstype).' for which access will be blocked.');
  195:     }
  196:     $display .= '</p>';
  197:     if ($context eq 'imsexport') {
  198:         $display .= '<div class="LC_columnSection">'."\n".
  199:                     '<fieldset>'.
  200:                     '<legend>'.&mt('Content items').'</legend>'."\n";
  201:     }
  202:     $display .= 
  203:         '<input type="button" value="'.&mt('check all').'" '.
  204:         'onclick="javascript:checkAll(document.'.$formname.'.archive)" />'.
  205:         '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
  206:         ' onclick="javascript:uncheckAll(document.'.$formname.'.archive)" />';
  207:     if ($context eq 'imsexport') {
  208:         $display .= '</fieldset>';
  209:         %discussiontime =
  210:             &Apache::lonnet::dump('discussiontimes',
  211:                                   $env{'course.'.$env{'request.course.id'}.'.domain'},
  212:                                   $env{'course.'.$env{'request.course.id'}.'.num'});
  213:         $numdisc = keys(%discussiontime);
  214:         if ($numdisc > 0) {
  215:             $display .= 
  216:                 '<fieldset>'.
  217:                 '<legend>'.&mt('Discussion posts').'</legend>'.
  218:                 '<input type="button" value="'.&mt('check all').'"'.
  219:                 ' onclick="javascript:checkAll(document.'.$formname.'.discussion)" />'.
  220:                 '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
  221:                 ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'.
  222:                 '</fieldset>';
  223:         }
  224:         $display .= '</div>';      
  225:     }
  226:     my $curRes;
  227:     my $lastcontainer = $startcount;
  228:     $display .= &Apache::loncommon::start_data_table()
  229:                .&Apache::loncommon::start_data_table_header_row();
  230:     if ($context eq 'imsexport') {
  231:         $display .= '<th>'.&mt('Export content item?').'</th>';
  232:         if ($numdisc > 0) {
  233:             $display .= '<th>'.&mt('Export discussion posts?').'</th>';
  234:         }
  235:     } elsif ($context eq 'examblock') {
  236:         $display .= '<th>'.&mt('Access blocked?').'</th>';
  237:     }
  238:     $display .= &Apache::loncommon::end_data_table_header_row();
  239:     while ($curRes = $it->next()) {
  240:         if (ref($curRes)) {
  241:              $count ++;
  242:         }
  243:         if ($curRes == $it->BEGIN_MAP()) {
  244:             $depth++;
  245:             $parent{$depth} = $lastcontainer;
  246:         }
  247:         if ($curRes == $it->END_MAP()) {
  248:             $depth--;
  249:             $lastcontainer = $parent{$depth};
  250:         }
  251:         if (ref($curRes)) {
  252:             my $symb = $curRes->symb();
  253:             my $ressymb = $symb;
  254:             if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) {
  255:                 unless ($ressymb =~ m|adm/wrapper/adm|) {
  256:                     $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
  257:                 }
  258:             }
  259:             my $currelem = $count+$boards+$startcount;
  260:             $display .= &Apache::loncommon::start_data_table_row().
  261:                        '<td>'."\n".
  262:                        '<input type="checkbox" name="archive" value="'.$count.'" ';
  263:             if (($curRes->is_sequence()) || ($curRes->is_page())) {
  264:                 $lastcontainer = $currelem;
  265:                 $display .= 'onclick="javascript:checkFolder(this.form,'."'$currelem'".')" ';
  266:                 my $mapurl = (&Apache::lonnet::decode_symb($symb))[2];
  267:                 if ($currmaps{$mapurl}) {
  268:                     $display .= 'checked="checked"';
  269:                     push(@checked_maps,$currelem);
  270:                 }
  271:             } else {
  272:                 if ($curRes->is_problem()) {
  273:                     $numprobs ++;
  274:                 }
  275:                 $display .= 'onclick="javascript:checkResource(this.form,'."'$currelem'".')" ';
  276:                 if ($currresources{$symb}) {
  277:                     $display .= 'checked="checked"';
  278:                 }
  279:             }
  280:             $display .= ' />'."\n";
  281:             for (my $i=0; $i<$depth; $i++) {
  282:                 $display .= "$whitespace\n";
  283:             }
  284:             my $icon = 'src="'.$location.'/unknown.gif" alt=""';
  285:             if ($curRes->is_sequence()) {
  286:                 $icon = 'src="'.$location.'/navmap.folder.open.gif" alt="'.&mt('"Folder').'"';
  287:             } elsif ($curRes->is_page()) {
  288:                 $icon = 'src="'.$location.'/navmap.page.open.gif" alt="'.&mt('Composite Page').'"';
  289:             } elsif ($curRes->is_problem()) {
  290:                 $icon = 'src="'.$location.'/problem.gif" alt="'.&mt('Problem').'"';
  291:             } elsif ($curRes->is_task()) {
  292:                 $icon = 'src="'.$location.'/task.gif" alt="'.&mt('Task').'"';
  293:             } elsif ($curRes->src ne '') {
  294:                 $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
  295:             }
  296:             $display .= '<img '.$icon.' />&nbsp;'."\n";
  297:             $children{$parent{$depth}} .= $currelem.':';
  298:             if ($context eq 'examblock') {
  299:                 if ($parent{$depth} > 1) {
  300:                     if ($hierarchy{$parent{$depth}}) {
  301:                         $hierarchy{$currelem} = $hierarchy{$parent{$depth}}.",'$parent{$depth}'";
  302:                     } else {
  303:                         $hierarchy{$currelem} = "'$parent{$depth}'";
  304:                     }
  305:                 }
  306:             }
  307:             $display .= '&nbsp;'.$curRes->title().'</td>'."\n";
  308: 
  309:             if ($context eq 'imsexport') {
  310: # Existing discussion posts?
  311:                 if ($discussiontime{$ressymb} > 0) {
  312:                     $boards ++;
  313:                     $display .= '<td align="right">'
  314:                                .'<input type="checkbox" name="discussion" value="'.$count.'" />'
  315:                                .'</td>'."\n";
  316:                 } elsif ($numdisc > 0) {
  317:                     $display .= '<td>&nbsp;</td>'."\n";
  318:                 }
  319:             }
  320:             $display .= &Apache::loncommon::end_data_table_row();
  321:         }
  322:     }
  323:     $display .= &Apache::loncommon::end_data_table();
  324:     if ($context eq 'imsexport') {
  325:         if ($numprobs > 0) {
  326:             $display .= '<p><span class="LC_nobreak">'.
  327:                         &mt('Export format for LON-CAPA problems:').
  328:                         '<label><input type="radio" name="format" value="xml" checked="checked" />'.
  329:                         '&nbsp;'.&mt('XML').'</label>'.('&nbsp;' x3).
  330:                         '<label><input type="radio" name="format" value="html" />'.
  331:                         '&nbsp;'.&mt('HTML').'</label>'.('&nbsp;' x3).
  332:                         '<label><input type="radio" name="format" value="plaintext" />'.
  333:                         '&nbsp;'.&mt('Text').'</label></span></p>';
  334:         }
  335:     }
  336:     $display .= '<p>';
  337:     if ($context eq 'imsexport') {
  338:         $display .= 
  339:            '<input type="hidden" name="finishexport" value="1" />'.
  340:            '<input type="submit" name="exportcourse" value="'.
  341:            &mt('Export').'" />';
  342:     } elsif ($context eq 'examblock') {
  343:         $display .=
  344:             '<input type="submit" name="resourceblocks" value="'.
  345:             &mt('Copy Choices to Main Window ').'" />';
  346:     }
  347:     $display .= '</p></form>';
  348:     my $numcount = $count + $boards + $startcount;
  349:     my $scripttag = 
  350:         &respicker_javascript($startcount,$numcount,$context,$formname,\%children,
  351:                               \%hierarchy,\@checked_maps);
  352:     my ($title,$crumbs,$args);
  353:     if ($context eq 'imsexport') {
  354:         $title = 'Export '.$crstype.' to IMS Package';
  355:     } elsif ($context eq 'examblock') {
  356:         $title = 'Resources with Access blocked';
  357:         $args = {'only_body'      => 1,
  358:                  'add_entries' => { onload => 'javascript:recurseFolders();' }, 
  359:                 };
  360:     }
  361:     my $output = &Apache::loncommon::start_page($title,$scripttag,$args);
  362:     if ($context eq 'imsexport') {
  363:         $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export').
  364:                    &Apache::londocs::startContentScreen('tools');
  365:     }
  366:     $output .= $display;
  367:     if ($context eq 'examblock') {
  368:         $output .= &Apache::loncommon::end_page();
  369:     } elsif ($context eq 'imsexport') {
  370:         $output .= &Apache::londocs::endContentScreen();
  371:     }
  372:     return $output;
  373: }
  374: 
  375: sub respicker_javascript {
  376:     my ($startcount,$numitems,$context,$formname,$children,$hierarchy,
  377:         $checked_maps) = @_;
  378:     return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH')
  379:                    && (ref($checked_maps) eq 'ARRAY'));
  380:     my $scripttag = <<"START";
  381: <script type="text/javascript">
  382: // <![CDATA[
  383: function checkAll(field) {
  384:     if (field.length > 0) {
  385:         for (i = 0; i < field.length; i++) {
  386:             field[i].checked = true ;
  387:         }
  388:     } else {
  389:         field.checked = true
  390:     }
  391: }
  392: 
  393: function uncheckAll(field) {
  394:     if (field.length > 0) {
  395:         for (i = 0; i < field.length; i++) {
  396:             field[i].checked = false;
  397:         }
  398:     } else {
  399:         field.checked = false;
  400:     }
  401: }
  402: 
  403: function checkFolder(form,item) {
  404:     if (form.elements[item].checked == true) {
  405:         containerCheck(form,item);
  406:     } else {
  407:         containerUncheck(form,item);
  408:     }
  409: }
  410: 
  411: function checkResource(form,item) {
  412:     if (form.elements[item].checked == false) {
  413:         containerUncheck(form,item);
  414:     }
  415: }
  416: 
  417: numitems = $numitems;
  418: var parents = new Array(numitems);
  419: var nesting = new Array(numitems);
  420: var initial = new Array();
  421: for (var i=$startcount; i<numitems; i++) {
  422:     parents[i] = new Array();
  423:     nesting[i] = new Array();
  424: }
  425: 
  426: START
  427: 
  428:     foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
  429:         my @contents = split(/:/,$children->{$container});
  430:         for (my $i=0; $i<@contents; $i ++) {
  431:             $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
  432:         }
  433:     }
  434: 
  435:     if ($context eq 'examblock') {
  436:         foreach my $item (sort { $a <=> $b } (keys(%{$hierarchy}))) {
  437:             $scripttag .= "nesting[$item] = new Array($hierarchy->{$item});\n";
  438:         }
  439:          
  440:         my @sorted_maps = sort { $a <=> $b } (@{$checked_maps});
  441:         for (my $i=0; $i<@sorted_maps; $i++) {
  442:             $scripttag .= "initial[$i] = '$sorted_maps[$i]'\n";
  443:         }
  444:         $scripttag .= <<"EXTRA";
  445: 
  446: function recurseFolders() {
  447:     if (initial.length > 0) {
  448:         for (var i=0; i<initial.length; i++) {
  449:             containerCheck(document.$formname,initial[i]);
  450:         }
  451:     }
  452:     return;
  453: }
  454: 
  455: EXTRA
  456:     }
  457: 
  458:     $scripttag .= <<"END";
  459: 
  460: function containerCheck(form,item) {
  461:     form.elements[item].checked = true;
  462:     if(Object.prototype.toString.call(parents[item]) === '[object Array]') {
  463:         if (parents[item].length > 0) {
  464:             for (var j=0; j<parents[item].length; j++) {
  465:                 containerCheck(form,parents[item][j]);
  466:             }
  467:         }
  468:     }
  469: }
  470: 
  471: function containerUncheck(form,item) {
  472:     if(Object.prototype.toString.call(nesting[item]) === '[object Array]') {
  473:         if (nesting[item].length > 0) {
  474:             for (var i=0; i<nesting[item].length; i++) {
  475:                 form.elements[nesting[item][i]].checked = false;
  476:             }
  477:         }
  478:     }
  479:     return;
  480: }
  481: 
  482: END
  483: 
  484:     if ($context eq 'examblock') {
  485:         $scripttag .= <<ENDEX;
  486: function writeToOpener(maps,resources) {
  487:     var checkedmaps = '';
  488:     var checkedresources = '';
  489:     for (var i=0; i<document.$formname.archive.length; i++) {
  490:         if (document.$formname.archive[i].checked) {
  491:             var isResource = 1;
  492:             var include = 1;
  493:             var elemnum = i+1+$startcount;
  494:             if (Object.prototype.toString.call(parents[elemnum]) === '[object Array]') {
  495:                 if (parents[elemnum].length > 0) {
  496:                     isResource = 0;
  497:                 }
  498:             }
  499:             if (isResource == 1) {
  500:                 if (nesting[elemnum].length > 0) {
  501:                     var lastelem = nesting[elemnum].length-1;
  502:                     if (document.$formname.elements[nesting[elemnum][lastelem]].checked) {
  503:                         include = 0;
  504:                     }
  505:                 }
  506:             }
  507:             if (include == 1) {
  508:                 if (isResource == 1) {
  509:                     checkedresources += document.$formname.archive[i].value+',';
  510:                 } else {
  511:                     checkedmaps += document.$formname.archive[i].value+',';
  512:                 }
  513:             }
  514:         }
  515:     }
  516:     opener.document.getElementById(maps).value = checkedmaps;
  517:     opener.document.getElementById(resources).value = checkedresources;
  518:     window.close();
  519:     return false;
  520: }
  521: 
  522: ENDEX
  523:     }
  524: 
  525:     $scripttag .= '
  526: // ]]>
  527: </script>
  528: ';
  529:     return $scripttag;
  530: }
  531: 
  532: sub get_navmap_object {
  533:     my ($crstype,$context) = @_;
  534:     my $navmap = Apache::lonnavmaps::navmap->new();
  535:     my $outcome;
  536:     if (!defined($navmap)) {
  537:         if ($context eq 'imsexport') {
  538:             $outcome = &Apache::loncommon::start_page('Export '.$crstype.' to IMS Package').
  539:                       '<h2>'.&mt('IMS Export Failed').'</h2>';
  540:         } elsif ($context eq 'examblock') {
  541:             $outcome = &Apache::loncommon::start_page('Selection of Resources for Blocking',
  542:                                                        undef,{'only_body' => 1,}).
  543:                       '<h2>'.&mt('Resource Display Failed').'</h2>';  
  544:         } 
  545:         $outcome .= '<div class="LC_error">';
  546:         if ($crstype eq 'Community') {
  547:             $outcome .= &mt('Unable to retrieve information about community contents');
  548:         } else {
  549:             $outcome .= &mt('Unable to retrieve information about course contents');
  550:         }
  551:         $outcome .= '</div>';
  552:         if ($context eq 'imsexport') {
  553:             $outcome .= '<a href="/adm/coursedocs">';
  554:             if ($crstype eq 'Community') {
  555:                 $outcome .= &mt('Return to Community Editor');
  556:             } else {
  557:                 $outcome .= &mt('Return to Course Editor');
  558:             }
  559:             $outcome .= '</a>';
  560:             &logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
  561:         } elsif ($context eq 'examblock') {
  562:             $outcome .=  '<href="javascript:window.close();">'.&mt('Close window').'</a>';         
  563:         }
  564:         return (undef,$outcome);
  565:     } else {
  566:         return ($navmap);
  567:     }
  568: }
  569: 
  570: 1;

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