File:  [LON-CAPA] / loncom / interface / loncourserespicker.pm
Revision 1.20: download - view: text, annotated - select for diffs
Fri Dec 20 12:25:13 2024 UTC (5 days, 18 hours ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, HEAD
- Update documentation for respicker_javascript() subroutine.

# The LearningOnline Network
#
# $Id: loncourserespicker.pm,v 1.20 2024/12/20 12:25:13 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#

=pod

=head1 NAME

loncourserespicker - Utilities to choose folders and resources in a course.

=head1 SYNOPSIS

loncourserespicker provides either (1) an interface for selecting which 
folders and/or resources are to be selected for a specific action, one of: 

(a) export to an IMS Content Package
(b) be subject to access blocking for the duration of an exam/quiz.
(c) dump to an Authoring Space
(d) receive shortened URLs to be used when deep-linking into a course

or (2) an interface for selecting a single folder or resource for which 
existing passback credentials can be used to send scores to another Course
Management System (CMS).

=head1 DESCRIPTION

This module provides routines to generate a hierarchical display of folders
and resources in a course which can be selected for specific actions. In 
all except one use case all items in the course are shown. The case where
only a filtered list is shown is passback of scores, and filtering limits
folders and resources to those items for which passback credentials exist,
(and their parent folders).

When the display is shown in a pop-up window, The choice of items will be
copied back to the main window from which the pop-up window used to display
the Course Contents was opened.

=head1 OVERVIEW

In the cases where multiple items may be selected the main subroutine:
&create_picker() will display the hierarchy of folders, sub-folders, and
resources in the Main Content area. Items can be selected using checkboxes,
and/or a "Check All" button.  Selection of a folder causes the contents of
the folder to also be selected automatically. The propagation of check
status is recursive into sub-folders.  Likewise, if an item deep in a nested
set of folders  and sub-folders is unchecked, the uncheck will propagate up
through the hierarchy causing any folders at a higher level to become
unchecked.

In the case where only a single item may be selected the main subroutine:
&create_picker() will display the hierarchy of folders and sub-folders for
only those items for which passback credentials exist,

There is a submit button, which will be named differently according to the 
context in which resource/folder selection is being made.

The five contexts currently supported are: IMS export, selection of
content to be subject to access restructions for the duration of an
exam, selection of items for dumping to an Authoring Space, display or 
creation of shortened URLs for deep-linking, and selection of a single
item for apssback of grades to another CMS.

=head1 INTERNAL SUBROUTINES

=item &create_picker()

In the cases where multiple items may be selected ...

Creates HTML markup to display contents of course with checkboxes to
select items.  Checking a folder causes recursive checking of items
within the folder. Unchecking a resource causing unchecking of folders
containing the item back up to the top level.

In the case where only a single item may be selected ...

Creates HTML markup to display filtered contents of course with radio
buttons to select an item.

Inputs: 13.
   - $navmap  -- Reference to LON-CAPA navmap object 
                (encapsulates information about resources in the course). 

   - $context -- Context in which course resource selection is being made.
                 Currently imsexport, examblock, dumpdocs, and shorturls
                 are supported.

   - $formname  -- Name of the form in the window from which the pop-up
                   used to select course items was launched. 

   - $crstype  -- Course or Community

   - $blockedmaps -- Reference to hash of previously selected maps
                     (e.g., for a live exam block).   

   - $blockedresources  -- Reference to hash of resources selected
                           previously (e.g., for an exam block).  

   - $block  -- An internal ID (integer) used to track which exam
                block currently being configured.

   - $preamble -- HTML form elements used to select Authoring Space
                  if more than one available, and also set name of 'Folder 
                  in Authoring Space' where content will be dumped, when
                  context is 'dumpdocs'.

   - $numhome -- number of possible Authoring Spaces where content could
                 be dumped when context is 'dumpdocs'.

   - $uploadedfiles -- Reference to hash: keys are paths to files in
                       /home/httpd/lonUsers/$cdom/$1/$2/$3/$cnum/userfiles.

   - $tiny -- Reference to hash: keys are symbs of course items for which
              shortened URLs have already been created.

   - $passback -- Reference to hash: keys are symbs of course items for
                  which passback credentials exist. For each symb the
                  hash value is itself a hash of deeplink launch items
                  for that symb with inner hash key set to: 
                  $linkuri\0$linkprotector\0$scope, and corresponding 
                  value of 1.

   - $readonly -- if true, no "check all" or "uncheck all" buttons will
                  be displayed, and checkboxes will be disabled, if this 
                  is for an exam block or for shortened URL creation,
                  and radio buttons will be disabled, if this is for
                  passback of scores to another CMS, 


Output: $output is the HTML mark-up for display/selection of content
        items, either in a pop-up window, or in the main window, 
        depending on context.

=item &respicker_javascript()

Creates javascript functions for checking/unchecking all items, and
for recursive checking triggered by checking a folder, or recursive
(upwards) unchecking of an item within a folder. 

Inputs: 9.
   - $startcount -- Starting offset of form element numbering for items  

   - $numcount -- Total number of folders and resources in course.

   - $context -- Context in which resources are being displayed
                 (imsexport, examblock, dumpdocs, shorturls, or
                  crsauthored). 

   - $formname --  Name of form.

   - $children -- Reference to hash of items contained within a folder. 

   - $hierarchy -- Reference to hierarchy of folders containing an item.

   - $checked_maps -- Reference to array of folders currently checked.

   - $numhome -- Number of possible Authoring Spaces on this server 
                 (context is dumpdocs or crsauthored).
 
   - $chkname -- Name of checkboxes used to indicate selection of folder
                 or resource.

Output: 1. Javascript (within <script></script> tags.


=item &get_navmap_object() 

Instantiates a navmaps object, and generates an error message if
no object instantiated.

Inputs: 2.
   - $crstype -- Container type: Course or Community

   - $context -- Context: imsexport, examblock, dumpdocs, shorturls
                          or passback.


=item &clean()
 
Takes incoming title and replaces non-alphanumeric characters with underscore,
so title can be used as suggested file name (with appended extension) for file
copied from course to Authoring Space.


=item &enumerate_course_contents()

Create hashes of maps (for folders/pages) and symbs (for resources) in
a course, where keys are numbers (starting with 1) and values are
map url, or symb, for an iteration through the course, as seen by
a Course Coordinator. Used to generate numerical IDs to facilitate
(a) storage of lists of maps or resources to be blocked during an exam,
(b) processing selected form elements during dumping of selected course
    content to Authoring Space.
(c) processing of checked checkboxes for creation of shortened URLs for
    deep-linking to course content.

Inputs: 7 

      $navmap - navmaps object

      $map_url - reference to hash to contain URLs of maps in course

      $resource_symb - reference to hash to contain symbs for
                       resources in course

      $title_ref - reference to hash containing titles for items in
                   course

      $context - examblock, dumpdocs or shorturls

      $cdom - course's domain

      $cnum - courseID

Outputs: None

Side Effects: $map_url and $resource_symb hashrefs are populated.


=over

=back

=cut


package Apache::loncourserespicker;

use strict;
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonhtmlcommon;
use Apache::lonnavmaps;
use Apache::londocs;
use Apache::lonlocal;
use LONCAPA qw(:DEFAULT :match);

sub create_picker {
    my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block,$preamble,
        $numhome,$uploadedfiles,$tiny,$passback,$readonly) = @_;
    return unless (ref($navmap));
    my ($it,$output,$numdisc,%discussiontime,%currmaps,%currresources,%files,
        %shorturls,%shownmaps,%shownsymbs,%recursed,%retrieved,%pb,$chkname);
    $chkname = 'archive';
    if ($context eq 'shorturls') {
        $chkname = 'addtiny';
    } elsif ($context eq 'passback') {
        $chkname = 'passback';
    }
    $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
    if (ref($blockedmaps) eq 'HASH') {
        %currmaps = %{$blockedmaps};
    }
    if (ref($blockedresources) eq 'HASH') {
        %currresources = %{$blockedresources};
    } elsif (ref($uploadedfiles) eq 'HASH') {
        %files = %{$uploadedfiles};
    } elsif (ref($tiny) eq 'HASH') {
        %shorturls = %{$tiny}; 
    } elsif ($context eq 'passback') {
        if (ref($passback) eq 'HASH') {
            %pb = %{$passback};
            foreach my $symb (keys(%pb)) {
                my ($map,$id,$url) = &Apache::lonnet::decode_symb($symb);
                my @recurseup;
                if ($url =~ /\.(page|sequence)$/) {
                    @recurseup = $navmap->recurseup_maps($url);
                    $shownmaps{&Apache::lonnet::clutter($url)} = 1;
                    if (ref($pb{$symb}) eq 'HASH') {
                        foreach my $entry (keys(%{$pb{$symb}})) {
                            my $scope = (split("\0",$entry))[-1];
                            if (($scope eq 'map') || ($scope eq 'rec')) {
                                my @contents;
                                if ($scope eq 'map') {
                                    unless ($retrieved{$url} || $recursed{$url}) {
                                        @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },0);
                                        $retrieved{$url} = 1;
                                    }
                                } elsif ($scope eq 'rec') {
                                    unless ($recursed{$url}) {
                                        @contents = $navmap->retrieveResources($url,sub { $_[0]->is_gradable() },1,0,1);
                                        my @subfolders = $navmap->retrieveResources($url,sub { $_[0]->is_map() },1,0,1);
                                        if (@subfolders) {
                                            map { $shownmaps{$_->src()} = 1; } @subfolders;
                                        }
                                        $recursed{$url} = 1;
                                    }
                                }
                                if (@contents) {
                                    map { $shownsymbs{$_->symb()} = 1; } @contents;
                                }
                            }
                        }
                    }
                } else {
                    @recurseup = $navmap->recurseup_maps($map);
                    $shownmaps{&Apache::lonnet::clutter($map)} = 1;
                    $shownsymbs{$symb} = 1;
                }
                if (@recurseup) {
                    map { $shownmaps{&Apache::lonnet::clutter($_)} = 1; } @recurseup;
                }
            }
        }
    }
    my @checked_maps;
    my $curRes;
    my $numprobs = 0;
    my $depth = 0;
    my $count = 0;
    my $boards = 0;
    my $startcount = 1;
    my %parent = ();
    my %children = ();
    my %hierarchy = ();
    my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
    my $whitespace =
        '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />';

    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
    my $crsprefix = &propath($cdom,$cnum).'/userfiles/';

    my ($info,$display,$onsubmit,$togglebuttons,$disabled,$action);
    if ($context eq 'examblock') {
        my $maps_elem = 'docs_maps_'.$block;
        my $res_elem = 'docs_resources_'.$block;
        $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"';
        $info = &mt('Items in '.lc($crstype).' for which access will be blocked.');
        if ($readonly) {
            $disabled = ' disabled="disabled"';
        }
    }
    if ($context eq 'dumpdocs') {
        $info = '<span class="LC_fontsize_medium">'.
                &mt('Choose the uploaded course items and templated pages/problems to be copied to Authoring Space.').
                '</span><br /><br />';
        $startcount = 3 + $numhome;
        $onsubmit = ' onsubmit="return checkUnique(document.'.$formname.',document.'.$formname.'.'.$chkname.');"';
    } elsif ($context eq 'shorturls') {
        $info = '<span class="LC_fontsize_medium">'.
                &mt('Choose the resource(s) and/or folder(s) from Main Content for which shortened URL(s) are needed.').
                '</span><br /><br />';
    } elsif ($context eq 'imsexport') {
        $info = &mt('Choose which items you wish to export from your '.$crstype.'.');
        $startcount = 5;
    } elsif ($context eq 'passback') {
        $action = '/adm/grades';
        $info = '<p>'.
                &mt('Select link-protected launch item for which scores should be sent to launcher CMS, then push Next [_1].',
                    '&rarr;').
                '</p><br />';
        if ($readonly) {
            $disabled = ' disabled="disabled"';
        }
    }
    if ($disabled) {
        $togglebuttons = '<br />';
    } elsif ($context ne 'passback') {
        $togglebuttons = '<input type="button" value="'.&mt('check all').'" '.
                         'onclick="javascript:checkAll(document.'.$formname.'.'.$chkname.')" />'.
                         '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
                         ' onclick="javascript:uncheckAll(document.'.$formname.'.'.$chkname.')" />';
    }
    $display = '<form name="'.$formname.'" action="'.$action.'" method="post"'.$onsubmit.'>'."\n";
    if ($context eq 'imsexport') {
        $display .= $info.
                    '<div class="LC_columnSection">'."\n".
                    '<fieldset>'.
                    '<legend>'.&mt('Content items').'</legend>'."\n".
                    $togglebuttons.
                    '</fieldset>';
        %discussiontime = &Apache::lonnet::dump('discussiontimes',$cdom,$cnum);
        $numdisc = keys(%discussiontime);
        if ($numdisc > 0) {
            $display .= 
                '<fieldset>'.
                '<legend>'.&mt('Discussion posts').'</legend>'.
                '<input type="button" value="'.&mt('check all').'"'.
                ' onclick="javascript:checkAll(document.'.$formname.'.discussion)" />'.
                '&nbsp;&nbsp;<input type="button" value="'.&mt('uncheck all').'"'.
                ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'.
                '</fieldset>';
        }
        $display .= '</div>';
    } elsif (($context eq 'examblock') || ($context eq 'shorturls') || ($context eq 'passback')) {
        $display .= $info.$togglebuttons;
    } elsif ($context eq 'dumpdocs') {
        $display .= $preamble.
                    '<div class="LC_left_float">'.
                    '<fieldset>'.
                    '<legend>'.&mt('Content to copy').('&nbsp;'x4).$togglebuttons.'</legend>'.
                    $info;
    }
    my $lastcontainer = $startcount;
    $display .= &Apache::loncommon::start_data_table()
               .&Apache::loncommon::start_data_table_header_row();
    if ($context eq 'imsexport') {
        $display .= '<th>'.&mt('Export content item?').'</th>';
        if ($numdisc > 0) {
            $display .= '<th>'.&mt('Export discussion posts?').'</th>';
        }
    } elsif ($context eq 'examblock') {
        $display .= '<th>'.&mt('Access blocked?').'</th>';
    } elsif ($context eq 'dumpdocs') {
        $display .= '<th>'.&mt('Copy?').'</th>'.
                    '<th>'.&mt("Title in $crstype").'</th>'.
                    '<th>'.&mt('Internal Identifier').'</th>'.
                    '<th>'.&mt('Save as ...').'</th>';
    } elsif ($context eq 'shorturls') {
        $display .= '<th colspan="2">'.&mt('Tiny URL').'</th>'.
                    '<th>'.&mt("Title in $crstype").'</th>';
    } elsif ($context eq 'passback') {
        $display .= '<th>'.&mt("Title in $crstype").'</th>'.
                    '<th>'.&mt('Tiny URL Deep-link').'</th>'.
                    '<th>'.&mt('Launcher').'</th>'.
                    '<th  style="padding-left: 6px; padding-right: 6px">'.&mt('Score Type').'</th>'.
                    '<th style="padding-left: 6px; padding-right: 6px">'.&mt('Select').'</th>';
    }
    $display .= &Apache::loncommon::end_data_table_header_row();
    while ($curRes = $it->next()) {
        if ($curRes == $it->BEGIN_MAP()) {
            $depth++;
            $parent{$depth} = $lastcontainer;
        }
        if ($curRes == $it->END_MAP()) {
            $depth--;
            $lastcontainer = $parent{$depth};
        }
        if (ref($curRes)) {
            my $symb = $curRes->symb();
            my $ressymb = $symb;
            if ($context eq 'dumpdocs') {
                next unless (($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(docs|supplemental|simplepage)}) ||
                             ($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(default|supplemental)_\d+\.(sequence|page)}) ||
                             ($curRes->src() eq '/res/lib/templates/simpleproblem.problem') ||
                             ($curRes->src() =~ m{^/adm/$match_domain/$match_username/\d+/smppg}));
            } elsif ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) {
                unless ($ressymb =~ m|adm/wrapper/adm|) {
                    $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
                }
            }
            $count ++;
            my ($currelem,$mapurl,$is_map,$showitem);
            if ($context eq 'imsexport') {
                $currelem = $count+$boards+$startcount;
            } else {
                $currelem = $count+$startcount;
            }
            if (($curRes->is_sequence()) || ($curRes->is_page())) {
                $lastcontainer = $currelem;
                $mapurl = (&Apache::lonnet::decode_symb($symb))[2];
                $is_map = 1;
            }
            if ($context eq 'passback') {
                if (($curRes->is_sequence()) || ($curRes->is_page())) {
                    next unless ($shownmaps{$curRes->src});
                } else {
                    next unless ($shownsymbs{$symb});
                }
            } else {
                $display .= &Apache::loncommon::start_data_table_row()."\n";
            }
            if ($context eq 'shorturls') {
                if ($shorturls{$symb}) {
                    $display .= '<td>&nbsp;</td><td align="right"><b>'."/tiny/$cdom/$shorturls{$symb}".'</b></td>'."\n";
                } else {
                    $display .= '<td align="left"><label><input type="checkbox" name="'.$chkname.'" '.
                                'value="'.$count.'"'.$disabled.' />'.&mt('Add').'</label></td>'.
                                '<td>&nbsp;</td>'."\n";
                }
            } elsif ($context ne 'passback') {
                $display .= '<td><input type="checkbox" name="'.$chkname.'" value="'.$count.'" ';
                if ($is_map) {
                    $display .= 'onclick="javascript:checkFolder(document.'.$formname.','."'$currelem'".')" ';
                    if ($currmaps{$mapurl}) {
                        $display .= 'checked="checked"';
                        push(@checked_maps,$currelem);
                    }
                } else {
                    if ($curRes->is_problem()) {
                        $numprobs ++;
                    }
                    $display .= 'onclick="javascript:checkResource(document.'.$formname.','."'$currelem'".')" ';
                    if ($currresources{$symb}) {
                        $display .= 'checked="checked"';
                    }
                }
                $display .= $disabled.' />'."\n";
            }
            if ($context eq 'dumpdocs') {
                $display .= '</td><td valign="top">';
            } elsif ($context eq 'shorturls') {
                $display .= '<td valign="top">';
            }
            for (my $i=0; $i<$depth; $i++) {
                $showitem .= "$whitespace\n";
            }
            my $icon = 'src="'.$location.'/unknown.gif" alt=""';
            if ($curRes->is_sequence()) {
                $icon = 'src="'.$location.'/navmap.folder.open.gif" alt="'.&mt('Folder').'"';
            } elsif ($curRes->is_page()) {
                $icon = 'src="'.$location.'/navmap.page.open.gif" alt="'.&mt('Composite Page').'"';
            } elsif ($curRes->is_problem()) {
                $icon = 'src="'.$location.'/problem.gif" alt="'.&mt('Problem').'"';
            } elsif ($curRes->is_task()) {
                $icon = 'src="'.$location.'/task.gif" alt="'.&mt('Task').'"';
            } elsif ($curRes->src ne '') {
                $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
            }
            $showitem .= '<img '.$icon.' />&nbsp;'."\n";
            $children{$parent{$depth}} .= $currelem.':';
            if ($context eq 'examblock') {
                if ($parent{$depth} > 1) {
                    if ($hierarchy{$parent{$depth}}) {
                        $hierarchy{$currelem} = $hierarchy{$parent{$depth}}.",'$parent{$depth}'";
                    } else {
                        $hierarchy{$currelem} = "'$parent{$depth}'";
                    }
                }
            }
            $showitem .= '&nbsp;'.$curRes->title().$whitespace;
            if ($context eq 'passback') {
                if ((exists($pb{$symb})) && (ref($pb{$symb}) eq 'HASH')) {
                    my $numlinks = scalar(keys(%{$pb{$symb}}));
                    my $count = 0;
                    foreach my $launcher (sort(keys(%{$pb{$symb}}))) {
                        if ($count == 0) {
                            $display .= &Apache::loncommon::start_data_table_row()."\n";
                            if ($numlinks > 1) {
                                $display .= '<td rowspan="'.$numlinks.'">'.$showitem.'</td>';
                            } else {
                                $display .= '<td style="vertical-align: baseline">'.$showitem.'</td>';
                            }
                        } else {
                            $display .= &Apache::loncommon::end_data_table_row().
                                        &Apache::loncommon::start_data_table_row()."\n";
                        }
                        my ($linkuri,$linkprotector,$scope) = split("\0",$launcher);
                        my ($ltinum,$ltitype) = ($linkprotector =~ /^(\d+)(c|d)$/);
                        my ($appname,$setter);
                        if ($ltitype eq 'c') {
                            my %lti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider');
                            if (ref($lti{$ltinum}) eq 'HASH') {
                                $appname = $lti{$ltinum}{'name'};
                                if ($appname) {
                                    $setter = ' (defined in course)';
                                }
                            }
                        } elsif ($ltitype eq 'd') {
                            my %lti = &Apache::lonnet::get_domain_lti($cdom,'linkprot');
                            if (ref($lti{$ltinum}) eq 'HASH') {
                                $appname = $lti{$ltinum}{'name'};
                                if ($appname) {
                                    $setter = ' (defined in domain)';
                                }
                            }
                        }
                        my $shownscope;
                        if ($scope eq 'res') {
                            $shownscope = &mt('Resource');
                        } elsif ($scope eq 'map') {
                            $shownscope = &mt('Folder');
                        }  elsif ($scope eq 'rec') {
                            $shownscope = &mt('Folder + sub-folders');
                        }
                        $display .= '<td style="vertical-align: baseline"><span style="font-weight: bold;">'.$linkuri.'</span></td>'."\n".
                                    '<td style="vertical-align: baseline; padding-left: 6px; padding-right: 6px">'.$appname.$setter.'</td>'."\n".
                                    '<td style="vertical-align: baseline"><span style="font-style: italic;">'.$shownscope.'</span></td>'."\n".
                                    '<td align="right" style="vertical-align: baseline"><input type="radio" name="'.$chkname.'" '.
                                    'value="'.&escape($launcher).'"'.$disabled.' /></td>'."\n";
                        $count ++;
                    }
                } else {
                    $display .= &Apache::loncommon::start_data_table_row()."\n".
                                '<td colspan="5">'.$showitem.'</td>';
                }
            } else {
                $display .= $showitem.'</td>'."\n";
            }
            if ($context eq 'imsexport') {
# Existing discussion posts?
                if ($discussiontime{$ressymb} > 0) {
                    $boards ++;
                    $display .= '<td align="right">'
                               .'<input type="checkbox" name="discussion" value="'.$count.'" />'
                               .'</td>'."\n";
                } elsif ($numdisc > 0) {
                    $display .= '<td>&nbsp;</td>'."\n";
                }
            } elsif ($context eq 'dumpdocs') {
                my $src = $curRes->src();
                my ($filepath,$title);
                if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E}) {
                    $filepath = &Apache::lonnet::filelocation('',$src);
                    $filepath =~ s/\Q$crsprefix\E//;
                    if ($curRes->is_map()) {
                        $title = $files{$filepath};
                    } else {
                        $filepath =~ s{docs/}{}; 
                        $title = $filepath;
                        $title =~ s{^(default|\d+)/\d*/?}{};
                    }
                } else {
                    $title = $curRes->title();
                    $title =~ s{/}{_}g;
                    $title = &clean($title);
                    if ($src eq '/res/lib/templates/simpleproblem.problem') {
                        my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb);
                        $map =~ s{^uploaded/$cdom/$cnum/}{};
                        $filepath = $map.'_'.$id;
                        $title .= '.problem';
                    } elsif ($src =~ m{^/adm/$match_domain/$match_username/(\d+)/smppg}) {
                        $filepath = 'smppage_'.$1.'.db';
                        $title .= '.html';
                    }
                }
                $display .= '<td>'.$filepath.'</td>'.
                            '<td><input type="text" size="40" name="namefor_'.$count.'" id="namefor_'.$count.'" value="'.$title.'" /></td>'."\n";
            }
            $display .= &Apache::loncommon::end_data_table_row();
        }
    }
    $display .= &Apache::loncommon::end_data_table();
    if ($context eq 'imsexport') {
        if ($numprobs > 0) {
            $display .= '<p><span class="LC_nobreak">'.
                        &mt('Export format for LON-CAPA problems:').
                        '<label><input type="radio" name="format" value="xml" checked="checked" />'.
                        '&nbsp;'.&mt('XML').'</label>'.('&nbsp;' x3).
                        '<label><input type="radio" name="format" value="html" />'.
                        '&nbsp;'.&mt('HTML').'</label>'.('&nbsp;' x3).
                        '<label><input type="radio" name="format" value="plaintext" />'.
                        '&nbsp;'.&mt('Text').'</label></span></p>';
        }
    }
    my $numcount;
    if ($context eq 'imsexport') {
        $display .= 
           '<p>'.
           '<input type="hidden" name="finishexport" value="1" />'.
           '<input type="submit" name="exportcourse" value="'.
           &mt('Export').'" /></p>';
        $numcount = $count + $boards + $startcount;
    } elsif ($context eq 'examblock') {
        unless ($readonly) {
            $display .=
                '<p>'.
                '<input type="submit" name="resourceblocks" value="'.
                &mt('Copy Choices to Main Window').'" /></p>';
        }
        $numcount = $count + $startcount;
    } elsif ($context eq 'dumpdocs') {
        $display .= '</fieldset>'.
                    '</div><div style="padding:0;clear:both;margin:0;border:0"></div>'.
                    '<div>'.
                    '<input type="submit" name="dumpcourse" value="'.&mt("Copy $crstype Content").'" />'.
                    '</div>';
        $numcount = $count + $startcount;
    } elsif ($context eq 'shorturls') {
        unless ($readonly) {
            $display .=
                '<p>'.
                '<input type="submit" name="shorturls" value="'.
                &mt('Create Tiny URL(s)').'" /></p>';
        }
    } elsif ($context eq 'passback') {
        unless ($readonly) {
            $display .=
                '<p>'.
                '<input type="hidden" name="symb" value="'.&Apache::lonenc::check_encrypt($env{'form.symb'}).'" />'."\n".
                '<input type="hidden" name="command" value="passback" />'.
                '<input type="submit" name="picklauncher" value="'.
                &mt('Next').' &rarr;" /></p>';
        }
    }
    $display .= '</form>';
    if ($context eq 'passback') {
        return $display;
    }
    my $scripttag =
        &respicker_javascript($startcount,$numcount,$context,$formname,\%children,
                              \%hierarchy,\@checked_maps,$numhome,$chkname);
    if (($context eq 'dumpdocs') || ($context eq 'shorturls')) {
        return $scripttag.$display; 
    }
    my ($title,$crumbs,$args);
    if ($context eq 'imsexport') { 
        $title = 'Export '.$crstype.' to IMS Package';
    } elsif ($context eq 'examblock') {
        $title = 'Resources with Access blocked';
        $args = {'only_body'      => 1,
                 'add_entries' => { onload => 'javascript:recurseFolders();' }, 
                };
    }
    $output = &Apache::loncommon::start_page($title,$scripttag,$args);
    if ($context eq 'imsexport') {
        $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export').
                   &Apache::londocs::startContentScreen('tools');
    } elsif ($context eq 'dumpdocs') {
        $output .= &Apache::lonhtmlcommon::breadcrumbs('Copying to Authoring Space').
                   &Apache::londocs::startContentScreen('tools');
    }
    $output .= $display;
    if ($context eq 'examblock') {
        $output .= &Apache::loncommon::end_page();
    } elsif ($context eq 'imsexport') {
        $output .= &Apache::londocs::endContentScreen();
    }
    return $output;
}

