--- loncom/interface/loncourserespicker.pm 2013/01/15 17:39:58 1.5 +++ loncom/interface/loncourserespicker.pm 2024/12/26 17:12:36 1.21 @@ -1,6 +1,6 @@ # The LearningOnline Network # -# $Id: loncourserespicker.pm,v 1.5 2013/01/15 17:39:58 raeburn Exp $ +# $Id: loncourserespicker.pm,v 1.21 2024/12/26 17:12:36 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -33,53 +33,79 @@ loncourserespicker - Utilities to choose =head1 SYNOPSIS -loncourserespicker provides an interface for selecting which folders and/or -resources are to be either: +loncourserespicker provides either (1) an interface for selecting which +folders and/or resources are to be selected for a specific action, one of: -(a) exported to an IMS Content Package -(b) subject to access blocking for the duriation of an exam/quiz. +(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. - -The choice of items is copied back to the main window from which the pop-up -window used to display the Course Contents was opened. +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 -The main subroutine: &create_picker() will display the hierarchy of folders, -sub-folders, and resources in the Main Course Documents 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 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 -content in which resource/folder selection is being made. +context in which resource/folder selection is being made. -The two contexts currently supported are: IMS export and selection of +The five contexts currently supported are: IMS export, selection of content to be subject to access restructions for the duration of an -exam. +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() -Created HTML mark up to display contents of course with checkboxes to +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. -Inputs: 7. +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 and examblock are supported. + 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. @@ -95,23 +121,52 @@ Inputs: 7. - $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 in the pop-up window. + 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 -(upeards) unchecking of an item within a folder. +(upwards) unchecking of an item within a folder. -Inputs: 7. +Inputs: 9. - $startcount -- Starting offset of form element numbering for items - - $numcount -- Total numer of folders and resources in course. + - $numcount -- Total number of folders and resources in course. - $context -- Context in which resources are being displayed - (imsexport or examblock). + (imsexport, examblock, dumpdocs, shorturls, or + crsauthored). - $formname -- Name of form. @@ -121,7 +176,13 @@ Inputs: 7. - $checked_maps -- Reference to array of folders currently checked. -Output: 1. Javascript (witthin <script></script> tags. + - $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() @@ -132,7 +193,51 @@ no object instantiated. Inputs: 2. - $crstype -- Container type: Course or Community - - $context -- Context: imsexport or examblock + - $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 @@ -153,15 +258,72 @@ use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); sub create_picker { - my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block) = @_; + my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block,$preamble, + $numhome,$uploadedfiles,$tiny,$passback,$readonly) = @_; return unless (ref($navmap)); - my ($it,$output,$numdisc,%maps,%resources,%discussiontime,%currmaps,%currresources); + 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; @@ -174,42 +336,63 @@ sub create_picker { my %children = (); my %hierarchy = (); my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); - my $whitespace = + my $whitespace = '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />'; - my $onsubmit; + 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"'; + } } - my $display = - '<form name="'.$formname.'" action="" method="post"'.$onsubmit.'>'."\n". - '<p>'; - if ($context eq 'imsexport') { - $display .= &mt('Choose which items you wish to export from your '. - $crstype.'.'); + 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 'examblock') { - $display .= &mt('Items in '.lc($crstype).' for which access will be blocked.'); + } 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].', + '→'). + '</p><br />'; + if ($readonly) { + $disabled = ' disabled="disabled"'; + } } - $display .= '</p>'; - if ($context eq 'imsexport') { - $display .= '<div class="LC_columnSection">'."\n". - '<fieldset>'. - '<legend>'.&mt('Content items').'</legend>'."\n"; + if ($disabled) { + $togglebuttons = '<br />'; + } elsif ($context ne 'passback') { + $togglebuttons = '<input type="button" value="'.&mt('check all').'" '. + 'onclick="javascript:checkAll(document.'.$formname.'.'.$chkname.')" />'. + ' <input type="button" value="'.&mt('uncheck all').'"'. + ' onclick="javascript:uncheckAll(document.'.$formname.'.'.$chkname.')" />'; } - $display .= - '<input type="button" value="'.&mt('check all').'" '. - 'onclick="javascript:checkAll(document.'.$formname.'.archive)" />'. - ' <input type="button" value="'.&mt('uncheck all').'"'. - ' onclick="javascript:uncheckAll(document.'.$formname.'.archive)" />'; + $display = '<form name="'.$formname.'" action="'.$action.'" method="post"'.$onsubmit.'>'."\n"; if ($context eq 'imsexport') { - $display .= '</fieldset>'; - %discussiontime = - &Apache::lonnet::dump('discussiontimes', - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $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 .= @@ -221,7 +404,15 @@ sub create_picker { ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'. '</fieldset>'; } - $display .= '</div>'; + $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').(' 'x4).$togglebuttons.'</legend>'. + $info; } my $lastcontainer = $startcount; $display .= &Apache::loncommon::start_data_table() @@ -233,12 +424,23 @@ sub create_picker { } } 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 (ref($curRes)) { - $count ++; - } if ($curRes == $it->BEGIN_MAP()) { $depth++; $parent{$depth} = $lastcontainer; @@ -250,35 +452,71 @@ sub create_picker { if (ref($curRes)) { my $symb = $curRes->symb(); my $ressymb = $symb; - if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) { + 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'; } } - my $currelem = $count+$boards+$startcount; - $display .= &Apache::loncommon::start_data_table_row(). - '<td>'."\n". - '<input type="checkbox" name="archive" value="'.$count.'" '; + $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; - $display .= 'onclick="javascript:checkFolder(this.form,'."'$currelem'".')" '; - my $mapurl = (&Apache::lonnet::decode_symb($symb))[2]; - if ($currmaps{$mapurl}) { - $display .= 'checked="checked"'; - push(@checked_maps,$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 { - if ($curRes->is_problem()) { - $numprobs ++; + $display .= &Apache::loncommon::start_data_table_row()."\n"; + } + if ($context eq 'shorturls') { + if ($shorturls{$symb}) { + $display .= '<td> </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> </td>'."\n"; } - $display .= 'onclick="javascript:checkResource(this.form,'."'$currelem'".')" '; - if ($currresources{$symb}) { - $display .= 'checked="checked"'; + } 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">'; } - $display .= ' />'."\n"; for (my $i=0; $i<$depth; $i++) { - $display .= "$whitespace\n"; + $showitem .= "$whitespace\n"; } my $icon = 'src="'.$location.'/unknown.gif" alt=""'; if ($curRes->is_sequence()) { @@ -292,7 +530,7 @@ sub create_picker { } elsif ($curRes->src ne '') { $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""'; } - $display .= '<img '.$icon.' /> '."\n"; + $showitem .= '<img '.$icon.' /> '."\n"; $children{$parent{$depth}} .= $currelem.':'; if ($context eq 'examblock') { if ($parent{$depth} > 1) { @@ -303,8 +541,65 @@ sub create_picker { } } } - $display .= ' '.$curRes->title().'</td>'."\n"; - + $showitem .= ' '.$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) { @@ -315,6 +610,35 @@ sub create_picker { } elsif ($numdisc > 0) { $display .= '<td> </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(); } @@ -332,24 +656,58 @@ sub create_picker { ' '.&mt('Text').'</label></span></p>'; } } - $display .= '<p>'; + my $numcount; if ($context eq 'imsexport') { $display .= + '<p>'. '<input type="hidden" name="finishexport" value="1" />'. '<input type="submit" name="exportcourse" value="'. - &mt('Export').'" />'; + &mt('Export').'" /></p>'; + $numcount = $count + $boards + $startcount; } elsif ($context eq 'examblock') { - $display .= - '<input type="submit" name="resourceblocks" value="'. - &mt('Copy Choices to Main Window').'" />'; - } - $display .= '</p></form>'; - my $numcount = $count + $boards + $startcount; - my $scripttag = + 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').' →" /></p>'; + } + } + $display .= '</form>'; + if ($context eq 'passback') { + return $display; + } + my $scripttag = &respicker_javascript($startcount,$numcount,$context,$formname,\%children, - \%hierarchy,\@checked_maps); + \%hierarchy,\@checked_maps,$numhome,$chkname); + if (($context eq 'dumpdocs') || ($context eq 'shorturls')) { + return $scripttag.$display; + } my ($title,$crumbs,$args); - if ($context eq 'imsexport') { + if ($context eq 'imsexport') { $title = 'Export '.$crstype.' to IMS Package'; } elsif ($context eq 'examblock') { $title = 'Resources with Access blocked'; @@ -361,6 +719,9 @@ sub create_picker { 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') { @@ -373,12 +734,8 @@ sub create_picker { sub respicker_javascript { my ($startcount,$numitems,$context,$formname,$children,$hierarchy, - $checked_maps) = @_; - return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH') - && (ref($checked_maps) eq 'ARRAY')); - my $scripttag = <<"START"; -<script type="text/javascript"> -// <![CDATA[ + $checked_maps,$numhome,$chkname) = @_; + my $check_uncheck = <<"FIRST"; function checkAll(field) { if (field.length > 0) { for (i = 0; i < field.length; i++) { @@ -398,9 +755,35 @@ function uncheckAll(field) { 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) { - if (form.elements[item].checked == true) { + var elem = $elem; + if (form.elements[elem].checked == true) { containerCheck(form,item); } else { containerUncheck(form,item); @@ -408,12 +791,13 @@ function checkFolder(form,item) { } function checkResource(form,item) { - if (form.elements[item].checked == false) { + var elem = $elem; + if (form.elements[elem].checked == false) { containerUncheck(form,item); } } -numitems = $numitems; +numitems = $startcount + $numitems; var parents = new Array(numitems); var nesting = new Array(numitems); var initial = new Array(); @@ -452,12 +836,76 @@ function recurseFolders() { } 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) { - form.elements[item].checked = true; + 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++) { @@ -471,7 +919,7 @@ 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++) { - form.elements[nesting[item][i]].checked = false; + var nested = $nested; } } } @@ -485,8 +933,8 @@ END function writeToOpener(maps,resources) { var checkedmaps = ''; var checkedresources = ''; - for (var i=0; i<document.$formname.archive.length; i++) { - if (document.$formname.archive[i].checked) { + 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; @@ -496,18 +944,20 @@ function writeToOpener(maps,resources) { } } if (isResource == 1) { - if (nesting[elemnum].length > 0) { - var lastelem = nesting[elemnum].length-1; - if (document.$formname.elements[nesting[elemnum][lastelem]].checked) { - include = 0; + 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.archive[i].value+','; + checkedresources += document.$formname.${chkname}[i].value+','; } else { - checkedmaps += document.$formname.archive[i].value+','; + checkedmaps += document.$formname.${chkname}[i].value+','; } } } @@ -540,7 +990,11 @@ sub get_navmap_object { $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'); @@ -548,7 +1002,7 @@ sub get_navmap_object { $outcome .= &mt('Unable to retrieve information about course contents'); } $outcome .= '</div>'; - if ($context eq 'imsexport') { + 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'); @@ -556,7 +1010,13 @@ sub get_navmap_object { $outcome .= &mt('Return to Course Editor'); } $outcome .= '</a>'; - &Apache::lonnet::logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'}); + 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>'; } @@ -566,4 +1026,48 @@ sub get_navmap_object { } } +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;