Annotation of loncom/interface/loncourserespicker.pm, revision 1.5
1.1 raeburn 1: # The LearningOnline Network
2: #
1.5 ! raeburn 3: # $Id: loncourserespicker.pm,v 1.4 2013/01/14 18:47:14 bisitz 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:
36: loncourserespicker provides an interface for selecting which folders and/or
37: resources are to be either:
38:
39: (a) exported to an IMS Content Package
40: (b) subject to access blocking for the duriation of an exam/quiz.
41:
42: =head1 DESCRIPTION
43:
44: This module provides routines to generate a hierarchical display of folders
45: and resources in a course which can be selected for specific actions.
46:
47: The choice of items is copied back to the main window from which the pop-up
48: window used to display the Course Contents was opened.
49:
50: =head1 OVERVIEW
51:
52: The main subroutine: &create_picker() will display the hierarchy of folders,
53: sub-folders, and resources in the Main Course Documents area. Items can be
54: selected using checkboxes, and/or a "Check All" button. Selection of a folder
55: causes the contents of the folder to also be selected automatically. The
56: propagation of check status is recursive into sub-folders. Likewise, if an
57: item deep in a nested set of folders and sub-folders is unchecked, the
58: uncheck will propagate up through the hierarchy causing any folders at
59: a higher level to become unchecked.
60:
61: There is a submit button, which will be named differently according to the
62: content in which resource/folder selection is being made.
63:
64: The two contexts currently supported are: IMS export and selection of
65: content to be subject to access restructions for the duration of an
66: exam.
67:
68: =head1 INTERNAL SUBROUTINES
69:
70: =item &create_picker()
71:
72: Created HTML mark up to display contents of course with checkboxes to
73: select items. Checking a folder causes recursive checking of items
74: within the folder. Unchecking a resource causing unchecking of folders
75: containing the item back up to the top level.
76:
77: Inputs: 7.
78: - $navmap -- Reference to LON-CAPA navmap object
79: (encapsulates information about resources in the course).
80:
81: - $context -- Context in which course resource selection is being made.
82: Currently imsexport and examblock are supported.
83:
84: - $formname -- Name of the form in the window from which the pop-up
85: used to select course items was launched.
86:
87: - $crstype -- Course or Community
88:
89: - $blockedmaps -- Reference to hash of previously selected maps
90: (e.g., for a live exam block).
91:
92: - $blockedresources -- Reference to hash of resources selected
93: previously (e.g., for an exam block).
94:
95: - $block -- An internal ID (integer) used to track which exam
96: block currently being configured.
97:
98:
99: Output: $output is the HTML mark-up for display/selection of content
100: items in the pop-up window.
101:
102: =item &respicker_javascript()
103:
104: Creates javascript functions for checking/unchecking all items, and
105: for recursive checking triggered by checking a folder, or recursive
106: (upeards) unchecking of an item within a folder.
107:
108: Inputs: 7.
109: - $startcount -- Starting offset of form element numbering for items
110:
111: - $numcount -- Total numer of folders and resources in course.
112:
113: - $context -- Context in which resources are being displayed
114: (imsexport or examblock).
115:
116: - $formname -- Name of form.
117:
118: - $children -- Reference to hash of items contained within a folder.
119:
120: - $hierarchy -- Reference to hierarchy of folders containing an item.
121:
122: - $checked_maps -- Reference to array of folders currently checked.
123:
124: Output: 1. Javascript (witthin <script></script> tags.
125:
126:
127: =item &get_navmap_object()
128:
129: Instantiates a navmaps object, and generates an error message if
130: no object instantiated.
131:
132: Inputs: 2.
133: - $crstype -- Container type: Course or Community
134:
135: - $context -- Context: imsexport or examblock
136:
137: =over
138:
139: =back
140:
141: =cut
142:
143:
144: package Apache::loncourserespicker;
145:
146: use strict;
147: use Apache::lonnet;
148: use Apache::loncommon;
149: use Apache::lonhtmlcommon;
150: use Apache::lonnavmaps;
1.2 raeburn 151: use Apache::londocs;
1.1 raeburn 152: use Apache::lonlocal;
153: use LONCAPA qw(:DEFAULT :match);
154:
155: sub create_picker {
156: my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block) = @_;
157: return unless (ref($navmap));
158: my ($it,$output,$numdisc,%maps,%resources,%discussiontime,%currmaps,%currresources);
159: $it = $navmap->getIterator(undef,undef,undef,1,undef,undef);
160: if (ref($blockedmaps) eq 'HASH') {
161: %currmaps = %{$blockedmaps};
162: }
163: if (ref($blockedresources) eq 'HASH') {
164: %currresources = %{$blockedresources};
165: }
166: my @checked_maps;
167: my $curRes;
168: my $numprobs = 0;
169: my $depth = 0;
170: my $count = 0;
171: my $boards = 0;
172: my $startcount = 1;
173: my %parent = ();
174: my %children = ();
175: my %hierarchy = ();
176: my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons");
177: my $whitespace =
178: '<img src="'.$location.'/whitespace_21.gif" class="LC_docs_spacer" alt="" />';
179:
180: my $onsubmit;
181: if ($context eq 'examblock') {
182: my $maps_elem = 'docs_maps_'.$block;
183: my $res_elem = 'docs_resources_'.$block;
184: $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"';
185: }
186: my $display =
187: '<form name="'.$formname.'" action="" method="post"'.$onsubmit.'>'."\n".
188: '<p>';
189: if ($context eq 'imsexport') {
190: $display .= &mt('Choose which items you wish to export from your '.
191: $crstype.'.');
192: $startcount = 5;
193: } elsif ($context eq 'examblock') {
194: $display .= &mt('Items in '.lc($crstype).' for which access will be blocked.');
195: }
196: $display .= '</p>';
197: if ($context eq 'imsexport') {
198: $display .= '<div class="LC_columnSection">'."\n".
199: '<fieldset>'.
200: '<legend>'.&mt('Content items').'</legend>'."\n";
201: }
202: $display .=
203: '<input type="button" value="'.&mt('check all').'" '.
204: 'onclick="javascript:checkAll(document.'.$formname.'.archive)" />'.
205: ' <input type="button" value="'.&mt('uncheck all').'"'.
206: ' onclick="javascript:uncheckAll(document.'.$formname.'.archive)" />';
207: if ($context eq 'imsexport') {
208: $display .= '</fieldset>';
209: %discussiontime =
210: &Apache::lonnet::dump('discussiontimes',
211: $env{'course.'.$env{'request.course.id'}.'.domain'},
212: $env{'course.'.$env{'request.course.id'}.'.num'});
213: $numdisc = keys(%discussiontime);
214: if ($numdisc > 0) {
215: $display .=
216: '<fieldset>'.
217: '<legend>'.&mt('Discussion posts').'</legend>'.
218: '<input type="button" value="'.&mt('check all').'"'.
219: ' onclick="javascript:checkAll(document.'.$formname.'.discussion)" />'.
220: ' <input type="button" value="'.&mt('uncheck all').'"'.
221: ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'.
1.2 raeburn 222: '</fieldset>';
1.1 raeburn 223: }
1.2 raeburn 224: $display .= '</div>';
1.1 raeburn 225: }
226: my $lastcontainer = $startcount;
227: $display .= &Apache::loncommon::start_data_table()
228: .&Apache::loncommon::start_data_table_header_row();
229: if ($context eq 'imsexport') {
230: $display .= '<th>'.&mt('Export content item?').'</th>';
231: if ($numdisc > 0) {
232: $display .= '<th>'.&mt('Export discussion posts?').'</th>';
233: }
234: } elsif ($context eq 'examblock') {
235: $display .= '<th>'.&mt('Access blocked?').'</th>';
236: }
237: $display .= &Apache::loncommon::end_data_table_header_row();
238: while ($curRes = $it->next()) {
239: if (ref($curRes)) {
240: $count ++;
241: }
242: if ($curRes == $it->BEGIN_MAP()) {
243: $depth++;
244: $parent{$depth} = $lastcontainer;
245: }
246: if ($curRes == $it->END_MAP()) {
247: $depth--;
248: $lastcontainer = $parent{$depth};
249: }
250: if (ref($curRes)) {
251: my $symb = $curRes->symb();
252: my $ressymb = $symb;
253: if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) {
254: unless ($ressymb =~ m|adm/wrapper/adm|) {
255: $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard';
256: }
257: }
258: my $currelem = $count+$boards+$startcount;
259: $display .= &Apache::loncommon::start_data_table_row().
260: '<td>'."\n".
261: '<input type="checkbox" name="archive" value="'.$count.'" ';
262: if (($curRes->is_sequence()) || ($curRes->is_page())) {
263: $lastcontainer = $currelem;
264: $display .= 'onclick="javascript:checkFolder(this.form,'."'$currelem'".')" ';
265: my $mapurl = (&Apache::lonnet::decode_symb($symb))[2];
266: if ($currmaps{$mapurl}) {
267: $display .= 'checked="checked"';
268: push(@checked_maps,$currelem);
269: }
270: } else {
271: if ($curRes->is_problem()) {
272: $numprobs ++;
273: }
274: $display .= 'onclick="javascript:checkResource(this.form,'."'$currelem'".')" ';
275: if ($currresources{$symb}) {
276: $display .= 'checked="checked"';
277: }
278: }
279: $display .= ' />'."\n";
280: for (my $i=0; $i<$depth; $i++) {
281: $display .= "$whitespace\n";
282: }
283: my $icon = 'src="'.$location.'/unknown.gif" alt=""';
284: if ($curRes->is_sequence()) {
1.4 bisitz 285: $icon = 'src="'.$location.'/navmap.folder.open.gif" alt="'.&mt('Folder').'"';
1.1 raeburn 286: } elsif ($curRes->is_page()) {
287: $icon = 'src="'.$location.'/navmap.page.open.gif" alt="'.&mt('Composite Page').'"';
288: } elsif ($curRes->is_problem()) {
289: $icon = 'src="'.$location.'/problem.gif" alt="'.&mt('Problem').'"';
290: } elsif ($curRes->is_task()) {
291: $icon = 'src="'.$location.'/task.gif" alt="'.&mt('Task').'"';
292: } elsif ($curRes->src ne '') {
293: $icon = 'src="'.&Apache::loncommon::icon($curRes->src).'" alt=""';
294: }
295: $display .= '<img '.$icon.' /> '."\n";
296: $children{$parent{$depth}} .= $currelem.':';
297: if ($context eq 'examblock') {
298: if ($parent{$depth} > 1) {
299: if ($hierarchy{$parent{$depth}}) {
300: $hierarchy{$currelem} = $hierarchy{$parent{$depth}}.",'$parent{$depth}'";
301: } else {
302: $hierarchy{$currelem} = "'$parent{$depth}'";
303: }
304: }
305: }
306: $display .= ' '.$curRes->title().'</td>'."\n";
307:
308: if ($context eq 'imsexport') {
309: # Existing discussion posts?
310: if ($discussiontime{$ressymb} > 0) {
311: $boards ++;
312: $display .= '<td align="right">'
313: .'<input type="checkbox" name="discussion" value="'.$count.'" />'
314: .'</td>'."\n";
315: } elsif ($numdisc > 0) {
316: $display .= '<td> </td>'."\n";
317: }
318: }
319: $display .= &Apache::loncommon::end_data_table_row();
320: }
321: }
322: $display .= &Apache::loncommon::end_data_table();
323: if ($context eq 'imsexport') {
324: if ($numprobs > 0) {
325: $display .= '<p><span class="LC_nobreak">'.
326: &mt('Export format for LON-CAPA problems:').
327: '<label><input type="radio" name="format" value="xml" checked="checked" />'.
328: ' '.&mt('XML').'</label>'.(' ' x3).
329: '<label><input type="radio" name="format" value="html" />'.
330: ' '.&mt('HTML').'</label>'.(' ' x3).
331: '<label><input type="radio" name="format" value="plaintext" />'.
332: ' '.&mt('Text').'</label></span></p>';
333: }
334: }
335: $display .= '<p>';
336: if ($context eq 'imsexport') {
337: $display .=
338: '<input type="hidden" name="finishexport" value="1" />'.
339: '<input type="submit" name="exportcourse" value="'.
340: &mt('Export').'" />';
341: } elsif ($context eq 'examblock') {
342: $display .=
343: '<input type="submit" name="resourceblocks" value="'.
1.4 bisitz 344: &mt('Copy Choices to Main Window').'" />';
1.1 raeburn 345: }
346: $display .= '</p></form>';
347: my $numcount = $count + $boards + $startcount;
348: my $scripttag =
349: &respicker_javascript($startcount,$numcount,$context,$formname,\%children,
350: \%hierarchy,\@checked_maps);
351: my ($title,$crumbs,$args);
352: if ($context eq 'imsexport') {
353: $title = 'Export '.$crstype.' to IMS Package';
354: } elsif ($context eq 'examblock') {
355: $title = 'Resources with Access blocked';
356: $args = {'only_body' => 1,
357: 'add_entries' => { onload => 'javascript:recurseFolders();' },
358: };
359: }
1.3 raeburn 360: $output = &Apache::loncommon::start_page($title,$scripttag,$args);
1.1 raeburn 361: if ($context eq 'imsexport') {
1.2 raeburn 362: $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export').
363: &Apache::londocs::startContentScreen('tools');
1.1 raeburn 364: }
365: $output .= $display;
366: if ($context eq 'examblock') {
367: $output .= &Apache::loncommon::end_page();
1.2 raeburn 368: } elsif ($context eq 'imsexport') {
369: $output .= &Apache::londocs::endContentScreen();
1.1 raeburn 370: }
371: return $output;
372: }
373:
374: sub respicker_javascript {
375: my ($startcount,$numitems,$context,$formname,$children,$hierarchy,
376: $checked_maps) = @_;
377: return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH')
378: && (ref($checked_maps) eq 'ARRAY'));
379: my $scripttag = <<"START";
380: <script type="text/javascript">
381: // <![CDATA[
382: function checkAll(field) {
383: if (field.length > 0) {
384: for (i = 0; i < field.length; i++) {
385: field[i].checked = true ;
386: }
387: } else {
388: field.checked = true
389: }
390: }
391:
392: function uncheckAll(field) {
393: if (field.length > 0) {
394: for (i = 0; i < field.length; i++) {
395: field[i].checked = false;
396: }
397: } else {
398: field.checked = false;
399: }
400: }
401:
402: function checkFolder(form,item) {
403: if (form.elements[item].checked == true) {
404: containerCheck(form,item);
405: } else {
406: containerUncheck(form,item);
407: }
408: }
409:
410: function checkResource(form,item) {
411: if (form.elements[item].checked == false) {
412: containerUncheck(form,item);
413: }
414: }
415:
416: numitems = $numitems;
417: var parents = new Array(numitems);
418: var nesting = new Array(numitems);
419: var initial = new Array();
420: for (var i=$startcount; i<numitems; i++) {
421: parents[i] = new Array();
422: nesting[i] = new Array();
423: }
424:
425: START
426:
427: foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
428: my @contents = split(/:/,$children->{$container});
429: for (my $i=0; $i<@contents; $i ++) {
430: $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
431: }
432: }
433:
434: if ($context eq 'examblock') {
435: foreach my $item (sort { $a <=> $b } (keys(%{$hierarchy}))) {
436: $scripttag .= "nesting[$item] = new Array($hierarchy->{$item});\n";
437: }
438:
439: my @sorted_maps = sort { $a <=> $b } (@{$checked_maps});
440: for (my $i=0; $i<@sorted_maps; $i++) {
441: $scripttag .= "initial[$i] = '$sorted_maps[$i]'\n";
442: }
443: $scripttag .= <<"EXTRA";
444:
445: function recurseFolders() {
446: if (initial.length > 0) {
447: for (var i=0; i<initial.length; i++) {
448: containerCheck(document.$formname,initial[i]);
449: }
450: }
451: return;
452: }
453:
454: EXTRA
455: }
456:
457: $scripttag .= <<"END";
458:
459: function containerCheck(form,item) {
460: form.elements[item].checked = true;
461: if(Object.prototype.toString.call(parents[item]) === '[object Array]') {
462: if (parents[item].length > 0) {
463: for (var j=0; j<parents[item].length; j++) {
464: containerCheck(form,parents[item][j]);
465: }
466: }
467: }
468: }
469:
470: function containerUncheck(form,item) {
471: if(Object.prototype.toString.call(nesting[item]) === '[object Array]') {
472: if (nesting[item].length > 0) {
473: for (var i=0; i<nesting[item].length; i++) {
474: form.elements[nesting[item][i]].checked = false;
475: }
476: }
477: }
478: return;
479: }
480:
481: END
482:
483: if ($context eq 'examblock') {
484: $scripttag .= <<ENDEX;
485: function writeToOpener(maps,resources) {
486: var checkedmaps = '';
487: var checkedresources = '';
488: for (var i=0; i<document.$formname.archive.length; i++) {
489: if (document.$formname.archive[i].checked) {
490: var isResource = 1;
491: var include = 1;
492: var elemnum = i+1+$startcount;
493: if (Object.prototype.toString.call(parents[elemnum]) === '[object Array]') {
494: if (parents[elemnum].length > 0) {
495: isResource = 0;
496: }
497: }
498: if (isResource == 1) {
499: if (nesting[elemnum].length > 0) {
500: var lastelem = nesting[elemnum].length-1;
501: if (document.$formname.elements[nesting[elemnum][lastelem]].checked) {
502: include = 0;
503: }
504: }
505: }
506: if (include == 1) {
507: if (isResource == 1) {
508: checkedresources += document.$formname.archive[i].value+',';
509: } else {
510: checkedmaps += document.$formname.archive[i].value+',';
511: }
512: }
513: }
514: }
515: opener.document.getElementById(maps).value = checkedmaps;
516: opener.document.getElementById(resources).value = checkedresources;
517: window.close();
518: return false;
519: }
520:
521: ENDEX
522: }
523:
524: $scripttag .= '
525: // ]]>
526: </script>
527: ';
528: return $scripttag;
529: }
530:
531: sub get_navmap_object {
532: my ($crstype,$context) = @_;
533: my $navmap = Apache::lonnavmaps::navmap->new();
534: my $outcome;
535: if (!defined($navmap)) {
536: if ($context eq 'imsexport') {
537: $outcome = &Apache::loncommon::start_page('Export '.$crstype.' to IMS Package').
538: '<h2>'.&mt('IMS Export Failed').'</h2>';
539: } elsif ($context eq 'examblock') {
540: $outcome = &Apache::loncommon::start_page('Selection of Resources for Blocking',
541: undef,{'only_body' => 1,}).
542: '<h2>'.&mt('Resource Display Failed').'</h2>';
543: }
544: $outcome .= '<div class="LC_error">';
545: if ($crstype eq 'Community') {
546: $outcome .= &mt('Unable to retrieve information about community contents');
547: } else {
548: $outcome .= &mt('Unable to retrieve information about course contents');
549: }
550: $outcome .= '</div>';
551: if ($context eq 'imsexport') {
552: $outcome .= '<a href="/adm/coursedocs">';
553: if ($crstype eq 'Community') {
554: $outcome .= &mt('Return to Community Editor');
555: } else {
556: $outcome .= &mt('Return to Course Editor');
557: }
558: $outcome .= '</a>';
1.5 ! raeburn 559: &Apache::lonnet::logthis('IMS export failed - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'});
1.1 raeburn 560: } elsif ($context eq 'examblock') {
561: $outcome .= '<href="javascript:window.close();">'.&mt('Close window').'</a>';
562: }
563: return (undef,$outcome);
564: } else {
565: return ($navmap);
566: }
567: }
568:
569: 1;
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>