sub respicker_javascript {
    my ($startcount,$numitems,$context,$formname,$children,$hierarchy,
        $checked_maps,$numhome,$chkname) = @_;
    my $check_uncheck = <<"FIRST";
function checkAll(field) {
    if (field.length > 0) {
        for (i = 0; i < field.length; i++) {
            field[i].checked = true ;
        }
    } else {
        field.checked = true
    }
}

function uncheckAll(field) {
    if (field.length > 0) {
        for (i = 0; i < field.length; i++) {
            field[i].checked = false;
        }
    } else {
        field.checked = false;
    }
}
FIRST
    if ($context eq 'shorturls') {
        return <<"END";
<script type="text/javascript">
// <![CDATA[
$check_uncheck
// ]]>
</script>
END
    }
    return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH')
                   && (ref($checked_maps) eq 'ARRAY'));
    my ($elem,$nested,$nameforelem);
    if ($context eq 'dumpdocs') {
        $elem='((parseInt(item)-'.$startcount.')*2)+'.$startcount;
        $nested='((parseInt(nesting[item][i])-'.$startcount.')*2)+'.$startcount;
        $nameforelem=$elem+1;
    } else {
        $elem='parseInt(item)';
        $nested='parseInt(nesting[item][i])';
    }
    my $scripttag = <<"START";
