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