--- loncom/interface/loncommon.pm 2016/06/19 04:27:49 1.1246
+++ loncom/interface/loncommon.pm 2016/08/31 19:08:19 1.1252
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1246 2016/06/19 04:27:49 raeburn Exp $
+# $Id: loncommon.pm,v 1.1252 2016/08/31 19:08:19 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -1775,6 +1775,162 @@ RESIZE
}
sub colorfuleditor_js {
+ my $browse_or_search;
+ my $respath;
+ my ($cnum,$cdom) = &crsauthor_url();
+ if ($cnum) {
+ $respath = "/res/$cdom/$cnum/";
+ my %js_lt = &Apache::lonlocal::texthash(
+ sunm => 'Sub-directory name',
+ save => 'Save page to make this permanent',
+ );
+ &js_escape(\%js_lt);
+ $browse_or_search = <<"END";
+
+ function toggleChooser(form,element,titleid,only,search) {
+ var disp = 'none';
+ if (document.getElementById('chooser_'+element)) {
+ var curr = document.getElementById('chooser_'+element).style.display;
+ if (curr == 'none') {
+ disp='inline';
+ if (form.elements['chooser_'+element].length) {
+ for (var i=0; i
1) {
+ window['select1'+element+'_changed']();
+ }
+ }
+ }
+ document.getElementById('chooser_'+element+'_crsres').style.display = 'block';
+
+ }
+ if (document.getElementById('chooser_'+element+'_upload')) {
+ document.getElementById('chooser_'+element+'_upload').style.display = 'none';
+ if (document.getElementById('uploadcrsres_'+element)) {
+ document.getElementById('uploadcrsres_'+element).value = '';
+ }
+ }
+ return;
+ }
+
+ function toggleCrsUpload(form,element,numcrsdirs) {
+ if (document.getElementById('chooser_'+element+'_crsres')) {
+ document.getElementById('chooser_'+element+'_crsres').style.display = 'none';
+ }
+ if (document.getElementById('chooser_'+element+'_upload')) {
+ var curr = document.getElementById('chooser_'+element+'_upload').style.display;
+ if (curr == 'none') {
+ if (numcrsdirs) {
+ form.elements['crsauthorpath_'+element].selectedIndex = 0;
+ form.elements['newsubdir_'+element][0].checked = true;
+ toggleNewsubdir(form,element);
+ }
+ }
+ document.getElementById('chooser_'+element+'_upload').style.display = 'block';
+ }
+ return;
+ }
+
+ function toggleResImport(form,element) {
+ var choices = new Array('crsres','upload');
+ for (var i=0; i$js_lt{sunm}';
+ }
+ } else {
+ document.getElementById('newsubdirname_'+element).type = "hidden";
+ document.getElementById('newsubdirname_'+element).value = "";
+ document.getElementById('newsubdir_'+element).innerHTML = "";
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ function updateCrsFile(form,element) {
+ var directory = form.elements['coursepath_'+element];
+ var filename = form.elements['coursefile_'+element];
+ var path = directory.options[directory.selectedIndex].value;
+ var file = filename.options[filename.selectedIndex].value;
+ form.elements[element].value = '$respath';
+ if (path == '/') {
+ form.elements[element].value += file;
+ } else {
+ form.elements[element].value += path+'/'+file;
+ }
+ unClean();
+ if (document.getElementById('previewimg_'+element)) {
+ document.getElementById('previewimg_'+element).src = form.elements[element].value;
+ var newsrc = document.getElementById('previewimg_'+element).src;
+ }
+ if (document.getElementById('showimg_'+element)) {
+ document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})';
+ }
+ toggleChooser(form,element);
+ return;
+ }
+
+ function uploadDone(suffix,name) {
+ if (name) {
+ document.forms["lonhomework"].elements[suffix].value = name;
+ unClean();
+ toggleChooser(document.forms["lonhomework"],suffix);
+ }
+ }
+
+\$(document).ready(function(){
+
+ \$(document).delegate('form :submit', 'click', function( event ) {
+ if ( \$( this ).hasClass( "LC_uploadcrsres" ) ) {
+ var buttonId = this.id;
+ var suffix = buttonId.toString();
+ suffix = suffix.replace(/^crsupload_/,'');
+ event.preventDefault();
+ document.lonhomework.target = 'crsupload_target_'+suffix;
+ document.lonhomework.action = '/adm/coursepub?LC_uploadcrsres='+suffix;
+ \$(this.form).submit();
+ document.lonhomework.target = '';
+ if (document.getElementById('crsuploadto_'+suffix)) {
+ document.lonhomework.action = document.getElementById('crsuploadto_'+suffix).value;
+ }
+ return false;
+ }
+ });
+});
+END
+ }
return <<"COLORFULEDIT"
@@ -2009,6 +2165,130 @@ sub insert_folding_button {
value=\"".&mt('Hide')."\" onclick=\"fold_box('$curDepth','$lastresource')\">";
}
+sub crsauthor_url {
+ my ($url) = @_;
+ if ($url eq '') {
+ $url = $ENV{'REQUEST_URI'};
+ }
+ my ($cnum,$cdom);
+ if ($env{'request.course.id'}) {
+ my ($audom,$auname) = ($url =~ m{^/priv/($match_domain)/($match_name)/});
+ if ($audom ne '' && $auname ne '') {
+ if (($env{'course.'.$env{'request.course.id'}.'.num'} eq $auname) &&
+ ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $audom)) {
+ $cnum = $auname;
+ $cdom = $audom;
+ }
+ }
+ }
+ return ($cnum,$cdom);
+}
+
+sub import_crsauthor_form {
+ my ($form,$firstselectname,$secondselectname,$onchangefirst,$only,$suffix) = @_;
+ return (0) unless ($env{'request.course.id'});
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $crshome = $env{'course.'.$env{'request.course.id'}.'.home'};
+ return (0) unless (($cnum ne '') && ($cdom ne ''));
+ my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
+ my @ids=&Apache::lonnet::current_machine_ids();
+ my ($output,$is_home,$relpath,%subdirs,%files,%selimport_menus);
+
+ if (grep(/^\Q$crshome\E$/,@ids)) {
+ $is_home = 1;
+ }
+ $relpath = "/priv/$cdom/$cnum";
+ &Apache::lonnet::recursedirs($is_home,'priv',$londocroot,$relpath,'',\%subdirs,\%files);
+ my %lt = &Apache::lonlocal::texthash (
+ fnam => 'Filename',
+ dire => 'Directory',
+ );
+ my $numdirs = scalar(keys(%files));
+ my (%possexts,$singledir,@singledirfiles);
+ if ($only) {
+ map { $possexts{$_} = 1; } split(/\s*,\s*/,$only);
+ }
+ my (%nonemptydirs,$possdirs);
+ if ($numdirs > 1) {
+ my @order;
+ foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) {
+ if (ref($files{$key}) eq 'HASH') {
+ my $shown = $key;
+ if ($key eq '') {
+ $shown = '/';
+ }
+ my @ordered = ();
+ foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$key}}))) {
+ if ($only) {
+ my ($ext) = ($file =~ /\.([^.]+)$/);
+ unless ($possexts{lc($ext)}) {
+ next;
+ }
+ }
+ $selimport_menus{$key}->{'select2'}->{$file} = $file;
+ push(@ordered,$file);
+ }
+ if (@ordered) {
+ push(@order,$key);
+ $nonemptydirs{$key} = 1;
+ $selimport_menus{$key}->{'text'} = $shown;
+ $selimport_menus{$key}->{'default'} = '';
+ $selimport_menus{$key}->{'select2'}->{''} = '';
+ $selimport_menus{$key}->{'order'} = \@ordered;
+ }
+ }
+ }
+ $possdirs = scalar(keys(%nonemptydirs));
+ if ($possdirs > 1) {
+ my @order = sort { lc($a) cmp lc($b) } (keys(%nonemptydirs));
+ $output = $lt{'dire'}.
+ &linked_select_forms($form,'
'.
+ $lt{'fnam'},'',
+ $firstselectname,$secondselectname,
+ \%selimport_menus,\@order,
+ $onchangefirst,'',$suffix).'
';
+ } elsif ($possdirs == 1) {
+ $singledir = (keys(%nonemptydirs))[0];
+ if (ref($selimport_menus{$singledir}->{'order'}) eq 'ARRAY') {
+ @singledirfiles = @{$selimport_menus{$singledir}->{'order'}};
+ }
+ delete($selimport_menus{$singledir});
+ }
+ } elsif ($numdirs == 1) {
+ $singledir = (keys(%files))[0];
+ foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$singledir}}))) {
+ if ($only) {
+ my ($ext) = ($file =~ /\.([^.]+)$/);
+ unless ($possexts{lc($ext)}) {
+ next;
+ }
+ }
+ push(@singledirfiles,$file);
+ }
+ if (@singledirfiles) {
+ $possdirs == 1;
+ }
+ }
+ if (($possdirs == 1) && (@singledirfiles)) {
+ my $showdir = $singledir;
+ if ($singledir eq '') {
+ $showdir = '/';
+ }
+ $output = $lt{'dire'}.
+ '
'.
+ $lt{'fnam'}.'
'."\n";
+ }
+ return ($possdirs,$output);
+}
+
=pod
=head1 Excel and CSV file utility routines
@@ -3874,7 +4154,11 @@ category
sub filecategorytypes {
my ($cat) = @_;
- return @{$category_extensions{lc($cat)}};
+ if (ref($category_extensions{lc($cat)}) eq 'ARRAY') {
+ return @{$category_extensions{lc($cat)}};
+ } else {
+ return ();
+ }
}
=pod
@@ -5022,7 +5306,7 @@ sub check_ip_acc {
return 1;
}
my $allowed;
- my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip;
+ my $ip=$ENV{'REMOTE_ADDR'} || $clientip || $env{'request.host'};
my $name;
my %access = (
@@ -5429,7 +5713,7 @@ sub CSTR_pageheader {
my ($crsauthor,$title);
if (($env{'request.course.id'}) &&
($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) &&
- ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname)) {
+ ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) {
$crsauthor = 1;
$title = &mt('Course Authoring Space');
} else {
@@ -10341,7 +10625,9 @@ reservable_now - ref to hash of student_
Keys in inner hash are:
(a) symb: either blank or symb to which slot use is restricted.
- (b) endreserve: end date of reservation period.
+ (b) endreserve: end date of reservation period.
+ (c) uniqueperiod: start,end dates when slot is to be uniquely
+ selected.
sorted_future - ref to array of student_schedulable slots reservable in
the future, ordered by start date of reservation period.
@@ -10351,7 +10637,9 @@ future_reservable - ref to hash of stude
Keys in inner hash are:
(a) symb: either blank or symb to which slot use is restricted.
- (b) startreserve: start date of reservation period.
+ (b) startreserve: start date of reservation period.
+ (c) uniqueperiod: start,end dates when slot is to be uniquely
+ selected.
=back
@@ -10427,6 +10715,10 @@ sub get_future_slots {
my $startreserve = $slots{$slot}->{'startreserve'};
my $endreserve = $slots{$slot}->{'endreserve'};
my $symb = $slots{$slot}->{'symb'};
+ my $uniqueperiod;
+ if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') {
+ $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}});
+ }
if (($startreserve < $now) &&
(!$endreserve || $endreserve > $now)) {
my $lastres = $endreserve;
@@ -10435,13 +10727,15 @@ sub get_future_slots {
}
$reservable_now{$slot} = {
symb => $symb,
- endreserve => $lastres
+ endreserve => $lastres,
+ uniqueperiod => $uniqueperiod,
};
} elsif (($startreserve > $now) &&
(!$endreserve || $endreserve > $startreserve)) {
$future_reservable{$slot} = {
symb => $symb,
- startreserve => $startreserve
+ startreserve => $startreserve,
+ uniqueperiod => $uniqueperiod,
};
}
}
@@ -10599,7 +10893,23 @@ sub get_env_multiple {
return(@values);
}
+# Looks at given dependencies, and returns something depending on the context.
+# For coursedocs paste, returns (undef, $counter, $numpathchg, \%existing).
+# For syllabus rewrites, returns (undef, $counter, $numpathchg, \%existing, \%mapping).
+# For all other contexts, returns ($output, $counter, $numpathchg).
+# $output: string with the HTML output. Can contain missing dependencies with an upload form, existing dependencies, and dependencies no longer in use.
+# $counter: integer with the number of existing dependencies when no HTML output is returned, and the number of missing dependencies when an HTML output is returned.
+# $numpathchg: integer with the number of cleaned up dependency paths.
+# \%existing: hash reference clean path -> 1 only for existing dependencies.
+# \%mapping: hash reference clean path -> original path for all dependencies.
+# @param {string} actionurl - The path to the handler, indicative of the context.
+# @param {string} state - Can contain HTML with hidden inputs that will be added to the output form.
+# @param {hash reference} allfiles - List of file info from lonnet::extract_embedded_items
+# @param {hash reference} codebase - undef, not modified by lonnet::extract_embedded_items ?
+# @param {hash reference} args - More parameters ! Possible keys: error_on_invalid_names (boolean), ignore_remote_references (boolean), current_path (string), docs_url (string), docs_title (string), context (string)
+# @return {Array} - array depending on the context (not a reference)
sub ask_for_embedded_content {
+ # NOTE: documentation was added afterwards, it could be wrong
my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges,
%currsubfile,%unused,$rem);
@@ -10615,6 +10925,9 @@ sub ask_for_embedded_content {
my $heading = &mt('Upload embedded files');
my $buttontext = &mt('Upload');
+ # fills these variables based on the context:
+ # $navmap, $cdom, $cnum, $udom, $uname, $url, $toplevel, $getpropath,
+ # $path, $fileloc, $title, $rem, $filename
if ($env{'request.course.id'}) {
if ($actionurl eq '/adm/dependencies') {
$navmap = Apache::lonnavmaps::navmap->new();
@@ -10699,6 +11012,16 @@ sub ask_for_embedded_content {
$fileloc = &Apache::lonnet::filelocation('',$toplevel).'/';
$fileloc =~ s{^/}{};
}
+
+ # parses the dependency paths to get some info
+ # fills $newfiles, $mapping, $subdependencies, $dependencies
+ # $newfiles: hash URL -> 1 for new files or external URLs
+ # (will be completed later)
+ # $mapping:
+ # for external URLs: external URL -> external URL
+ # for relative paths: clean path -> original path
+ # $subdependencies: hash clean path -> clean file name -> 1 for relative paths in subdirectories
+ # $dependencies: hash clean or not file name -> 1 for relative paths not in subdirectories
foreach my $file (keys(%{$allfiles})) {
my $embed_file;
if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) {
@@ -10741,6 +11064,19 @@ sub ask_for_embedded_content {
}
}
}
+
+ # looks for all existing files in dependency subdirectories (from $subdependencies filled above)
+ # and lists
+ # fills $currsubfile, $pathchanges, $existing, $numexisting, $newfiles, $unused
+ # $currsubfile: hash clean path -> file name -> 1 for all existing files in the path
+ # $pathchanges: hash clean path -> 1 if the file in subdirectory exists and
+ # the path had to be cleaned up
+ # $existing: hash clean path -> 1 if the file exists
+ # $numexisting: number of keys in $existing
+ # $newfiles: updated with clean path -> 1 for files in subdirectories that do not exist
+ # $unused: only for /adm/dependencies, hash clean path -> 1 for existing files in
+ # dependency subdirectories that are
+ # not listed as dependencies, with some exceptions using $rem
my $dirptr = 16384;
foreach my $path (keys(%subdependencies)) {
$currsubfile{$path} = {};
@@ -10816,6 +11152,9 @@ sub ask_for_embedded_content {
}
}
}
+
+ # fills $currfile, hash file name -> 1 or [$size,$mtime]
+ # for files in $url or $fileloc (target directory) in some contexts
my %currfile;
if (($actionurl eq '/adm/portfolio') ||
($actionurl eq '/adm/coursegrp_portfolio')) {
@@ -10854,6 +11193,8 @@ sub ask_for_embedded_content {
}
}
}
+ # updates $pathchanges, $existing, $numexisting, $newfiles and $unused for files that
+ # are not in subdirectories, using $currfile
foreach my $file (keys(%dependencies)) {
if (exists($currfile{$file})) {
unless ($mapping{$file} eq $file) {
@@ -10882,6 +11223,8 @@ sub ask_for_embedded_content {
$unused{$file} = 1;
}
}
+
+ # returns some results for coursedocs paste and syllabus rewrites ($output is undef)
if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
($args->{'context'} eq 'paste')) {
$counter = scalar(keys(%existing));
@@ -10893,6 +11236,12 @@ sub ask_for_embedded_content {
$numpathchg = scalar(keys(%pathchanges));
return ($output,$counter,$numpathchg,\%existing,\%mapping);
}
+
+ # returns HTML otherwise, with dependency results and to ask for more uploads
+
+ # $upload_output: missing dependencies (with upload form)
+ # $modify_output: uploaded dependencies (in use)
+ # $delete_output: files no longer in use (unused files are not listed for londocs, bug?)
foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) {
if ($actionurl eq '/adm/dependencies') {
next if ($embed_file =~ m{^\w+://});
@@ -15152,6 +15501,9 @@ sub construct_course {
if ($args->{'setcontent'}) {
$cenv{'question.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
}
+ if ($args->{'setcomment'}) {
+ $cenv{'comment.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
+ }
}
if ($args->{'reshome'}) {
$cenv{'reshome'}=$args->{'reshome'}.'/';