<script type="text/javascript">
// <![CDATA[
$check_uncheck

function checkFolder(form,item) {
    var elem = $elem;
    if (form.elements[elem].checked == true) {
        containerCheck(form,item);
    } else {
        containerUncheck(form,item);
    }
}

function checkResource(form,item) {
    var elem = $elem;
    if (form.elements[elem].checked == false) {
        containerUncheck(form,item);
    }
}

numitems = $numitems;
var parents = new Array(numitems);
var nesting = new Array(numitems);
var initial = new Array();
for (var i=$startcount; i<numitems; i++) {
    parents[i] = new Array();
    nesting[i] = new Array();
}

START

    foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
        my @contents = split(/:/,$children->{$container});
        for (my $i=0; $i<@contents; $i ++) {
            $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
        }
    }

    if ($context eq 'examblock') {
        foreach my $item (sort { $a <=> $b } (keys(%{$hierarchy}))) {
            $scripttag .= "nesting[$item] = new Array($hierarchy->{$item});\n";
        }
         
        my @sorted_maps = sort { $a <=> $b } (@{$checked_maps});
        for (my $i=0; $i<@sorted_maps; $i++) {
            $scripttag .= "initial[$i] = '$sorted_maps[$i]'\n";
        }
        $scripttag .= <<"EXTRA";

function recurseFolders() {
    if (initial.length > 0) {
        for (var i=0; i<initial.length; i++) {
            containerCheck(document.$formname,initial[i]);
        }
    }
    return;
}

EXTRA
    } elsif ($context eq 'dumpdocs') {
        my $blankmsg = &mt('An item selected has no filename set in the "Save as ..." column.');
        my $dupmsg = &mt('Items selected for copying need unique filenames in the "Save as ..." column.');
        my $homemsg = &mt('An Authoring Space needs to be selected.');
        &js_escape(\$blankmsg);
        &js_escape(\$dupmsg);
        &js_escape(\$homemsg);
        $scripttag .= <<"EXTRA";

function checkUnique(form,field) {
    var duplicate = 0;
    var blank = 0;
    var numhome = '$numhome';
    if (field.length > 0) {
        for (i=0; i<field.length; i++) {
            if (field[i].checked) {
                var item = field[i].value;
                var namefor = document.getElementById('namefor_'+item);
                if (namefor) {
                    var possval = namefor.value;
                    if (!possval) {
                        blank = item;
                        break;
                    }
                    for (j=i+1; j<field.length; j++) {
                        if (field[j].checked) {
                            var curritem = field[j].value;
                            var currnamefor = document.getElementById('namefor_'+curritem);
                            if (currnamefor) {
                                var currval = currnamefor.value;
                                if (currval == possval) {
                                    duplicate = curritem;
                                    break;
                                }
                            }
                        }
                    }
                    if (duplicate) {
                        break;
                    }
                }
            }
        }
    }
    if (blank) {
        alert('$blankmsg');
        return false;
    }
    if (duplicate) {
        alert('$dupmsg');
        return false;
    }
    if (numhome > 1) {
        if (!form.authorspace.options[form.authorspace.selectedIndex].value) {
            alert('$homemsg');
            return false;
        }
    }
    return true;
}

EXTRA

    }

    $scripttag .= <<"END";

