--- loncom/interface/loncourserespicker.pm 2013/01/15 17:39:58 1.5 +++ loncom/interface/loncourserespicker.pm 2024/11/24 04:17:50 1.18 @@ -1,6 +1,6 @@ # The LearningOnline Network # -# $Id: loncourserespicker.pm,v 1.5 2013/01/15 17:39:58 raeburn Exp $ +# $Id: loncourserespicker.pm,v 1.18 2024/11/24 04:17:50 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -37,7 +37,9 @@ loncourserespicker provides an interface resources are to be either: (a) exported to an IMS Content Package -(b) subject to access blocking for the duriation of an exam/quiz. +(b) subject to access blocking for the duration of an exam/quiz. +(c) dumped to an Authoring Space +(d) receive shortened URLs to be used when deep-linking into a course =head1 DESCRIPTION @@ -50,8 +52,8 @@ window used to display the Course Conten =head1 OVERVIEW The main subroutine: &create_picker() will display the hierarchy of folders, -sub-folders, and resources in the Main Course Documents area. Items can be -selected using checkboxes, and/or a "Check All" button. Selection of a folder +sub-folders, and resources in the Main Content area. Items can be selected +using checkboxes, and/or a "Check All" button. Selection of a folder causes the contents of the folder to also be selected automatically. The propagation of check status is recursive into sub-folders. Likewise, if an item deep in a nested set of folders and sub-folders is unchecked, the @@ -59,11 +61,12 @@ uncheck will propagate up through the hi a higher level to become unchecked. There is a submit button, which will be named differently according to the -content in which resource/folder selection is being made. +context in which resource/folder selection is being made. -The two contexts currently supported are: IMS export and selection of +The four contexts currently supported are: IMS export, selection of content to be subject to access restructions for the duration of an -exam. +exam, selection of items for dumping to an Authoring Space, and +display or creation of shortened URLs for deep-linking, =head1 INTERNAL SUBROUTINES @@ -74,12 +77,13 @@ select items. Checking a folder causes within the folder. Unchecking a resource causing unchecking of folders containing the item back up to the top level. -Inputs: 7. +Inputs: 11. - $navmap -- Reference to LON-CAPA navmap object (encapsulates information about resources in the course). - $context -- Context in which course resource selection is being made. - Currently imsexport and examblock are supported. + Currently imsexport, examblock, dumpdocs, and shorturls + are supported. - $formname -- Name of the form in the window from which the pop-up used to select course items was launched. @@ -95,6 +99,24 @@ Inputs: 7. - $block -- An internal ID (integer) used to track which exam block currently being configured. + - $preamble -- HTML form elements used to select Authoring Space + if more than one available, and also set name of 'Folder + in Authoring Space' where content will be dumped, when + context is 'dumpdocs'. + + - $numhome -- number of possible Authoring Spaces where content could + be dumped when context is 'dumpdocs'. + + - $uploadedfiles -- Reference to hash: keys are paths to files in + /home/httpd/lonUsers/$cdom/$1/$2/$3/$cnum/userfiles. + + - $tiny -- Reference to hash: keys are symbs of course items for which + shortened URLs have already been created. + + - $readonly -- if true, no "check all" or "uncheck all" buttons will + be displayed, and checkboxes will be disabled, if this + is for an exam block or for shortened URL creation. + Output: $output is the HTML mark-up for display/selection of content items in the pop-up window. @@ -111,7 +133,7 @@ Inputs: 7. - $numcount -- Total numer of folders and resources in course. - $context -- Context in which resources are being displayed - (imsexport or examblock). + (imsexport, examblock, dumpdocs or shorturls). - $formname -- Name of form. @@ -121,7 +143,7 @@ Inputs: 7. - $checked_maps -- Reference to array of folders currently checked. -Output: 1. Javascript (witthin tags. +Output: 1. Javascript (within tags. =item &get_navmap_object() @@ -132,7 +154,50 @@ no object instantiated. Inputs: 2. - $crstype -- Container type: Course or Community - - $context -- Context: imsexport or examblock + - $context -- Context: imsexport, examblock, dumpdocs, or shorturls + + +=item &clean() + +Takes incoming title and replaces non-alphanumeric characters with underscore, +so title can be used as suggested file name (with appended extension) for file +copied from course to Authoring Space. + + +=item &enumerate_course_contents() + +Create hashes of maps (for folders/pages) and symbs (for resources) in +a course, where keys are numbers (starting with 1) and values are +map url, or symb, for an iteration through the course, as seen by +a Course Coordinator. Used to generate numerical IDs to facilitate +(a) storage of lists of maps or resources to be blocked during an exam, +(b) processing selected form elements during dumping of selected course + content to Authoring Space. +(c) processing of checked checkboxes for creation of shortened URLs for + deep-linking to course content. + +Inputs: 7 + + $navmap - navmaps object + + $map_url - reference to hash to contain URLs of maps in course + + $resource_symb - reference to hash to contain symbs for + resources in course + + $title_ref - reference to hash containing titles for items in + course + + $context - examblock, dumpdocs or shorturls + + $cdom - course's domain + + $cnum - courseID + +Outputs: None + +Side Effects: $map_url and $resource_symb hashrefs are populated. + =over @@ -153,15 +218,25 @@ use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); sub create_picker { - my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block) = @_; + my ($navmap,$context,$formname,$crstype,$blockedmaps,$blockedresources,$block,$preamble, + $numhome,$uploadedfiles,$tiny,$readonly) = @_; return unless (ref($navmap)); - my ($it,$output,$numdisc,%maps,%resources,%discussiontime,%currmaps,%currresources); + my ($it,$output,$numdisc,%discussiontime,%currmaps,%currresources,%files, + %shorturls,$chkname); + $chkname = 'archive'; + if ($context eq 'shorturls') { + $chkname = 'addtiny'; + } $it = $navmap->getIterator(undef,undef,undef,1,undef,undef); if (ref($blockedmaps) eq 'HASH') { %currmaps = %{$blockedmaps}; } if (ref($blockedresources) eq 'HASH') { %currresources = %{$blockedresources}; + } elsif (ref($uploadedfiles) eq 'HASH') { + %files = %{$uploadedfiles}; + } elsif (ref($tiny) eq 'HASH') { + %shorturls = %{$tiny}; } my @checked_maps; my $curRes; @@ -174,42 +249,54 @@ sub create_picker { my %children = (); my %hierarchy = (); my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons"); - my $whitespace = + my $whitespace = ''; - my $onsubmit; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $crsprefix = &propath($cdom,$cnum).'/userfiles/'; + + my ($info,$display,$onsubmit,$togglebuttons,$disabled); if ($context eq 'examblock') { my $maps_elem = 'docs_maps_'.$block; my $res_elem = 'docs_resources_'.$block; $onsubmit = ' onsubmit="return writeToOpener('."'$maps_elem','$res_elem'".');"'; + $info = &mt('Items in '.lc($crstype).' for which access will be blocked.'); + if ($readonly) { + $disabled = ' disabled="disabled"'; + } } - my $display = - '
'."\n". - '

