--- loncom/lonnet/perl/lonnet.pm	2016/04/02 04:31:03	1.1305
+++ loncom/lonnet/perl/lonnet.pm	2016/09/27 18:04:52	1.1325
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1305 2016/04/02 04:31:03 raeburn Exp $
+# $Id: lonnet.pm,v 1.1325 2016/09/27 18:04:52 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -229,6 +229,48 @@ sub get_server_distarch {
     return;
 }
 
+sub get_servercerts_info {
+    my ($lonhost,$context) = @_;
+    my ($rep,$uselocal);
+    if (grep { $_ eq $lonhost } &current_machine_ids()) {
+        $uselocal = 1;
+    }
+    if (($context ne 'cgi') && ($uselocal)) {
+        my $distro = (split(/\:/,&get_server_distarch($lonhost)))[0];
+        if ($distro eq '') {
+            $uselocal = 0;
+        } elsif ($distro =~ /^(?:centos|redhat|scientific)(\d+)$/) {
+            if ($1 < 6) {
+                $uselocal = 0;
+            }
+        }
+    }
+    if ($uselocal) {
+        $rep = LONCAPA::Lond::server_certs(\%perlvar);
+    } else {
+        $rep=&reply('servercerts',$lonhost);
+    }
+    my ($result,%returnhash);
+    if (defined($lonhost)) {
+        if (!defined(&hostname($lonhost))) {
+            return;
+        }
+    }
+    if (($rep=~/^(refused|rejected|error)/) || ($rep eq 'con_lost') ||
+        ($rep eq 'unknown_cmd')) {
+        $result = $rep;
+    } else {
+        $result = 'ok';
+        my @pairs=split(/\&/,$rep);
+        foreach my $item (@pairs) {
+            my ($key,$value)=split(/=/,$item,2);
+            my $what = &unescape($key);
+            $returnhash{$what}=&thaw_unescape($value);
+        }
+    }
+    return ($result,\%returnhash);
+}
+
 sub get_server_loncaparev {
     my ($dom,$lonhost,$ignore_cache,$caller) = @_;
     if (defined($lonhost)) {
@@ -1288,7 +1330,7 @@ sub check_loadbalancing {
     my $uintdom = &Apache::lonnet::internet_dom($uprimary_id);
     my $intdom = &Apache::lonnet::internet_dom($lonhost);
     my $serverhomedom = &host_domain($lonhost);
-
+    my $domneedscache;
     my $cachetime = 60*60*24;
 
     if (($uintdom ne '') && ($uintdom eq $intdom)) {
@@ -1303,6 +1345,8 @@ sub check_loadbalancing {
             &Apache::lonnet::get_dom('configuration',['loadbalancing'],$dom_in_use);
         if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
             $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+        } else {
+            $domneedscache = $dom_in_use;
         }
     }
     if (ref($result) eq 'HASH') {
@@ -1361,7 +1405,9 @@ sub check_loadbalancing {
             my %domconfig =
                 &Apache::lonnet::get_dom('configuration',['loadbalancing'],$serverhomedom);
             if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
-                $result = &do_cache_new('loadbalancing',$dom_in_use,$domconfig{'loadbalancing'},$cachetime);
+                $result = &do_cache_new('loadbalancing',$serverhomedom,$domconfig{'loadbalancing'},$cachetime);
+            } else {
+                $domneedscache = $serverhomedom;
             }
         }
         if (ref($result) eq 'HASH') {
@@ -1381,12 +1427,21 @@ sub check_loadbalancing {
                 $is_balancer = 1;
                 $offloadto = &this_host_spares($dom_in_use);
             }
+            unless (defined($cached)) {
+                $domneedscache = $serverhomedom;
+            }
         }
     } else {
         if ($perlvar{'lonBalancer'} eq 'yes') {
             $is_balancer = 1;
             $offloadto = &this_host_spares($dom_in_use);
         }
+        unless (defined($cached)) {
+            $domneedscache = $serverhomedom;
+        }
+    }
+    if ($domneedscache) {
+        &do_cache_new('loadbalancing',$domneedscache,$is_balancer,$cachetime);
     }
     if ($is_balancer) {
         my $lowest_load = 30000;
@@ -1895,7 +1950,7 @@ sub retrieve_inst_usertypes {
 
 sub is_domainimage {
     my ($url) = @_;
-    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+-) {
+    if ($url=~m-^/+res/+($match_domain)/+\1\-domainconfig/+(img|logo|domlogo)/+[^/]-) {
         if (&domain($1) ne '') {
             return '1';
         }
@@ -2189,7 +2244,8 @@ sub get_domain_defaults {
                                   'requestcourses','inststatus',
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
-                                  'coursecategories'],$domain);
+                                  'coursecategories','ssl','autoenroll',
+                                  'trust'],$domain);
     my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2315,10 +2371,45 @@ sub get_domain_defaults {
             $domdefaults{'catunauth'} = $domconfig{'coursecategories'}{'unauth'};
         }
     }
+    if (ref($domconfig{'ssl'}) eq 'HASH') {
+        if (ref($domconfig{'ssl'}{'replication'}) eq 'HASH') {
+            $domdefaults{'replication'} = $domconfig{'ssl'}{'replication'};
+        }
+        if (ref($domconfig{'ssl'}{'connect'}) eq 'HASH') {
+            $domdefaults{'connect'} = $domconfig{'ssl'}{'connect'};
+        }
+    }
+    if (ref($domconfig{'trust'}) eq 'HASH') {
+        my @prefixes = qw(content shared enroll othcoau coaurem domroles catalog reqcrs msg);
+        foreach my $prefix (@prefixes) {
+            if (ref($domconfig{'trust'}{$prefix}) eq 'HASH') {
+                $domdefaults{'trust'.$prefix} = $domconfig{'trust'}{$prefix};
+            }
+        }
+    }
+    if (ref($domconfig{'autoenroll'}) eq 'HASH') {
+        $domdefaults{'autofailsafe'} = $domconfig{'autoenroll'}{'autofailsafe'};
+    }
     &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime);
     return %domdefaults;
 }
 