function containerCheck(form,item) {
    var elem = $elem;
    form.elements[elem].checked = true;
    if(Object.prototype.toString.call(parents[item]) === '[object Array]') {
        if (parents[item].length > 0) {
            for (var j=0; j<parents[item].length; j++) {
                containerCheck(form,parents[item][j]);
            }
        }
    }
}

function containerUncheck(form,item) {
    if(Object.prototype.toString.call(nesting[item]) === '[object Array]') {
        if (nesting[item].length > 0) {
            for (var i=0; i<nesting[item].length; i++) {
                var nested = $nested;
            }
        }
    }
    return;
}

END

    if ($context eq 'examblock') {
        $scripttag .= <<ENDEX;
function writeToOpener(maps,resources) {
    var checkedmaps = '';
    var checkedresources = '';
    for (var i=0; i<document.$formname.${chkname}.length; i++) {
        if (document.$formname.${chkname}[i].checked) {
            var isResource = 1;
            var include = 1;
            var elemnum = i+1+$startcount;
            if (Object.prototype.toString.call(parents[elemnum]) === '[object Array]') {
                if (parents[elemnum].length > 0) {
                    isResource = 0;
                }
            }
            if (isResource == 1) {
                if (nesting[elemnum] != null) {
                    if (nesting[elemnum].length > 0) {
                        var lastelem = nesting[elemnum].length-1;
                        if (document.$formname.elements[nesting[elemnum][lastelem]].checked) {
                            include = 0;
                        }
                    }
                }
            }
            if (include == 1) {
                if (isResource == 1) {
                    checkedresources += document.$formname.${chkname}[i].value+',';
                } else {
                    checkedmaps += document.$formname.${chkname}[i].value+',';
                }
            }
        }
    }
    opener.document.getElementById(maps).value = checkedmaps;
    opener.document.getElementById(resources).value = checkedresources;
    window.close();
    return false;
}

ENDEX
    }

    $scripttag .= '