'; - if ($context eq 'imsexport') { - $display .= &mt('Choose which items you wish to export from your '. - $crstype.'.'); + if ($context eq 'dumpdocs') { + $info = ''. + &mt('Choose the uploaded course items and templated pages/problems to be copied to Authoring Space.'). + '

'; + $startcount = 3 + $numhome; + $onsubmit = ' onsubmit="return checkUnique(document.'.$formname.',document.'.$formname.'.'.$chkname.');"'; + } elsif ($context eq 'shorturls') { + $info = ''. + &mt('Choose the resource(s) and/or folder(s) from Main Content for which shortened URL(s) are needed.'). + '

'; + } elsif ($context eq 'imsexport') { + $info = &mt('Choose which items you wish to export from your '.$crstype.'.'); $startcount = 5; - } elsif ($context eq 'examblock') { - $display .= &mt('Items in '.lc($crstype).' for which access will be blocked.'); } - $display .= '

'; - if ($context eq 'imsexport') { - $display .= '
'."\n". - '
'. - ''.&mt('Content items').''."\n"; + if ($disabled) { + $togglebuttons = '
'; + } else { + $togglebuttons = ''. + '  '; } - $display .= - ''. - '  '; + $display = ''."\n"; if ($context eq 'imsexport') { - $display .= '
'; - %discussiontime = - &Apache::lonnet::dump('discussiontimes', - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'}); + $display .= $info. + '
'."\n". + '
'. + ''.&mt('Content items').''."\n". + $togglebuttons. + '
'; + %discussiontime = &Apache::lonnet::dump('discussiontimes',$cdom,$cnum); $numdisc = keys(%discussiontime); if ($numdisc > 0) { $display .= @@ -221,7 +308,15 @@ sub create_picker { ' onclick="javascript:uncheckAll(document.'.$formname.'.discussion)" />'. ''; } - $display .= '
'; + $display .= '
'; + } elsif (($context eq 'examblock') || ($context eq 'shorturls')) { + $display .= $info.$togglebuttons; + } elsif ($context eq 'dumpdocs') { + $display .= $preamble. + '
'. + '
'. + ''.&mt('Content to copy').(' 'x4).$togglebuttons.''. + $info; } my $lastcontainer = $startcount; $display .= &Apache::loncommon::start_data_table() @@ -233,12 +328,17 @@ sub create_picker { } } elsif ($context eq 'examblock') { $display .= ''.&mt('Access blocked?').''; + } elsif ($context eq 'dumpdocs') { + $display .= ''.&mt('Copy?').''. + ''.&mt("Title in $crstype").''. + ''.&mt('Internal Identifier').''. + ''.&mt('Save as ...').''; + } elsif ($context eq 'shorturls') { + $display .= ''.&mt('Tiny URL').''. + ''.&mt("Title in $crstype").''; } $display .= &Apache::loncommon::end_data_table_header_row(); while ($curRes = $it->next()) { - if (ref($curRes)) { - $count ++; - } if ($curRes == $it->BEGIN_MAP()) { $depth++; $parent{$depth} = $lastcontainer; @@ -250,33 +350,61 @@ sub create_picker { if (ref($curRes)) { my $symb = $curRes->symb(); my $ressymb = $symb; - if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) { + if ($context eq 'dumpdocs') { + next unless (($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(docs|supplemental|simplepage)}) || + ($curRes->src() =~ m{^\Q/uploaded/$cdom/$cnum/\E(default|supplemental)_\d+\.(sequence|page)}) || + ($curRes->src() eq '/res/lib/templates/simpleproblem.problem') || + ($curRes->src() =~ m{^/adm/$match_domain/$match_username/\d+/smppg})); + } elsif ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) { unless ($ressymb =~ m|adm/wrapper/adm|) { $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3.'/bulletinboard'; } } - my $currelem = $count+$boards+$startcount; - $display .= &Apache::loncommon::start_data_table_row(). - ''."\n". - 'is_sequence()) || ($curRes->is_page())) { $lastcontainer = $currelem; - $display .= 'onclick="javascript:checkFolder(this.form,'."'$currelem'".')" '; - my $mapurl = (&Apache::lonnet::decode_symb($symb))[2]; - if ($currmaps{$mapurl}) { - $display .= 'checked="checked"'; - push(@checked_maps,$currelem); + $mapurl = (&Apache::lonnet::decode_symb($symb))[2]; + $is_map = 1; + } + if ($context eq 'shorturls') { + if ($shorturls{$symb}) { + $display .= ' '."/tiny/$cdom/$shorturls{$symb}".''."\n"; + } else { + $display .= ''. + ' '."\n"; } } else { - if ($curRes->is_problem()) { - $numprobs ++; - } - $display .= 'onclick="javascript:checkResource(this.form,'."'$currelem'".')" '; - if ($currresources{$symb}) { - $display .= 'checked="checked"'; + $display .= 'is_problem()) { + $numprobs ++; + } + $display .= 'onclick="javascript:checkResource(document.'.$formname.','."'$currelem'".')" '; + if ($currresources{$symb}) { + $display .= 'checked="checked"'; + } } + $display .= $disabled.' />'."\n"; + } + if ($context eq 'dumpdocs') { + $display .= ''; + } elsif ($context eq 'shorturls') { + $display .= ''; } - $display .= ' />'."\n"; for (my $i=0; $i<$depth; $i++) { $display .= "$whitespace\n"; } @@ -303,7 +431,7 @@ sub create_picker { } } } - $display .= ' '.$curRes->title().''."\n"; + $display .= ' '.$curRes->title().$whitespace.''."\n"; if ($context eq 'imsexport') { # Existing discussion posts? @@ -315,6 +443,35 @@ sub create_picker { } elsif ($numdisc > 0) { $display .= ' '."\n"; } + } elsif ($context eq 'dumpdocs') { + my $src = $curRes->src(); + my ($filepath,$title); + if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E}) { + $filepath = &Apache::lonnet::filelocation('',$src); + $filepath =~ s/\Q$crsprefix\E//; + if ($curRes->is_map()) { + $title = $files{$filepath}; + } else { + $filepath =~ s{docs/}{}; + $title = $filepath; + $title =~ s{^(default|\d+)/\d*/?}{}; + } + } else { + $title = $curRes->title(); + $title =~ s{/}{_}g; + $title = &clean($title); + if ($src eq '/res/lib/templates/simpleproblem.problem') { + my ($map,$id,$res) = &Apache::lonnet::decode_symb($symb); + $map =~ s{^uploaded/$cdom/$cnum/}{}; + $filepath = $map.'_'.$id; + $title .= '.problem'; + } elsif ($src =~ m{^/adm/$match_domain/$match_username/(\d+)/smppg}) { + $filepath = 'smppage_'.$1.'.db'; + $title .= '.html'; + } + } + $display .= ''.$filepath.''. + ''."\n"; } $display .= &Apache::loncommon::end_data_table_row(); } @@ -332,24 +489,46 @@ sub create_picker { ' '.&mt('Text').'

'; } } - $display .= '

'; + my $numcount; if ($context eq 'imsexport') { $display .= + '

'. ''. ''; + &mt('Export').'" />

'; + $numcount = $count + $boards + $startcount; } elsif ($context eq 'examblock') { - $display .= - ''; - } - $display .= '

'; - my $numcount = $count + $boards + $startcount; - my $scripttag = + unless ($readonly) { + $display .= + '

'. + '

'; + } + $numcount = $count + $startcount; + } elsif ($context eq 'dumpdocs') { + $display .= '
'. + '
'. + '
'. + ''. + '
'; + $numcount = $count + $startcount; + } elsif ($context eq 'shorturls') { + unless ($readonly) { + $display .= + '

'. + '

'; + } + } + $display .= ''; + my $scripttag = &respicker_javascript($startcount,$numcount,$context,$formname,\%children, - \%hierarchy,\@checked_maps); + \%hierarchy,\@checked_maps,$numhome,$chkname); + if (($context eq 'dumpdocs') || ($context eq 'shorturls')) { + return $scripttag.$display; + } my ($title,$crumbs,$args); - if ($context eq 'imsexport') { + if ($context eq 'imsexport') { $title = 'Export '.$crstype.' to IMS Package'; } elsif ($context eq 'examblock') { $title = 'Resources with Access blocked'; @@ -361,6 +540,9 @@ sub create_picker { if ($context eq 'imsexport') { $output .= &Apache::lonhtmlcommon::breadcrumbs('IMS Export'). &Apache::londocs::startContentScreen('tools'); + } elsif ($context eq 'dumpdocs') { + $output .= &Apache::lonhtmlcommon::breadcrumbs('Copying to Authoring Space'). + &Apache::londocs::startContentScreen('tools'); } $output .= $display; if ($context eq 'examblock') { @@ -373,12 +555,8 @@ sub create_picker { sub respicker_javascript { my ($startcount,$numitems,$context,$formname,$children,$hierarchy, - $checked_maps) = @_; - return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH') - && (ref($checked_maps) eq 'ARRAY')); - my $scripttag = <<"START"; - +END + } + return unless ((ref($children) eq 'HASH') && (ref($hierarchy) eq 'HASH') + && (ref($checked_maps) eq 'ARRAY')); + my ($elem,$nested,$nameforelem); + if ($context eq 'dumpdocs') { + $elem='((parseInt(item)-'.$startcount.')*2)+'.$startcount; + $nested='((parseInt(nesting[item][i])-'.$startcount.')*2)+'.$startcount; + $nameforelem=$elem+1; + } else { + $elem='parseInt(item)'; + $nested='parseInt(nesting[item][i])'; + } + my $scripttag = <<"START"; +