--- loncom/lonnet/perl/lonnet.pm	2024/05/21 02:57:16	1.1527
+++ loncom/lonnet/perl/lonnet.pm	2025/01/13 01:00:58	1.1534
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1527 2024/05/21 02:57:16 raeburn Exp $
+# $Id: lonnet.pm,v 1.1534 2025/01/13 01:00:58 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -188,7 +188,11 @@ sub create_connection {
 				     Type    => SOCK_STREAM,
 				     Timeout => 10);
     return 0 if (!$client);
-    print $client (join(':',$hostname,$lonid,&machine_ids($hostname),$loncaparevs{$lonid})."\n");
+    if ($loncaparevs{$lonid} =~ /^(\d+\.\d+\.[\w.]+)-\d+$/) {
+        print $client (join(':',$hostname,$lonid,$1,&machine_ids($hostname))."\n");
+    } else {
+        print $client (join(':',$hostname,$lonid,&machine_ids($hostname))."\n");
+    }
     my $result = <$client>;
     chomp($result);
     return 1 if ($result eq 'done');
@@ -224,7 +228,7 @@ sub get_server_distarch {
             }
         }
         my $rep = &reply('serverdistarch',$lonhost);
-        unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' ||
+        unless ($rep eq 'unknown_cmd' || $rep eq 'no_such_host' ||
                 $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' ||
                 $rep eq '') {
             return &do_cache_new('serverdistarch',$lonhost,$rep,$cachetime);
@@ -2825,6 +2829,9 @@ sub get_domain_defaults {
         if (ref($domconfig{'coursedefaults'}{'postsubmit'}) eq 'HASH') {
             $domdefaults{'postsubmit'} = $domconfig{'coursedefaults'}{'postsubmit'}{'client'};
         }
+        if (ref($domconfig{'coursedefaults'}{'crseditors'}) eq 'ARRAY') {
+            $domdefaults{'crseditors'}=join(',',@{$domconfig{'coursedefaults'}{'crseditors'}});
+        }
         foreach my $type (@coursetypes) {
             if (ref($domconfig{'coursedefaults'}{'coursecredits'}) eq 'HASH') {
                 unless ($type eq 'community') {
@@ -2858,9 +2865,6 @@ sub get_domain_defaults {
             } else {
                 $domdefaults{$type.'crsauthor'} = 1;
             }
-            if (ref($domconfig{'coursedefaults'}{'crseditors'}) eq 'ARRAY') {
-                $domdefaults{'crseditors'}=join(',',@{$domconfig{'coursedefaults'}{'crseditors'}});
-            }
         }
         if (ref($domconfig{'coursedefaults'}{'canclone'}) eq 'HASH') {
             if (ref($domconfig{'coursedefaults'}{'canclone'}{'instcode'}) eq 'ARRAY') {
@@ -3015,6 +3019,9 @@ sub get_domain_defaults {
                 last if ($domdefaults{'userapprovals'});
             }
         }
+        if (ref($domconfig{'privacy'}{'othdom'}) eq 'HASH') {
+            $domdefaults{'privacyothdom'} = $domconfig{'privacy'}{'othdom'};
+        }
     }
     &do_cache_new('domdefaults',$domain,\%domdefaults,$cachetime);
     return %domdefaults;
@@ -6163,7 +6170,7 @@ sub courselastaccess {
 sub extract_lastaccess {
     my ($returnhash,$rep) = @_;
     if (ref($returnhash) eq 'HASH') {
-        unless ($rep eq 'unknown_command' || $rep eq 'no_such_host' || 
+        unless ($rep eq 'unknown_cmd' || $rep eq 'no_such_host' || 
                 $rep eq 'con_lost' || $rep eq 'rejected' || $rep eq 'refused' ||
                  $rep eq '') {
             my @pairs=split(/\&/,$rep);
@@ -6750,13 +6757,17 @@ sub cstore {
 
     if ($stuname) { $home=&homeserver($stuname,$domain); }
 
-    $symb=&symbclean($symb);
+    unless (($symb eq '_feedback') || ($symb eq '_discussion')) {
+        $symb=&symbclean($symb);
+    }
     if (!$symb) { unless ($symb=&symbread()) { return ''; } }
 
     if (!$domain) { $domain=$env{'user.domain'}; }
     if (!$stuname) { $stuname=$env{'user.name'}; }
 
-    &devalidate($symb,$stuname,$domain);
+    unless (($symb eq '_feedback') || ($symb eq '_discussion')) {
+        &devalidate($symb,$stuname,$domain);
+    }
 
     $symb=escape($symb);
     if (!$namespace) { 
@@ -6766,7 +6777,7 @@ sub cstore {
     }
     if (!$home) { $home=$env{'user.home'}; }
 
-    $$storehash{'ip'}=&get_requestor_ip();
+    $$storehash{'ip'} = &get_requestor_ip();
     $$storehash{'host'}=$perlvar{'lonHostID'};
 
     my $namevalue='';
@@ -10401,7 +10412,7 @@ sub auto_instsec_reformat {
             my $info = &freeze_escape($instsecref);
             my $response=&reply('autoinstsecreformat:'.$cdom.':'.
                                 $action.':'.$info,$server);
-            next if ($response =~ /(con_lost|error|no_such_host|refused|unknown_command)/);
+            next if ($response =~ /(con_lost|error|no_such_host|refused|unknown_cmd)/);
             my @items = split(/&/,$response);
             foreach my $item (@items) {
                 my ($key,$value) = split(/=/,$item);
@@ -10483,7 +10494,7 @@ sub auto_export_grades {
             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)/) {
+            unless ($response =~ /(con_lost|error|no_such_host|refused|unknown_cmd)/) {
                 my @items = split(/&/,$response);
                 foreach my $item (@items) {
                     my ($key,$value) = split('=',$item);
@@ -11676,7 +11687,7 @@ sub is_course {
 }
 
 sub store_userdata {
-    my ($storehash,$datakey,$namespace,$udom,$uname) = @_;
+    my ($storehash,$datakey,$namespace,$udom,$uname,$ip) = @_;
     my $result;
     if ($datakey ne '') {
         if (ref($storehash) eq 'HASH') {
@@ -11688,7 +11699,11 @@ sub store_userdata {
             if (($uhome eq '') || ($uhome eq 'no_host')) {
                 $result = 'error: no_host';
             } else {
-                $storehash->{'ip'} = &get_requestor_ip();
+                if ($ip ne '') {
+                    $storehash->{'ip'} = $ip;
+                } else {
+                    $storehash->{'ip'} = &get_requestor_ip();
+                }
                 $storehash->{'host'} = $perlvar{'lonHostID'};
 
                 my $namevalue='';
@@ -12543,6 +12558,8 @@ sub stat_file {
 # $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)
+# $getlastmod - if true, will set value for each key in innerhash in $filehashref
+#               to last modification time of file; value set to 1 otherwise.
 #
 # Returns: nothing
 #
@@ -12555,7 +12572,8 @@ sub stat_file {
 #
 
 sub recursedirs {
-    my ($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$relpath,$dirhashref,$filehashref) = @_;
+    my ($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,
+        $relpath,$dirhashref,$filehashref,$getlastmod) = @_;
     return unless (ref($dirhashref) eq 'HASH');
     my $docroot = $perlvar{'lonDocRoot'};
     my $currpath = $docroot.$toppath;
@@ -12563,7 +12581,7 @@ sub recursedirs {
         $currpath .= "/$relpath";
     }
     my ($savefile,$checkinc,$checkexc);
-    if (ref($filehashref)) {
+    if (ref($filehashref) eq 'HASH') {
         $savefile = 1;
     }
     if (ref($include) eq 'HASH') {
@@ -12586,7 +12604,8 @@ sub recursedirs {
                     }
                     $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
                     if ($recurse) {
-                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,
+                                     $toppath,$newpath,$dirhashref,$filehashref,$getlastmod);
                     }
                 } elsif (($savefile) || ($relpath eq '')) {
                     next if ($nonemptydir && $filecount);
@@ -12603,10 +12622,16 @@ sub recursedirs {
                         $dirhashref->{'/'} = 1;
                     }
                     if ($savefile) {
+                        my $value;
+                        if ($getlastmod) {
+                            ($value) = (stat("$currpath/$item"))[9];
+                        } else {
+                            $value = 1;
+                        }
                         if ($relpath eq '') {
-                            $filehashref->{'/'}{$item} = 1;
+                            $filehashref->{'/'}{$item} = $value
                         } else {
-                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = $value;
                         }
                     }
                     $filecount ++;
@@ -12615,8 +12640,11 @@ sub recursedirs {
             closedir($dirh);
         }
     } else {
-        my ($dirlistref,$listerror) =
-            &dirlist($toppath.$relpath);
+        my $url = $toppath;
+        if ($relpath ne '') {
+            $url = $toppath.'/'.$relpath;
+        }
+        my ($dirlistref,$listerror) = &dirlist($url);
         my @dir_lines;
         my $dirptr=16384;
         if (ref($dirlistref) eq 'ARRAY') {
@@ -12640,12 +12668,13 @@ sub recursedirs {
                     }
                     $dirhashref->{&Apache::lonlocal::js_escape($newpath)} = 1;
                     if ($recurse) {
-                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,
+                                     $toppath,$newpath,$dirhashref,$filehashref,$getlastmod);
                     }
                 } elsif (($savefile) || ($relpath eq '')) {
                     next if ($nonemptydir && $filecount);
                     if ($checkinc || $checkexc) {
-                        my $extension;
+                        my ($extension) = ($item =~ /\.(\w+)$/);
                         if ($checkinc) {
                             next unless ($extension && $include->{$extension});
                         }
@@ -12657,10 +12686,16 @@ sub recursedirs {
                         $dirhashref->{'/'} = 1;
                     }
                     if ($savefile) {
+                        my $value;
+                        if ($getlastmod) {
+                            $value = $mtime;
+                        } else {
+                            $value = 1;
+                        }
                         if ($relpath eq '') {
-                            $filehashref->{'/'}{$item} = 1;
+                            $filehashref->{'/'}{$item} = $value;
                         } else {
-                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = $value;
                         }
                     }
                     $filecount ++; 
@@ -12687,6 +12722,14 @@ sub priv_exclude {
            };
 }
 
+sub res_exclude {
+    return {
+             meta => 1,
+             subscription => 1,
+             rights => 1,
+           };
+}
+
 # -------------------------------------------------------- Value of a Condition
 
 # gets the value of a specific preevaluated condition
@@ -15156,6 +15199,49 @@ sub repcopy_userfile {
     return 'ok';
 }
 
+sub repcopy_crsprivfile {
+    my ($src,$dest) = @_;
+    my $result;
+    if ($src =~ m{^/priv/($match_domain)/($match_courseid)/(.+)$}) {
+        my ($cdom,$cnum,$filepath) = ($1,$2,$3);
+        $filepath =~ s/\.{2,}//g;
+        my $chome = &homeserver($cnum,$cdom);
+        unless ($chome eq 'no_host') {
+            my @ids=&current_machine_ids();
+            unless (grep(/^\Q$chome\E$/,@ids)) {
+                if (&is_course($cdom,$cnum)) {
+                    my $londocroot = $perlvar{'lonDocRoot'};
+                    if ($dest =~ m{^\Q$londocroot/priv/\E$match_domain/$match_username/.*\Q$filepath\E$}) {
+                        my $cmd = 'crsfilefrompriv:'.&escape($filepath).':'.&escape($cnum).':'.&escape($cdom);
+                        $result = &reply($cmd,$chome);
+                        unless (($result eq 'unknown_cmd') || ($result =~ /^error:/)) {
+                            my $url = &unescape($result);
+                            if ($url =~ m{^https?://[^/]+\Q/userfiles/$cdom/$cnum/priv/$filepath\E$}) {
+                                my $request=new HTTP::Request('GET',$url);
+                                my $response=&LONCAPA::LWPReq::makerequest($chome,$request,'',\%perlvar,1200,1);
+                                if ($response->is_error()) {
+                                    $result = 'error: '.$response->status_line;
+                                } else {
+                                    if (open(my $fh,'>',$dest)) {
+                                        print $fh $response->content;
+                                        close($fh);
+                                        $result = 'ok';
+                                    } else {
+                                        $result = 'error: nowrite';
+                                    }
+                                }
+                            } else {
+                                $result = 'error: invalidurl';
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return $result;
+}
+
 sub tokenwrapper {
     my $uri=shift;
     $uri=~s|^https?\://([^/]+)||;