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