// ]]>
</script>
';
    return $scripttag;
}

sub get_navmap_object {
    my ($crstype,$context) = @_;
    my $navmap = Apache::lonnavmaps::navmap->new();
    my $outcome;
    if (!defined($navmap)) {
        if ($context eq 'imsexport') {
            $outcome = &Apache::loncommon::start_page('Export '.$crstype.' to IMS Package').
                      '<h2>'.&mt('IMS Export Failed').'</h2>';
        } elsif ($context eq 'examblock') {
            $outcome = &Apache::loncommon::start_page('Selection of Resources for Blocking',
                                                       undef,{'only_body' => 1,}).
                      '<h2>'.&mt('Resource Display Failed').'</h2>';  
        } elsif ($context eq 'dumpdocs') {
            $outcome = '<h2>'.&mt('Copying to Authoring Space unavailable').'</h2>';
        } elsif ($context eq 'shorturls') {
            $outcome = '<h2>'.&mt('Display/Setting of shortened URLs unavailable').'</h2>';
        }
        $outcome .= '<div class="LC_error">';
        if ($crstype eq 'Community') {
            $outcome .= &mt('Unable to retrieve information about community contents');
        } else {
            $outcome .= &mt('Unable to retrieve information about course contents');
        }
        $outcome .= '</div>';
        if (($context eq 'imsexport') || ($context eq 'dumpdocs') || ($context eq 'shorturls') ) {
            $outcome .= '<a href="/adm/coursedocs">';
            if ($crstype eq 'Community') {
                $outcome .= &mt('Return to Community Editor');
            } else {
                $outcome .= &mt('Return to Course Editor');
            }
            $outcome .= '</a>';
            if ($context eq 'imsexport') {
                &Apache::lonnet::logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
            } elsif ($context eq 'dumpdocs') {
                &Apache::lonnet::logthis('Copying to Authoring Space failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
            } elsif ($context eq 'shorturls') {
                &Apache::lonnet::logthis('Displaying and/or saving URL shortcuts failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
            }
        } elsif ($context eq 'examblock') {
            $outcome .=  '<href="javascript:window.close();">'.&mt('Close window').'</a>';         
        }
        return (undef,$outcome);
    } else {
        return ($navmap);
    }
}

sub clean {
    my ($title)=@_;
    $title=~s/[^\w\/\!\$\%\^\*\-\_\=\+\;\:\,\\\|\`\~]+/\_/gs;
    return $title;
}

sub enumerate_course_contents {
    my ($navmap,$map_url,$resource_symb,$titleref,$context,$cdom,$cnum) = @_;
    if ((ref($navmap)) && (ref($map_url) eq 'HASH') &&
        (ref($resource_symb) eq 'HASH') && (ref($titleref) eq 'HASH')) {
        my $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
        my $count = 0;
        while (my $curRes = $it->next()) {
            if (ref($curRes)) {
                my $symb = $curRes->symb();
                my $ressymb = $symb;
                if ($context eq 'dumpdocs') {
                    next unless (($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(docs|supplemental|simplepage)/}) ||
                                 ($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(default|supplemental)_\d+\.(sequence|page)}) ||
                                 ($curRes->src() eq '/res/lib/templates/simpleproblem.problem') ||
                                 ($curRes->src() =~ m{^/adm/$match_domain/$match_username/\d+/smppg}));
                } elsif ($ressymb =~ m{adm/($match_domain)/($match_username)/(\d+)/bulletinboard$}) {
                    unless ($ressymb =~ m{adm/wrapper/adm}) {
                        $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.
                                   '/bulletinboard';
                    }
                }
                $count ++;
                if ($context eq 'shorturls') {
                    $resource_symb->{$count} = $ressymb;
                } else {
                    if (($curRes->is_sequence()) || ($curRes->is_page())) {
                        $map_url->{$count} = (&Apache::lonnet::decode_symb($symb))[2];
                    } else {
                        $resource_symb->{$count} = $ressymb;
                    }
                }
                $titleref->{$count} = $curRes->title();
            }
        }
    }
    return;
}

1;

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