File:  [LON-CAPA] / loncom / interface / loncourserespicker.pm
Revision 1.21: download - view: text, annotated - select for diffs
Thu Dec 26 17:12:36 2024 UTC (8 weeks, 1 day ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, version_2_11_6_msu, HEAD
- Use correct endpoint for loop to populate parents array.

    1: # The LearningOnline Network
    2: #
    3: # $Id: loncourserespicker.pm,v 1.21 2024/12/26 17:12:36 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 either (1) an interface for selecting which 
   37: folders and/or resources are to be selected for a specific action, one of: 
   38: 
   39: (a) export to an IMS Content Package
   40: (b) be subject to access blocking for the duration of an exam/quiz.
   41: (c) dump to an Authoring Space
   42: (d) receive shortened URLs to be used when deep-linking into a course
   43: 
   44: or (2) an interface for selecting a single folder or resource for which 
   45: existing passback credentials can be used to send scores to another Course
   46: Management System (CMS).
   47: 
   48: =head1 DESCRIPTION
   49: 
   50: This module provides routines to generate a hierarchical display of folders
   51: and resources in a course which can be selected for specific actions. In 
   52: all except one use case all items in the course are shown. The case where
   53: only a filtered list is shown is passback of scores, and filtering limits
   54: folders and resources to those items for which passback credentials exist,
   55: (and their parent folders).
   56: 
   57: When the display is shown in a pop-up window, The choice of items will be
   58: copied back to the main window from which the pop-up window used to display
   59: the Course Contents was opened.
   60: 
   61: =head1 OVERVIEW
   62: 
   63: In the cases where multiple items may be selected the main subroutine:
   64: &create_picker() will display the hierarchy of folders, sub-folders, and
   65: resources in the Main Content area. Items can be selected using checkboxes,
   66: and/or a "Check All" button.  Selection of a folder causes the contents of
   67: the folder to also be selected automatically. The propagation of check
   68: status is recursive into sub-folders.  Likewise, if an item deep in a nested
   69: set of folders  and sub-folders is unchecked, the uncheck will propagate up
   70: through the hierarchy causing any folders at a higher level to become
   71: unchecked.
   72: 
   73: In the case where only a single item may be selected the main subroutine:
   74: &create_picker() will display the hierarchy of folders and sub-folders for
   75: only those items for which passback credentials exist,
   76: 
   77: There is a submit button, which will be named differently according to the 
   78: context in which resource/folder selection is being made.
   79: 
   80: The five contexts currently supported are: IMS export, selection of
   81: content to be subject to access restructions for the duration of an
   82: exam, selection of items for dumping to an Authoring Space, display or 
   83: creation of shortened URLs for deep-linking, and selection of a single
   84: item for apssback of grades to another CMS.
   85: 
   86: =head1 INTERNAL SUBROUTINES
   87: 
   88: =item &create_picker()
   89: 
   90: In the cases where multiple items may be selected ...
   91: 
   92: Creates HTML markup to display contents of course with checkboxes to
   93: select items.  Checking a folder causes recursive checking of items
   94: within the folder. Unchecking a resource causing unchecking of folders
   95: containing the item back up to the top level.
   96: 
   97: In the case where only a single item may be selected ...
   98: 
   99: Creates HTML markup to display filtered contents of course with radio
  100: buttons to select an item.
  101: 
  102: Inputs: 13.
  103:    - $navmap  -- Reference to LON-CAPA navmap object 
  104:                 (encapsulates information about resources in the course). 
  105: 
  106:    - $context -- Context in which course resource selection is being made.
  107:                  Currently imsexport, examblock, dumpdocs, and shorturls
  108:                  are supported.
  109: 
  110:    - $formname  -- Name of the form in the window from which the pop-up
  111:                    used to select course items was launched. 
  112: 
  113:    - $crstype  -- Course or Community
  114: 
  115:    - $blockedmaps -- Reference to hash of previously selected maps
  116:                      (e.g., for a live exam block).   
  117: 
  118:    - $blockedresources  -- Reference to hash of resources selected
  119:                            previously (e.g., for an exam block).  
  120: 
  121:    - $block  -- An internal ID (integer) used to track which exam
  122:                 block currently being configured.
  123: 
  124:    - $preamble -- HTML form elements used to select Authoring Space
  125:                   if more than one available, and also set name of 'Folder 
  126:                   in Authoring Space' where content will be dumped, when
  127:                   context is 'dumpdocs'.
  128: 
  129:    - $numhome -- number of possible Authoring Spaces where content could
  130:                  be dumped when context is 'dumpdocs'.
  131: 
  132:    - $uploadedfiles -- Reference to hash: keys are paths to files in
  133:                        /home/httpd/lonUsers/$cdom/$1/$2/$3/$cnum/userfiles.
  134: 
  135:    - $tiny -- Reference to hash: keys are symbs of course items for which
  136:               shortened URLs have already been created.
  137: 
  138:    - $passback -- Reference to hash: keys are symbs of course items for
  139:                   which passback credentials exist. For each symb the
  140:                   hash value is itself a hash of deeplink launch items
  141:                   for that symb with inner hash key set to: 
  142:                   $linkuri\0$linkprotector\0$scope, and corresponding 
  143:                   value of 1.
  144: 
  145:    - $readonly -- if true, no "check all" or "uncheck all" buttons will
  146:                   be displayed, and checkboxes will be disabled, if this 
  147:                   is for an exam block or for shortened URL creation,
  148:                   and radio buttons will be disabled, if this is for
  149:                   passback of scores to another CMS, 
  150: 
  151: 
  152: Output: $output is the HTML mark-up for display/selection of content
  153:         items, either in a pop-up window, or in the main window, 
  154:         depending on context.
  155: 
  156: =item &respicker_javascript()
  157: 
  158: Creates javascript functions for checking/unchecking all items, and
  159: for recursive checking triggered by checking a folder, or recursive
  160: (upwards) unchecking of an item within a folder. 
  161: 
  162: Inputs: 9.
  163:    - $startcount -- Starting offset of form element numbering for items  
  164: 
  165:    - $numcount -- Total number of folders and resources in course.
  166: 
  167:    - $context -- Context in which resources are being displayed
  168:                  (imsexport, examblock, dumpdocs, shorturls, or
  169:                   crsauthored). 
  170: 
  171:    - $formname --  Name of form.
  172: 
  173:    - $children -- Reference to hash of items contained within a folder. 
  174: 
  175:    - $hierarchy -- Reference to hierarchy of folders containing an item.
  176: 
  177:    - $checked_maps -- Reference to array of folders currently checked.
  178: 
  179:    - $numhome -- Number of possible Authoring Spaces on this server 
  180:                  (context is dumpdocs or crsauthored).
  181:  
  182:    - $chkname -- Name of checkboxes used to indicate selection of folder
  183:                  or resource.
  184: 
  185: Output: 1. Javascript (within <script></script> tags.
  186: 
  187: 
  188: =item &get_navmap_object() 
  189: 
  190: Instantiates a navmaps object, and generates an error message if
  191: no object instantiated.
  192: 
  193: Inputs: 2.
  194:    - $crstype -- Container type: Course or Community
  195: 
  196:    - $context -- Context: imsexport, examblock, dumpdocs, shorturls
  197:                           or passback.
  198: 
  199: 
  200: =item &clean()
  201:  
  202: Takes incoming title and replaces non-alphanumeric characters with underscore,
  203: so title can be used as suggested file name (with appended extension) for file
  204: copied from course to Authoring Space.
  205: 
  206: 
  207: =item &enumerate_course_contents()
  208: 
  209: Create hashes of maps (for folders/pages) and symbs (for resources) in
  210: a course, where keys are numbers (starting with 1) and values are
  211: map url, or symb, for an iteration through the course, as seen by
  212: a Course Coordinator. Used to generate numerical IDs to facilitate
  213: (a) storage of lists of maps or resources to be blocked during an exam,
  214: (b) processing selected form elements during dumping of selected course
  215:     content to Authoring Space.
  216: (c) processing of checked checkboxes for creation of shortened URLs for
  217:     deep-linking to course content.
  218: 
  219: Inputs: 7 
  220: 
  221:       $navmap - navmaps object
  222: 
  223:       $map_url - reference to hash to contain URLs of maps in course
  224: 
  225:       $resource_symb - reference to hash to contain symbs for
  226:                        resources in course
  227: 
  228:       $title_ref - reference to hash containing titles for items in
  229:                    course
  230: 
  231:       $context - examblock, dumpdocs or shorturls
  232: 
  233:       $cdom - course's domain
  234: 
  235:       $cnum - courseID
  236: 
  237: Outputs: None
  238: 
  239: Side Effects: $map_url and $resource_symb hashrefs are populated.
  240: 
  241: 
  242: =over
  243: 
  244: =back
  245: 
  246: =cut
  247: 
  248: 
  249: package Apache::loncourserespicker;
  250: 
  251: use strict;
  252: use Apache::lonnet;
  253: use Apache::loncommon;
  254: use Apache::lonhtmlcommon;
  255: use Apache::lonnavmaps;
  256: use Apache::londocs;
  257: use Apache::lonlocal;
  258: use LONCAPA qw(:DEFAULT :match);
  259: 
  260: sub create_picker {
  261:     my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block,$preamble,
  262:         $numhome,$uploadedfiles,$tiny,$passback,$readonly) = @_;
  263:     return unless (ref($navmap));
  264:     my ($it,$output,$numdisc,%discussiontime,%currmaps,%currresources,%files,
  265:         %shorturls,%shownmaps,%shownsymbs,%recursed,%retrieved,%pb,$chkname);
  266:     $chkname = 'archive';
  267:     if ($context eq 'shorturls') {
  268:         $chkname = 'addtiny';
  269:     } elsif ($context eq 'passback') {
  270:         $chkname = 'passback';
  271:     }
  272:     $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
  273:     if (ref($blockedmaps) eq 'HASH') {
  274:         %currmaps = %{$blockedmaps};
  275:     }
  276:     if (ref($blockedresources) eq 'HASH') {
  277:         %currresources = %{$blockedresources};
  278:     } elsif (ref($uploadedfiles) eq 'HASH') {
  279:         %files = %{$uploadedfiles};
  280:     } elsif (ref($tiny) eq 'HASH') {
  281:         %shorturls = %{$tiny}; 
  282:     } elsif ($context eq 'passback') {
  283:         if (ref($passback) eq 'HASH') {
  284:             %pb = %{$passback};
  285:             foreach my $symb (keys(%pb)) {
  286:                 my ($map,$id,$url) = &Apache::lonnet::decode_symb($symb);
  287:                 my @recurseup;
  288:                 if ($url =~ /\.(page|sequence)$/) {
  289:                     @recurseup = $navmap->recurseup_maps($url);
  290:                     $shownmaps{&Apache::lonnet::clutter($url)} = 1;
  291:                     if (ref($pb{$symb}) eq 'HASH') {
  292:                         foreach my $entry (keys(%{$pb{$symb}})) {
  293:                             my $scope = (split("\0",$entry))[-1];
  294:                             if (($scope eq 'map') || ($scope eq 'rec')) {
  295:                                 my @contents;
  296:                                 if ($scope eq 'map') {
  297:                                     unless ($retrieved{$url} || $recursed{$url}) {
  298:                                         @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },0);
  299:                                         $retrieved{$url} = 1;
  300:                                     }
  301:                                 } elsif ($scope eq 'rec') {
  302:                                     unless ($recursed{$url}) {
  303:                                         @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },1,0,1);
  304:                                         my @subfolders = $navmap->retrieveResources($url,sub { $_[0]->is_map() },1,0,1);
  305:                                         if (@subfolders) {
  306:                                             map { $shownmaps{$_->src()} = 1; } @subfolders;
  307:                                         }
  308:                                         $recursed{$url} = 1;
  309:                                     }
  310:                                 }
  311:                                 if (@contents) {
  312:                                     map { $shownsymbs{$_->symb()} = 1; } @contents;
  313:                                 }
  314:                             }
  315:                         }
  316:                     }
  317:                 } else {
  318:                     @recurseup = $navmap->recurseup_maps($map);
  319:                     $shownmaps{&Apache::lonnet::clutter($map)} = 1;
  320:                     $shownsymbs{$symb} = 1;
  321:                 }
  322:                 if (@recurseup) {
  323:                     map { $shownmaps{&Apache::lonnet::clutter($_)} = 1; } @recurseup;
  324:                 }
  325:             }
  326:         }
  327:     }
  328:     my @checked_maps;
  329:     my $curRes;
  330:     my $numprobs = 0;
  331:     my $depth = 0;
  332:     my $count = 0;
  333:     my $boards = 0;
  334:     my $startcount = 1;
  335:     my %parent = ();
  336:     my %children = ();
  337:     my %hierarchy = ();
  338:     my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
  339:     my $whitespace =
  340:         '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />';
  341: 
  342:     my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
  343:     my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
  344:     my $crsprefix = &propath($cdom,$cnum).'/userfiles/';
  345: 
  346:     my ($info,$display,$onsubmit,$togglebuttons,$disabled,$action);
  347:     if ($context eq 'examblock') {
  348:         my $maps_elem = 'docs_maps_'.$block;
  349:         my $res_elem = 'docs_resources_'.$block;
  350:         $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"';
  351:         $info = &mt('Items in '.lc($crstype).' for which access will be blocked.');
  352:         if ($readonly) {
  353:             $disabled = ' disabled="disabled"';
  354:         }
  355:     }
  356:     if ($context eq 'dumpdocs') {
  357:         $info = '<span class="LC_fontsize_medium">'.
  358:                 &mt('Choose the uploaded course items and templated pages/problems to be copied to Authoring Space.').
  359:                 '</span><br /><br />';
  360:         $startcount = 3 + $numhome;
  361:         $onsubmit = ' onsubmit="return checkUnique(document.'.$formname.',document.'.$formname.'.'.$chkname.');"';
  362:     } elsif ($context eq 'shorturls') {
  363:         $info = '<span class="LC_fontsize_medium">'.
  364:                 &mt('Choose the resource(s) and/or folder(s) from Main Content for which shortened URL(s) are needed.').
  365:                 '</span><br /><br />';
  366:     } elsif ($context eq 'imsexport') {
  367:         $info = &mt('Choose which items you wish to export from your '.$crstype.'.');
  368:         $startcount = 5;
  369:     } elsif ($context eq 'passback') {
  370:         $action = '/adm/grades';
  371:         $info = '<p>'.
  372:                 &mt('Select link-protected launch item for which scores should be sent to launcher CMS, then push Next [_1].',
  373:                     '&rarr;').
  374:                 '</p><br />';
  375:         if ($readonly) {
  376:             $disabled = ' disabled="disabled"';
  377:         }
  378:     }
  379:     if ($disabled) {
  380:         $togglebuttons = '<br />';
  381:     } elsif ($context ne 'passback') {
  382:         $togglebuttons = '<input type="button" value="'.&mt('check all').'" '.
  383:                          'onclick="javascript:checkAll(document.'.$formname.'.'.$chkname.')" />'.
  384:                          '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
  385:                          ' onclick="javascript:uncheckAll(document.'.$formname.'.'.$chkname.')" />';
  386:     }
  387:     $display = '<form name="'.$formname.'" action="'.$action.'" method="post"'.$onsubmit.'>'."\n";
  388:     if ($context eq 'imsexport') {
  389:         $display .= $info.
  390:                     '<div class="LC_columnSection">'."\n".
  391:                     '<fieldset>'.
  392:                     '<legend>'.&mt('Content items').'</legend>'."\n".
  393:                     $togglebuttons.
  394:                     '</fieldset>';
  395:         %discussiontime = &Apache::lonnet::dump('discussiontimes',$cdom,$cnum);
  396:         $numdisc = keys(%discussiontime);
  397:         if ($numdisc > 0) {
  398:             $display .= 
  399:                 '<fieldset>'.
  400:                 '<legend>'.&mt('Discussion posts').'</legend>'.
  401:                 '<input type="button" value="'.&mt('check all').'"'.
  402:                 ' onclick="javascript:checkAll(document.'.$formname.'.discussion)" />'.
  403:                 '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
  404:                 ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'.
  405:                 '</fieldset>';
  406:         }
  407:         $display .= '</div>';
  408:     } elsif (($context eq 'examblock') || ($context eq 'shorturls') || ($context eq 'passback')) {
  409:         $display .= $info.$togglebuttons;
  410:     } elsif ($context eq 'dumpdocs') {
  411:         $display .= $preamble.
  412:                     '<div class="LC_left_float">'.
  413:                     '<fieldset>'.
  414:                     '<legend>'.&mt('Content to copy').('&nbsp;'x4).$togglebuttons.'</legend>'.
  415:                     $info;
  416:     }
  417:     my $lastcontainer = $startcount;
  418:     $display .= &Apache::loncommon::start_data_table()
  419:                .&Apache::loncommon::start_data_table_header_row();
  420:     if ($context eq 'imsexport') {
  421:         $display .= '<th>'.&mt('Export content item?').'</th>';
  422:         if ($numdisc > 0) {
  423:             $display .= '<th>'.&mt('Export discussion posts?').'</th>';
  424:         }
  425:     } elsif ($context eq 'examblock') {
  426:         $display .= '<th>'.&mt('Access blocked?').'</th>';
  427:     } elsif ($context eq 'dumpdocs') {
  428:         $display .= '<th>'.&mt('Copy?').'</th>'.
  429:                     '<th>'.&mt("Title in $crstype").'</th>'.
  430:                     '<th>'.&mt('Internal Identifier').'</th>'.
  431:                     '<th>'.&mt('Save as ...').'</th>';
  432:     } elsif ($context eq 'shorturls') {
  433:         $display .= '<th colspan="2">'.&mt('Tiny URL').'</th>'.
  434:                     '<th>'.&mt("Title in $crstype").'</th>';
  435:     } elsif ($context eq 'passback') {
  436:         $display .= '<th>'.&mt("Title in $crstype").'</th>'.
  437:                     '<th>'.&mt('Tiny URL Deep-link').'</th>'.
  438:                     '<th>'.&mt('Launcher').'</th>'.
  439:                     '<th  style="padding-left: 6px; padding-right: 6px">'.&mt('Score Type').'</th>'.
  440:                     '<th style="padding-left: 6px; padding-right: 6px">'.&mt('Select').'</th>';
  441:     }
  442:     $display .= &Apache::loncommon::end_data_table_header_row();
  443:     while ($curRes = $it->next()) {
  444:         if ($curRes == $it->BEGIN_MAP()) {
  445:             $depth++;
  446:             $parent{$depth} = $lastcontainer;
  447:         }
  448:         if ($curRes == $it->END_MAP()) {
  449:             $depth--;
  450:             $lastcontainer = $parent{$depth};
  451:         }
  452:         if (ref($curRes)) {
  453:             my $symb = $curRes->symb();
  454:             my $ressymb = $symb;
  455:             if ($context eq 'dumpdocs') {
  456:                 next unless (($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(docs|supplemental|simplepage)}) ||
  457:                              ($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(default|supplemental)_\d+\.(sequence|page)}) ||
  458:                              ($curRes->src() eq '/res/lib/templates/simpleproblem.problem') ||
  459:                              ($curRes->src() =~ m{^/adm/$match_domain/$match_username/\d+/smppg}));
  460:             } elsif ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) {
  461:                 unless ($ressymb =~ m|adm/wrapper/adm|) {
  462:                     $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
  463:                 }
  464:             }
  465:             $count ++;
  466:             my ($currelem,$mapurl,$is_map,$showitem);
  467:             if ($context eq 'imsexport') {
  468:                 $currelem = $count+$boards+$startcount;
  469:             } else {
  470:                 $currelem = $count+$startcount;
  471:             }
  472:             if (($curRes->is_sequence()) || ($curRes->is_page())) {
  473:                 $lastcontainer = $currelem;
  474:                 $mapurl = (&Apache::lonnet::decode_symb($symb))[2];
  475:                 $is_map = 1;
  476:             }
  477:             if ($context eq 'passback') {
  478:                 if (($curRes->is_sequence()) || ($curRes->is_page())) {
  479:                     next unless ($shownmaps{$curRes->src});
  480:                 } else {
  481:                     next unless ($shownsymbs{$symb});
  482:                 }
  483:             } else {
  484:                 $display .= &Apache::loncommon::start_data_table_row()."\n";
  485:             }
  486:             if ($context eq 'shorturls') {
  487:                 if ($shorturls{$symb}) {
  488:                     $display .= '<td>&nbsp;</td><td align="right"><b>'."/tiny/$cdom/$shorturls{$symb}".'</b></td>'."\n";
  489:                 } else {
  490:                     $display .= '<td align="left"><label><input type="checkbox" name="'.$chkname.'" '.
  491:                                 'value="'.$count.'"'.$disabled.' />'.&mt('Add').'</label></td>'.
  492:                                 '<td>&nbsp;</td>'."\n";
  493:                 }
  494:             } elsif ($context ne 'passback') {
  495:                 $display .= '<td><input type="checkbox" name="'.$chkname.'" value="'.$count.'" ';
  496:                 if ($is_map) {
  497:                     $display .= 'onclick="javascript:checkFolder(document.'.$formname.','."'$currelem'".')" ';
  498:                     if ($currmaps{$mapurl}) {
  499:                         $display .= 'checked="checked"';
  500:                         push(@checked_maps,$currelem);
  501:                     }
  502:                 } else {
  503:                     if ($curRes->is_problem()) {
  504:                         $numprobs ++;
  505:                     }
  506:                     $display .= 'onclick="javascript:checkResource(document.'.$formname.','."'$currelem'".')" ';
  507:                     if ($currresources{$symb}) {
  508:                         $display .= 'checked="checked"';
  509:                     }
  510:                 }
  511:                 $display .= $disabled.' />'."\n";
  512:             }
  513:             if ($context eq 'dumpdocs') {
  514:                 $display .= '</td><td valign="top">';
  515:             } elsif ($context eq 'shorturls') {
  516:                 $display .= '<td valign="top">';
  517:             }
  518:             for (my $i=0; $i<$depth; $i++) {
  519:                 $showitem .= "$whitespace\n";
  520:             }
  521:             my $icon = 'src="'.$location.'/unknown.gif" alt=""';
  522:             if ($curRes->is_sequence()) {
  523:                 $icon = 'src="'.$location.'/navmap.folder.open.gif" alt="'.&mt('Folder').'"';
  524:             } elsif ($curRes->is_page()) {
  525:                 $icon = 'src="'.$location.'/navmap.page.open.gif" alt="'.&mt('Composite Page').'"';
  526:             } elsif ($curRes->is_problem()) {
  527:                 $icon = 'src="'.$location.'/problem.gif" alt="'.&mt('Problem').'"';
  528:             } elsif ($curRes->is_task()) {
  529:                 $icon = 'src="'.$location.'/task.gif" alt="'.&mt('Task').'"';
  530:             } elsif ($curRes->src ne '') {
  531:                 $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
  532:             }
  533:             $showitem .= '<img '.$icon.' />&nbsp;'."\n";
  534:             $children{$parent{$depth}} .= $currelem.':';
  535:             if ($context eq 'examblock') {
  536:                 if ($parent{$depth} > 1) {
  537:                     if ($hierarchy{$parent{$depth}}) {
  538:                         $hierarchy{$currelem} = $hierarchy{$parent{$depth}}.",'$parent{$depth}'";
  539:                     } else {
  540:                         $hierarchy{$currelem} = "'$parent{$depth}'";
  541:                     }
  542:                 }
  543:             }
  544:             $showitem .= '&nbsp;'.$curRes->title().$whitespace;
  545:             if ($context eq 'passback') {
  546:                 if ((exists($pb{$symb})) && (ref($pb{$symb}) eq 'HASH')) {
  547:                     my $numlinks = scalar(keys(%{$pb{$symb}}));
  548:                     my $count = 0;
  549:                     foreach my $launcher (sort(keys(%{$pb{$symb}}))) {
  550:                         if ($count == 0) {
  551:                             $display .= &Apache::loncommon::start_data_table_row()."\n";
  552:                             if ($numlinks > 1) {
  553:                                 $display .= '<td rowspan="'.$numlinks.'">'.$showitem.'</td>';
  554:                             } else {
  555:                                 $display .= '<td style="vertical-align: baseline">'.$showitem.'</td>';
  556:                             }
  557:                         } else {
  558:                             $display .= &Apache::loncommon::end_data_table_row().
  559:                                         &Apache::loncommon::start_data_table_row()."\n";
  560:                         }
  561:                         my ($linkuri,$linkprotector,$scope) = split("\0",$launcher);
  562:                         my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
  563:                         my ($appname,$setter);
  564:                         if ($ltitype eq 'c') {
  565:                             my %lti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
  566:                             if (ref($lti{$ltinum}) eq 'HASH') {
  567:                                 $appname = $lti{$ltinum}{'name'};
  568:                                 if ($appname) {
  569:                                     $setter = ' (defined in course)';
  570:                                 }
  571:                             }
  572:                         } elsif ($ltitype eq 'd') {
  573:                             my %lti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
  574:                             if (ref($lti{$ltinum}) eq 'HASH') {
  575:                                 $appname = $lti{$ltinum}{'name'};
  576:                                 if ($appname) {
  577:                                     $setter = ' (defined in domain)';
  578:                                 }
  579:                             }
  580:                         }
  581:                         my $shownscope;
  582:                         if ($scope eq 'res') {
  583:                             $shownscope = &mt('Resource');
  584:                         } elsif ($scope eq 'map') {
  585:                             $shownscope = &mt('Folder');
  586:                         }  elsif ($scope eq 'rec') {
  587:                             $shownscope = &mt('Folder + sub-folders');
  588:                         }
  589:                         $display .= '<td style="vertical-align: baseline"><span style="font-weight: bold;">'.$linkuri.'</span></td>'."\n".
  590:                                     '<td style="vertical-align: baseline; padding-left: 6px; padding-right: 6px">'.$appname.$setter.'</td>'."\n".
  591:                                     '<td style="vertical-align: baseline"><span style="font-style: italic;">'.$shownscope.'</span></td>'."\n".
  592:                                     '<td align="right" style="vertical-align: baseline"><input type="radio" name="'.$chkname.'" '.
  593:                                     'value="'.&escape($launcher).'"'.$disabled.' /></td>'."\n";
  594:                         $count ++;
  595:                     }
  596:                 } else {
  597:                     $display .= &Apache::loncommon::start_data_table_row()."\n".
  598:                                 '<td colspan="5">'.$showitem.'</td>';
  599:                 }
  600:             } else {
  601:                 $display .= $showitem.'</td>'."\n";
  602:             }
  603:             if ($context eq 'imsexport') {
  604: # Existing discussion posts?
  605:                 if ($discussiontime{$ressymb} > 0) {
  606:                     $boards ++;
  607:                     $display .= '<td align="right">'
  608:                                .'<input type="checkbox" name="discussion" value="'.$count.'" />'
  609:                                .'</td>'."\n";
  610:                 } elsif ($numdisc > 0) {
  611:                     $display .= '<td>&nbsp;</td>'."\n";
  612:                 }
  613:             } elsif ($context eq 'dumpdocs') {
  614:                 my $src = $curRes->src();
  615:                 my ($filepath,$title);
  616:                 if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E}) {
  617:                     $filepath = &Apache::lonnet::filelocation('',$src);
  618:                     $filepath =~ s/\Q$crsprefix\E//;
  619:                     if ($curRes->is_map()) {
  620:                         $title = $files{$filepath};
  621:                     } else {
  622:                         $filepath =~ s{docs/}{}; 
  623:                         $title = $filepath;
  624:                         $title =~ s{^(default|\d+)/\d*/?}{};
  625:                     }
  626:                 } else {
  627:                     $title = $curRes->title();
  628:                     $title =~ s{/}{_}g;
  629:                     $title = &clean($title);
  630:                     if ($src eq '/res/lib/templates/simpleproblem.problem') {
  631:                         my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb);
  632:                         $map =~ s{^uploaded/$cdom/$cnum/}{};
  633:                         $filepath = $map.'_'.$id;
  634:                         $title .= '.problem';
  635:                     } elsif ($src =~ m{^/adm/$match_domain/$match_username/(\d+)/smppg}) {
  636:                         $filepath = 'smppage_'.$1.'.db';
  637:                         $title .= '.html';
  638:                     }
  639:                 }
  640:                 $display .= '<td>'.$filepath.'</td>'.
  641:                             '<td><input type="text" size="40" name="namefor_'.$count.'" id="namefor_'.$count.'" value="'.$title.'" /></td>'."\n";
  642:             }
  643:             $display .= &Apache::loncommon::end_data_table_row();
  644:         }
  645:     }
  646:     $display .= &Apache::loncommon::end_data_table();
  647:     if ($context eq 'imsexport') {
  648:         if ($numprobs > 0) {
  649:             $display .= '<p><span class="LC_nobreak">'.
  650:                         &mt('Export format for LON-CAPA problems:').
  651:                         '<label><input type="radio" name="format" value="xml" checked="checked" />'.
  652:                         '&nbsp;'.&mt('XML').'</label>'.('&nbsp;' x3).
  653:                         '<label><input type="radio" name="format" value="html" />'.
  654:                         '&nbsp;'.&mt('HTML').'</label>'.('&nbsp;' x3).
  655:                         '<label><input type="radio" name="format" value="plaintext" />'.
  656:                         '&nbsp;'.&mt('Text').'</label></span></p>';
  657:         }
  658:     }
  659:     my $numcount;
  660:     if ($context eq 'imsexport') {
  661:         $display .= 
  662:            '<p>'.
  663:            '<input type="hidden" name="finishexport" value="1" />'.
  664:            '<input type="submit" name="exportcourse" value="'.
  665:            &mt('Export').'" /></p>';
  666:         $numcount = $count + $boards + $startcount;
  667:     } elsif ($context eq 'examblock') {
  668:         unless ($readonly) {
  669:             $display .=
  670:                 '<p>'.
  671:                 '<input type="submit" name="resourceblocks" value="'.
  672:                 &mt('Copy Choices to Main Window').'" /></p>';
  673:         }
  674:         $numcount = $count + $startcount;
  675:     } elsif ($context eq 'dumpdocs') {
  676:         $display .= '</fieldset>'.
  677:                     '</div><div style="padding:0;clear:both;margin:0;border:0"></div>'.
  678:                     '<div>'.
  679:                     '<input type="submit" name="dumpcourse" value="'.&mt("Copy $crstype Content").'" />'.
  680:                     '</div>';
  681:         $numcount = $count + $startcount;
  682:     } elsif ($context eq 'shorturls') {
  683:         unless ($readonly) {
  684:             $display .=
  685:                 '<p>'.
  686:                 '<input type="submit" name="shorturls" value="'.
  687:                 &mt('Create Tiny URL(s)').'" /></p>';
  688:         }
  689:     } elsif ($context eq 'passback') {
  690:         unless ($readonly) {
  691:             $display .=
  692:                 '<p>'.
  693:                 '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($env{'form.symb'}).'" />'."\n".
  694:                 '<input type="hidden" name="command" value="passback" />'.
  695:                 '<input type="submit" name="picklauncher" value="'.
  696:                 &mt('Next').' &rarr;" /></p>';
  697:         }
  698:     }
  699:     $display .= '</form>';
  700:     if ($context eq 'passback') {
  701:         return $display;
  702:     }
  703:     my $scripttag =
  704:         &respicker_javascript($startcount,$numcount,$context,$formname,\%children,
  705:                               \%hierarchy,\@checked_maps,$numhome,$chkname);
  706:     if (($context eq 'dumpdocs') || ($context eq 'shorturls')) {
  707:         return $scripttag.$display; 
  708:     }
  709:     my ($title,$crumbs,$args);
  710:     if ($context eq 'imsexport') { 
  711:         $title = 'Export '.$crstype.' to IMS Package';
  712:     } elsif ($context eq 'examblock') {
  713:         $title = 'Resources with Access blocked';
  714:         $args = {'only_body'      => 1,
  715:                  'add_entries' => { onload => 'javascript:recurseFolders();' }, 
  716:                 };
  717:     }
  718:     $output = &Apache::loncommon::start_page($title,$scripttag,$args);
  719:     if ($context eq 'imsexport') {
  720:         $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export').
  721:                    &Apache::londocs::startContentScreen('tools');
  722:     } elsif ($context eq 'dumpdocs') {
  723:         $output .= &Apache::lonhtmlcommon::breadcrumbs('Copying to Authoring Space').
  724:                    &Apache::londocs::startContentScreen('tools');
  725:     }
  726:     $output .= $display;
  727:     if ($context eq 'examblock') {
  728:         $output .= &Apache::loncommon::end_page();
  729:     } elsif ($context eq 'imsexport') {
  730:         $output .= &Apache::londocs::endContentScreen();
  731:     }
  732:     return $output;
  733: }
  734: 
  735: sub respicker_javascript {
  736:     my ($startcount,$numitems,$context,$formname,$children,$hierarchy,
  737:         $checked_maps,$numhome,$chkname) = @_;
  738:     my $check_uncheck = <<"FIRST";
  739: function checkAll(field) {
  740:     if (field.length > 0) {
  741:         for (i = 0; i < field.length; i++) {
  742:             field[i].checked = true ;
  743:         }
  744:     } else {
  745:         field.checked = true
  746:     }
  747: }
  748: 
  749: function uncheckAll(field) {
  750:     if (field.length > 0) {
  751:         for (i = 0; i < field.length; i++) {
  752:             field[i].checked = false;
  753:         }
  754:     } else {
  755:         field.checked = false;
  756:     }
  757: }
  758: FIRST
  759:     if ($context eq 'shorturls') {
  760:         return <<"END";
  761: <script type="text/javascript">
  762: // <![CDATA[
  763: $check_uncheck
  764: // ]]>
  765: </script>
  766: END
  767:     }
  768:     return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH')
  769:                    && (ref($checked_maps) eq 'ARRAY'));
  770:     my ($elem,$nested,$nameforelem);
  771:     if ($context eq 'dumpdocs') {
  772:         $elem='((parseInt(item)-'.$startcount.')*2)+'.$startcount;
  773:         $nested='((parseInt(nesting[item][i])-'.$startcount.')*2)+'.$startcount;
  774:         $nameforelem=$elem+1;
  775:     } else {
  776:         $elem='parseInt(item)';
  777:         $nested='parseInt(nesting[item][i])';
  778:     }
  779:     my $scripttag = <<"START";
  780: <script type="text/javascript">
  781: // <![CDATA[
  782: $check_uncheck
  783: 
  784: function checkFolder(form,item) {
  785:     var elem = $elem;
  786:     if (form.elements[elem].checked == true) {
  787:         containerCheck(form,item);
  788:     } else {
  789:         containerUncheck(form,item);
  790:     }
  791: }
  792: 
  793: function checkResource(form,item) {
  794:     var elem = $elem;
  795:     if (form.elements[elem].checked == false) {
  796:         containerUncheck(form,item);
  797:     }
  798: }
  799: 
  800: numitems = $startcount + $numitems;
  801: var parents = new Array(numitems);
  802: var nesting = new Array(numitems);
  803: var initial = new Array();
  804: for (var i=$startcount; i<numitems; i++) {
  805:     parents[i] = new Array();
  806:     nesting[i] = new Array();
  807: }
  808: 
  809: START
  810: 
  811:     foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
  812:         my @contents = split(/:/,$children->{$container});
  813:         for (my $i=0; $i<@contents; $i ++) {
  814:             $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
  815:         }
  816:     }
  817: 
  818:     if ($context eq 'examblock') {
  819:         foreach my $item (sort { $a <=> $b } (keys(%{$hierarchy}))) {
  820:             $scripttag .= "nesting[$item] = new Array($hierarchy->{$item});\n";
  821:         }
  822:          
  823:         my @sorted_maps = sort { $a <=> $b } (@{$checked_maps});
  824:         for (my $i=0; $i<@sorted_maps; $i++) {
  825:             $scripttag .= "initial[$i] = '$sorted_maps[$i]'\n";
  826:         }
  827:         $scripttag .= <<"EXTRA";
  828: 
  829: function recurseFolders() {
  830:     if (initial.length > 0) {
  831:         for (var i=0; i<initial.length; i++) {
  832:             containerCheck(document.$formname,initial[i]);
  833:         }
  834:     }
  835:     return;
  836: }
  837: 
  838: EXTRA
  839:     } elsif ($context eq 'dumpdocs') {
  840:         my $blankmsg = &mt('An item selected has no filename set in the "Save as ..." column.');
  841:         my $dupmsg = &mt('Items selected for copying need unique filenames in the "Save as ..." column.');
  842:         my $homemsg = &mt('An Authoring Space needs to be selected.');
  843:         &js_escape(\$blankmsg);
  844:         &js_escape(\$dupmsg);
  845:         &js_escape(\$homemsg);
  846:         $scripttag .= <<"EXTRA";
  847: 
  848: function checkUnique(form,field) {
  849:     var duplicate = 0;
  850:     var blank = 0;
  851:     var numhome = '$numhome';
  852:     if (field.length > 0) {
  853:         for (i=0; i<field.length; i++) {
  854:             if (field[i].checked) {
  855:                 var item = field[i].value;
  856:                 var namefor = document.getElementById('namefor_'+item);
  857:                 if (namefor) {
  858:                     var possval = namefor.value;
  859:                     if (!possval) {
  860:                         blank = item;
  861:                         break;
  862:                     }
  863:                     for (j=i+1; j<field.length; j++) {
  864:                         if (field[j].checked) {
  865:                             var curritem = field[j].value;
  866:                             var currnamefor = document.getElementById('namefor_'+curritem);
  867:                             if (currnamefor) {
  868:                                 var currval = currnamefor.value;
  869:                                 if (currval == possval) {
  870:                                     duplicate = curritem;
  871:                                     break;
  872:                                 }
  873:                             }
  874:                         }
  875:                     }
  876:                     if (duplicate) {
  877:                         break;
  878:                     }
  879:                 }
  880:             }
  881:         }
  882:     }
  883:     if (blank) {
  884:         alert('$blankmsg');
  885:         return false;
  886:     }
  887:     if (duplicate) {
  888:         alert('$dupmsg');
  889:         return false;
  890:     }
  891:     if (numhome > 1) {
  892:         if (!form.authorspace.options[form.authorspace.selectedIndex].value) {
  893:             alert('$homemsg');
  894:             return false;
  895:         }
  896:     }
  897:     return true;
  898: }
  899: 
  900: EXTRA
  901: 
  902:     }
  903: 
  904:     $scripttag .= <<"END";
  905: 
  906: function containerCheck(form,item) {
  907:     var elem = $elem;
  908:     form.elements[elem].checked = true;
  909:     if(Object.prototype.toString.call(parents[item]) === '[object Array]') {
  910:         if (parents[item].length > 0) {
  911:             for (var j=0; j<parents[item].length; j++) {
  912:                 containerCheck(form,parents[item][j]);
  913:             }
  914:         }
  915:     }
  916: }
  917: 
  918: function containerUncheck(form,item) {
  919:     if(Object.prototype.toString.call(nesting[item]) === '[object Array]') {
  920:         if (nesting[item].length > 0) {
  921:             for (var i=0; i<nesting[item].length; i++) {
  922:                 var nested = $nested;
  923:             }
  924:         }
  925:     }
  926:     return;
  927: }
  928: 
  929: END
  930: 
  931:     if ($context eq 'examblock') {
  932:         $scripttag .= <<ENDEX;
  933: function writeToOpener(maps,resources) {
  934:     var checkedmaps = '';
  935:     var checkedresources = '';
  936:     for (var i=0; i<document.$formname.${chkname}.length; i++) {
  937:         if (document.$formname.${chkname}[i].checked) {
  938:             var isResource = 1;
  939:             var include = 1;
  940:             var elemnum = i+1+$startcount;
  941:             if (Object.prototype.toString.call(parents[elemnum]) === '[object Array]') {
  942:                 if (parents[elemnum].length > 0) {
  943:                     isResource = 0;
  944:                 }
  945:             }
  946:             if (isResource == 1) {
  947:                 if (nesting[elemnum] != null) {
  948:                     if (nesting[elemnum].length > 0) {
  949:                         var lastelem = nesting[elemnum].length-1;
  950:                         if (document.$formname.elements[nesting[elemnum][lastelem]].checked) {
  951:                             include = 0;
  952:                         }
  953:                     }
  954:                 }
  955:             }
  956:             if (include == 1) {
  957:                 if (isResource == 1) {
  958:                     checkedresources += document.$formname.${chkname}[i].value+',';
  959:                 } else {
  960:                     checkedmaps += document.$formname.${chkname}[i].value+',';
  961:                 }
  962:             }
  963:         }
  964:     }
  965:     opener.document.getElementById(maps).value = checkedmaps;
  966:     opener.document.getElementById(resources).value = checkedresources;
  967:     window.close();
  968:     return false;
  969: }
  970: 
  971: ENDEX
  972:     }
  973: 
  974:     $scripttag .= '
  975: // ]]>
  976: </script>
  977: ';
  978:     return $scripttag;
  979: }
  980: 
  981: sub get_navmap_object {
  982:     my ($crstype,$context) = @_;
  983:     my $navmap = Apache::lonnavmaps::navmap->new();
  984:     my $outcome;
  985:     if (!defined($navmap)) {
  986:         if ($context eq 'imsexport') {
  987:             $outcome = &Apache::loncommon::start_page('Export '.$crstype.' to IMS Package').
  988:                       '<h2>'.&mt('IMS Export Failed').'</h2>';
  989:         } elsif ($context eq 'examblock') {
  990:             $outcome = &Apache::loncommon::start_page('Selection of Resources for Blocking',
  991:                                                        undef,{'only_body' => 1,}).
  992:                       '<h2>'.&mt('Resource Display Failed').'</h2>';  
  993:         } elsif ($context eq 'dumpdocs') {
  994:             $outcome = '<h2>'.&mt('Copying to Authoring Space unavailable').'</h2>';
  995:         } elsif ($context eq 'shorturls') {
  996:             $outcome = '<h2>'.&mt('Display/Setting of shortened URLs unavailable').'</h2>';
  997:         }
  998:         $outcome .= '<div class="LC_error">';
  999:         if ($crstype eq 'Community') {
 1000:             $outcome .= &mt('Unable to retrieve information about community contents');
 1001:         } else {
 1002:             $outcome .= &mt('Unable to retrieve information about course contents');
 1003:         }
 1004:         $outcome .= '</div>';
 1005:         if (($context eq 'imsexport') || ($context eq 'dumpdocs') || ($context eq 'shorturls') ) {
 1006:             $outcome .= '<a href="/adm/coursedocs">';
 1007:             if ($crstype eq 'Community') {
 1008:                 $outcome .= &mt('Return to Community Editor');
 1009:             } else {
 1010:                 $outcome .= &mt('Return to Course Editor');
 1011:             }
 1012:             $outcome .= '</a>';
 1013:             if ($context eq 'imsexport') {
 1014:                 &Apache::lonnet::logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
 1015:             } elsif ($context eq 'dumpdocs') {
 1016:                 &Apache::lonnet::logthis('Copying to Authoring Space failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
 1017:             } elsif ($context eq 'shorturls') {
 1018:                 &Apache::lonnet::logthis('Displaying and/or saving URL shortcuts failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
 1019:             }
 1020:         } elsif ($context eq 'examblock') {
 1021:             $outcome .=  '<href="javascript:window.close();">'.&mt('Close window').'</a>';         
 1022:         }
 1023:         return (undef,$outcome);
 1024:     } else {
 1025:         return ($navmap);
 1026:     }
 1027: }
 1028: 
 1029: sub clean {
 1030:     my ($title)=@_;
 1031:     $title=~s/[^\w\/\!\$\%\^\*\-\_\=\+\;\:\,\\\|\`\~]+/\_/gs;
 1032:     return $title;
 1033: }
 1034: 
 1035: sub enumerate_course_contents {
 1036:     my ($navmap,$map_url,$resource_symb,$titleref,$context,$cdom,$cnum) = @_;
 1037:     if ((ref($navmap)) && (ref($map_url) eq 'HASH') &&
 1038:         (ref($resource_symb) eq 'HASH') && (ref($titleref) eq 'HASH')) {
 1039:         my $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
 1040:         my $count = 0;
 1041:         while (my $curRes = $it->next()) {
 1042:             if (ref($curRes)) {
 1043:                 my $symb = $curRes->symb();
 1044:                 my $ressymb = $symb;
 1045:                 if ($context eq 'dumpdocs') {
 1046:                     next unless (($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(docs|supplemental|simplepage)/}) ||
 1047:                                  ($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(default|supplemental)_\d+\.(sequence|page)}) ||
 1048:                                  ($curRes->src() eq '/res/lib/templates/simpleproblem.problem') ||
 1049:                                  ($curRes->src() =~ m{^/adm/$match_domain/$match_username/\d+/smppg}));
 1050:                 } elsif ($ressymb =~ m{adm/($match_domain)/($match_username)/(\d+)/bulletinboard$}) {
 1051:                     unless ($ressymb =~ m{adm/wrapper/adm}) {
 1052:                         $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.
 1053:                                    '/bulletinboard';
 1054:                     }
 1055:                 }
 1056:                 $count ++;
 1057:                 if ($context eq 'shorturls') {
 1058:                     $resource_symb->{$count} = $ressymb;
 1059:                 } else {
 1060:                     if (($curRes->is_sequence()) || ($curRes->is_page())) {
 1061:                         $map_url->{$count} = (&Apache::lonnet::decode_symb($symb))[2];
 1062:                     } else {
 1063:                         $resource_symb->{$count} = $ressymb;
 1064:                     }
 1065:                 }
 1066:                 $titleref->{$count} = $curRes->title();
 1067:             }
 1068:         }
 1069:     }
 1070:     return;
 1071: }
 1072: 
 1073: 1;

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