+sub course_portal_url {
+    my ($cnum,$cdom) = @_;
+    my $chome = &homeserver($cnum,$cdom);
+    my $hostname = &hostname($chome);
+    my $protocol = $protocol{$chome};
+    $protocol = 'http' if ($protocol ne 'https');
+    my %domdefaults = &get_domain_defaults($cdom);
+    my $firsturl;
+    if ($domdefaults{'portal_def'}) {
+        $firsturl = $domdefaults{'portal_def'};
+    } else {
+        $firsturl = $protocol.'://'.$hostname;
+    }
+    return $firsturl;
+}
+
 # --------------------------------------------------- Assign a key to a student
 
 sub assign_access_key {
@@ -2555,21 +2646,23 @@ sub make_key {
 sub devalidate_cache_new {
     my ($name,$id,$debug) = @_;
     if ($debug) { &Apache::lonnet::logthis("deleting $name:$id"); }
+    my $remembered_id=$name.':'.$id;
     $id=&make_key($name,$id);
     $memcache->delete($id);
-    delete($remembered{$id});
-    delete($accessed{$id});
+    delete($remembered{$remembered_id});
+    delete($accessed{$remembered_id});
 }
 
 sub is_cached_new {
     my ($name,$id,$debug) = @_;
-    $id=&make_key($name,$id);
-    if (exists($remembered{$id})) {
-	if ($debug) { &Apache::lonnet::logthis("Early return $id of $remembered{$id} "); }
-	$accessed{$id}=[&gettimeofday()];
+    my $remembered_id=$name.':'.$id; # this is to avoid make_key (which is slow) whenever possible
+    if (exists($remembered{$remembered_id})) {
+	if ($debug) { &Apache::lonnet::logthis("Early return $remembered_id of $remembered{$remembered_id} "); }
+	$accessed{$remembered_id}=[&gettimeofday()];
 	$hits++;
-	return ($remembered{$id},1);
+	return ($remembered{$remembered_id},1);
     }
+    $id=&make_key($name,$id);
     my $value = $memcache->get($id);
     if (!(defined($value))) {
 	if ($debug) { &Apache::lonnet::logthis("getting $id is not defined"); }
@@ -2579,13 +2672,14 @@ sub is_cached_new {
 	if ($debug) { &Apache::lonnet::logthis("getting $id is __undef__"); }
 	$value=undef;
     }
-    &make_room($id,$value,$debug);
+    &make_room($remembered_id,$value,$debug);
     if ($debug) { &Apache::lonnet::logthis("getting $id is $value"); }
     return ($value,1);
 }
 
 sub do_cache_new {
     my ($name,$id,$value,$time,$debug) = @_;
+    my $remembered_id=$name.':'.$id;
     $id=&make_key($name,$id);
     my $setvalue=$value;
     if (!defined($setvalue)) {
@@ -2601,17 +2695,17 @@ sub do_cache_new {
 	$memcache->disconnect_all();
     }
     # need to make a copy of $value
-    &make_room($id,$value,$debug);
+    &make_room($remembered_id,$value,$debug);
     return $value;
 }
 
 sub make_room {
-    my ($id,$value,$debug)=@_;
+    my ($remembered_id,$value,$debug)=@_;
 
-    $remembered{$id}= (ref($value)) ? &Storable::dclone($value)
+    $remembered{$remembered_id}= (ref($value)) ? &Storable::dclone($value)
                                     : $value;
     if ($to_remember<0) { return; }
-    $accessed{$id}=[&gettimeofday()];
+    $accessed{$remembered_id}=[&gettimeofday()];
     if (scalar(keys(%remembered)) <= $to_remember) { return; }
     my $to_kick;
     my $max_time=0;
@@ -4029,10 +4123,19 @@ sub flushcourselogs {
         delete $domainrolehash{$entry};
     }
     foreach my $dom (keys(%domrolebuffer)) {
-	my %servers = &get_servers($dom,'library');
+	my %servers;
+	if (defined(&domain($dom,'primary'))) {
+	    my $primary=&domain($dom,'primary');
+	    my $hostname=&hostname($primary);
+	    $servers{$primary} = $hostname;
+	} else { 
+	    %servers = &get_servers($dom,'library');
+	}
 	foreach my $tryserver (keys(%servers)) {
-	    unless (&reply('domroleput:'.$dom.':'.
-			   $domrolebuffer{$dom},$tryserver) eq 'ok') {
+	    if (&reply('domroleput:'.$dom.':'.
+		       $domrolebuffer{$dom},$tryserver) eq 'ok') {
+		last;
+	    } else {  
 		&logthis('Put of domain roles failed for '.$dom.' and  '.$tryserver);
 	    }
         }
@@ -4633,9 +4736,10 @@ my %cachedtimes=();
 my $cachedtime='';
 
 sub load_all_first_access {
-    my ($uname,$udom)=@_;
+    my ($uname,$udom,$ignorecache)=@_;
     if (($cachedkey eq $uname.':'.$udom) &&
-        (abs($cachedtime-time)<5) && (!$env{'form.markaccess'})) {
+        (abs($cachedtime-time)<5) && (!$env{'form.markaccess'}) &&
+        (!$ignorecache)) {
         return;
     }
     $cachedtime=time;
@@ -4644,7 +4748,7 @@ sub load_all_first_access {
 }
 
 sub get_first_access {
-    my ($type,$argsymb,$argmap)=@_;
+    my ($type,$argsymb,$argmap,$ignorecache)=@_;
     my ($symb,$courseid,$udom,$uname)=&whichuser();
     if ($argsymb) { $symb=$argsymb; }
     my ($map,$id,$res)=&decode_symb($symb);
@@ -4656,7 +4760,7 @@ sub get_first_access {
     } else {
 	$res=$symb;
     }
-    &load_all_first_access($uname,$udom);
+    &load_all_first_access($uname,$udom,$ignorecache);
     return $cachedtimes{"$courseid\0$res"};
 }
 
@@ -7332,7 +7436,7 @@ sub constructaccess {
     my ($ownername,$ownerdomain,$ownerhome);
 
     ($ownerdomain,$ownername) =
-        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)(?:/daxepage|/daxeopen)?/priv/($match_domain)/($match_username)/});
+        ($url=~ m{^(?:\Q$perlvar{'lonDocRoot'}\E|)(?:/daxepage|/daxeopen)?/priv/($match_domain)/($match_username)(?:/|$)});
 
 # The URL does not really point to any authorspace, forget it
     unless (($ownername) && ($ownerdomain)) { return ''; }
@@ -7353,6 +7457,15 @@ sub constructaccess {
             $ownerhome = &homeserver($ownername,$ownerdomain);
             return ($ownername,$ownerdomain,$ownerhome);
         }
+        if ($env{'request.course.id'}) {
+            if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) &&
+                ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) {
+                if (&allowed('mdc',$env{'request.course.id'})) {
+                    $ownerhome = $env{'course.'.$env{'request.course.id'}.'.home'};
+                    return ($ownername,$ownerdomain,$ownerhome);
+                }
+            }
+        }
     }
 
 # We don't have any access right now. If we are not possibly going to do anything about this,
@@ -7505,8 +7618,8 @@ sub get_commblock_resources {
                             }
                         }
                     }
-                    if ($interval[0] =~ /^\d+/) {
-                        my ($timelimit) = split(/_/,$interval[0]);
+                    if ($interval[0] =~ /^(\d+)/) {
+                        my $timelimit = $1; 
                         my $first_access;
                         if ($type eq 'resource') {
                             $first_access=&get_first_access($interval[1],$item);
@@ -7785,10 +7898,12 @@ sub update_allusers_table {
 
 sub fetch_enrollment_query {
     my ($context,$affiliatesref,$replyref,$dom,$cnum) = @_;
-    my $homeserver;
+    my ($homeserver,$sleep,$loopmax);
     my $maxtries = 1;
     if ($context eq 'automated') {
         $homeserver = $perlvar{'lonHostID'};
+        $sleep = 2;
+        $loopmax = 100;
         $maxtries = 10; # will wait for up to 2000s for retrieval of classlist data before timeout
     } else {
         $homeserver = &homeserver($cnum,$dom);
@@ -7806,17 +7921,17 @@ sub fetch_enrollment_query {
         &logthis('fetch_enrollment_query: invalid queryid: '.$queryid.' for host: '.$host.' and homeserver: '.$homeserver.' context: '.$context.' '.$cnum); 
         return 'error: '.$queryid;
     }
-    my $reply = &get_query_reply($queryid);
+    my $reply = &get_query_reply($queryid,$sleep,$loopmax);
     my $tries = 1;
     while (($reply=~/^timeout/) && ($tries < $maxtries)) {
-        $reply = &get_query_reply($queryid);
+        $reply = &get_query_reply($queryid,$sleep,$loopmax);
         $tries ++;
     }
     if ( ($reply =~/^timeout/) || ($reply =~/^error/) ) {
         &logthis('fetch_enrollment_query error: '.$reply.' for '.$dom.' '.$env{'user.name'}.' for '.$queryid.' context: '.$context.' '.$cnum.' maxtries: '.$maxtries.' tries: '.$tries);
     } else {
         my @responses = split(/:/,$reply);
-        if ($homeserver eq $perlvar{'lonHostID'}) {
+        if (grep { $_ eq $homeserver } &current_machine_ids()) {
             foreach my $line (@responses) {
                 my ($key,$value) = split(/=/,$line,2);
                 $$replyref{$key} = $value;
@@ -7851,11 +7966,17 @@ sub fetch_enrollment_query {
 }
 
 sub get_query_reply {
-    my $queryid=shift;
+    my ($queryid,$sleep,$loopmax) = @_;;
+    if (($sleep eq '') || ($sleep !~ /^\d+\.?\d*$/)) {
+        $sleep = 0.2;
+    }
+    if (($loopmax eq '') || ($loopmax =~ /\D/)) {
+        $loopmax = 100;
+    }
     my $replyfile=LONCAPA::tempdir().$queryid;
     my $reply='';
-    for (1..100) {
-	sleep(0.2);
+    for (1..$loopmax) {
+	sleep($sleep);
         if (-e $replyfile.'.end') {
 	    if (open(my $fh,$replyfile)) {
 		$reply = join('',<$fh>);
@@ -8279,8 +8400,30 @@ sub auto_crsreq_update {
 }
 
 sub auto_export_grades {
-    my ($cnum,$cdom,$gradesref) = @_;
-    return;
+    my ($cdom,$cnum,$inforef,$gradesref) = @_;
+    my ($homeserver,%exportresponse);
+    if ($cdom =~ /^$match_domain$/) {
+        $homeserver = &domain($cdom,'primary');
+    }
+    unless (($homeserver eq 'no_host') || ($homeserver eq '')) {
+        my $info;
+        if (ref($inforef) eq 'HASH') {
+            $info = &freeze_escape($inforef);
+        }
+        if (ref($gradesref) eq 'HASH') {
+            my $grades = &freeze_escape($gradesref);
+            my $response=&reply('encrypt:autoexportgrades:'.$cdom.':'.$cnum.':'.
+                                $info.':'.$grades,$homeserver);
+            unless ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/) {
+                my @items = split(/&/,$response);
+                foreach my $item (@items) {
+                    my ($key,$value) = split('=',$item);
+                    $exportresponse{&unescape($key)} = &thaw_unescape($value);
+                }
+            }
+        }
+    }
+    return \%exportresponse;
 }
 
 sub check_instcode_cloning {
@@ -9027,7 +9170,7 @@ sub modifyuser {
 sub modifystudent {
     my ($udom,$uname,$uid,$umode,$upass,$first,$middle,$last,$gene,$usec,
         $end,$start,$forceid,$desiredhome,$email,$type,$locktype,$cid,
-        $selfenroll,$context,$inststatus,$credits)=@_;
+        $selfenroll,$context,$inststatus,$credits,$instsec)=@_;
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
 	    return 'not_in_class';
@@ -9043,13 +9186,13 @@ sub modifystudent {
     $uid = undef if (!$forceid);
     $reply = &modify_student_enrollment($udom,$uname,$uid,$first,$middle,$last,
                                         $gene,$usec,$end,$start,$type,$locktype,
-                                        $cid,$selfenroll,$context,$credits);
+                                        $cid,$selfenroll,$context,$credits,$instsec);
     return $reply;
 }
 
 sub modify_student_enrollment {
     my ($udom,$uname,$uid,$first,$middle,$last,$gene,$usec,$end,$start,$type,
-        $locktype,$cid,$selfenroll,$context,$credits) = @_;
+        $locktype,$cid,$selfenroll,$context,$credits,$instsec) = @_;
     my ($cdom,$cnum,$chome);
     if (!$cid) {
 	unless ($cid=$env{'request.course.id'}) {
@@ -9096,7 +9239,7 @@ sub modify_student_enrollment {
     my %old_entry = &Apache::lonnet::get('classlist',[$user],$cdom,$cnum);
     my $reply=cput('classlist',
 		   {$user => 
-			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits) },
+			join(':',$end,$start,$uid,$usec,$fullname,$type,$locktype,$credits,$instsec) },
 		   $cdom,$cnum);
     if (($reply eq 'ok') || ($reply eq 'delayed')) {
         &devalidate_getsection_cache($udom,$uname,$cid);
@@ -10082,7 +10225,24 @@ sub dirlist {
             foreach my $user (sort(keys(%allusers))) {
                 push(@alluserslist,$user.'&user');
             }
-            return (\@alluserslist);
+
+            if (!%listerror) {
+                # no errors
+                return (\@alluserslist);
+            } elsif (scalar(keys(%servers)) == 1) {
+                # one library server, one error 
+                my ($key) = keys(%listerror);
+                return (\@alluserslist, $listerror{$key});
+            } elsif ( grep { $_ eq 'con_lost' } values(%listerror) ) {
+                # con_lost indicates that we might miss data from at least one
+                # library server
+                return (\@alluserslist, 'con_lost');
+            } else {
+                # multiple library servers and no con_lost -> data should be
+                # complete. 
+                return (\@alluserslist);
+            }
+
         } else {
             return ([],'missing username');
         }
@@ -10155,6 +10315,115 @@ sub stat_file {
     return ();
 }
 
+# --------------------------------------------------------- recursedirs
+# Recursive function to traverse either a specific user's Authoring Space
+# or corresponding Published Resource Space, and populate the hash ref:
+# $dirhashref with URLs of all directories, and if $filehashref hash
+# ref arg is provided, the URLs of any files, excluding versioned, .meta,
+# or .rights files in resource space, and .meta, .save, .log, and .bak
+# files in Authoring Space.
+#
+# Inputs:
+#
+# $is_home - true if current server is home server for user's space
+# $context - either: priv, or res respectively for Authoring or Resource Space.
+# $docroot - Document root (i.e., /home/httpd/html
+# $toppath - Top level directory (i.e., /res/$dom/$uname or /priv/$dom/$uname
+# $relpath - Current path (relative to top level).
+# $dirhashref - reference to hash to populate with URLs of directories (Required)
+# $filehashref - reference to hash to populate with URLs of files (Optional)
+#
+# Returns: nothing
+#
+# Side Effects: populates $dirhashref, and $filehashref (if provided).
+#
+# Currently used by interface/londocs.pm to create linked select boxes for
+# directory and filename to import a Course "Author" resource into a course, and
+# also to create linked select boxes for Authoring Space and Directory to choose
+# save location for creation of a new "standard" problem from the Course Editor.
+#
+
+sub recursedirs {
+    my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_;
+    return unless (ref($dirhashref) eq 'HASH');
+    my $currpath = $docroot.$toppath;
+    if ($relpath) {
+        $currpath .= "/$relpath";
+    }
+    my $savefile;
+    if (ref($filehashref)) {
+        $savefile = 1;
+    }
+    if ($is_home) {
+        if (opendir(my $dirh,$currpath)) {
+            foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) {
+                next if ($item eq '');
+                if (-d "$currpath/$item") {
+                    my $newpath;
+                    if ($relpath) {
+                        $newpath = "$relpath/$item";
+                    } else {
+                        $newpath = $item;
+                    }
+                    $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
+                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
+                } elsif ($savefile) {
+                    if ($context eq 'priv') {
+                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                        }
+                    } else {
+                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/) || ($item =~ /\.rights$/)) {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                        }
+                    }
+                }
+            }
+            closedir($dirh);
+        }
+    } else {
+        my ($dirlistref,$listerror) =
+            &dirlist($toppath.$relpath);
+        my @dir_lines;
+        my $dirptr=16384;
+        if (ref($dirlistref) eq 'ARRAY') {
+            foreach my $dir_line (sort
+                              {
+                                  my ($afile)=split('&',$a,2);
+                                  my ($bfile)=split('&',$b,2);
+                                  return (lc($afile) cmp lc($bfile));
+                              } (@{$dirlistref})) {
+                my ($item,$dom,undef,$testdir,undef,undef,undef,undef,$size,undef,$mtime,undef,undef,undef,$obs,undef) =
+                    split(/\&/,$dir_line,16);
+                $item =~ s/\s+$//;
+                next if (($item =~ /^\.\.?$/) || ($obs));
+                if ($dirptr&$testdir) {
+                    my $newpath;
+                    if ($relpath) {
+                        $newpath = "$relpath/$item";
+                    } else {
+                        $relpath = '/';
+                        $newpath = $item;
+                    }
+                    $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
+                    &recursedirs($is_home,$context,$docroot,$toppath,$newpath,$dirhashref,$filehashref);
+                } elsif ($savefile) {
+                    if ($context eq 'priv') {
+                        unless ($item =~ /\.(meta|save|log|bak|DS_Store)$/) {
+                            $filehashref->{$relpath}{$item} = 1;
+                        }
+                    } else {
+                        unless (($item =~ /\.meta$/) || ($item =~ /\.\d+\.\w+$/)) {
+                            $filehashref->{$relpath}{$item} = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
 # -------------------------------------------------------- Value of a Condition
 
 # gets the value of a specific preevaluated condition
@@ -10318,7 +10587,7 @@ sub get_userresdata {
 #  Parameters:
 #     $name      - Course/user name.
 #     $domain    - Name of the domain the user/course is registered on.
-#     $type      - Type of thing $name is (must be 'course' or 'user'
+#     $type      - Type of thing $name is (must be 'course' or 'user')
 #     $mapp      - decluttered URL of enclosing map  
 #     $recursed  - Ref to scalar -- set to 1, if nested maps have been recursed.
 #     $recurseup - Ref to array of map URLs, starting with map containing
@@ -13693,6 +13962,8 @@ Inputs:
 
 =item $credits, number of credits student will earn from this class
 
+=item $instsec, institutional course section code for student
+
 =back