--- loncom/lonnet/perl/lonnet.pm	2022/10/22 17:24:55	1.1497
+++ loncom/lonnet/perl/lonnet.pm	2023/04/11 20:35:19	1.1508
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1497 2022/10/22 17:24:55 raeburn Exp $
+# $Id: lonnet.pm,v 1.1508 2023/04/11 20:35:19 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2698,7 +2698,9 @@ sub get_domain_defaults {
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
                                   'coursecategories','ssl','autoenroll',
-                                  'trust','helpsettings','wafproxy','ltisec'],$domain);
+                                  'trust','helpsettings','wafproxy',
+                                  'ltisec','toolsec','domexttool',
+                                  'exttool',],$domain);
     my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
@@ -2770,6 +2772,16 @@ sub get_domain_defaults {
                         $domconfig{'coursedefaults'}{'postsubmit'}{'timeout'}{$type}; 
                 }
             }
+            if (ref($domconfig{'coursedefaults'}{'domexttool'}) eq 'HASH') {
+                $domdefaults{$type.'domexttool'} = $domconfig{'coursedefaults'}{'domexttool'}{$type};
+            } else {
+                $domdefaults{$type.'domexttool'} = 1;
+            }
+            if (ref($domconfig{'coursedefaults'}{'exttool'}) eq 'HASH') {
+                $domdefaults{$type.'exttool'} = $domconfig{'coursedefaults'}{'exttool'}{$type};
+            } else {
+                $domdefaults{$type.'exttool'} = 0;
+            }
         }
         if (ref($domconfig{'coursedefaults'}{'canclone'}) eq 'HASH') {
             if (ref($domconfig{'coursedefaults'}{'canclone'}{'instcode'}) eq 'ARRAY') {
@@ -2884,7 +2896,18 @@ sub get_domain_defaults {
         }
         if (ref($domconfig{'ltisec'}{'private'}) eq 'HASH') {
             if (ref($domconfig{'ltisec'}{'private'}{'keys'}) eq 'ARRAY') {
-                $domdefaults{'privhosts'} = $domconfig{'ltisec'}{'private'}{'keys'};
+                $domdefaults{'ltiprivhosts'} = $domconfig{'ltisec'}{'private'}{'keys'};
+            }
+        }
+    }
+    if (ref($domconfig{'toolsec'}) eq 'HASH') {
+        if (ref($domconfig{'toolsec'}{'encrypt'}) eq 'HASH') {
+            $domdefaults{'toolenc_crs'} = $domconfig{'toolsec'}{'encrypt'}{'crs'};
+            $domdefaults{'toolenc_dom'} = $domconfig{'toolsec'}{'encrypt'}{'dom'};
+        }
+        if (ref($domconfig{'toolsec'}{'private'}) eq 'HASH') {
+            if (ref($domconfig{'toolsec'}{'private'}{'keys'}) eq 'ARRAY') {
+                $domdefaults{'toolprivhosts'} = $domconfig{'toolsec'}{'private'}{'keys'};
             }
         }
     }
@@ -2924,6 +2947,7 @@ sub get_dom_instcats {
             if (&auto_instcode_format($caller,$dom,\%coursecodes,\%codes,
                                       \@codetitles,\%cat_titles,\%cat_order) eq 'ok') {
                 $instcats = {
+                                totcodes => $totcodes,
                                 codes => \%codes,
                                 codetitles => \@codetitles,
                                 cat_titles => \%cat_titles,
@@ -3856,10 +3880,15 @@ sub can_edit_resource {
                     return;
                 }
             } elsif (!$crsedit) {
+                if ($env{'request.role'} =~ m{^st\./$cdom/$cnum}) {
 #
 # No edit allowed where CC has switched to student role.
 #
-                return;
+                    return;
+                } elsif (($resurl !~ m{^/res/$match_domain/$match_username/}) ||
+                         ($resurl =~ m{^/res/lib/templates/})) {
+                    return;
+                }
             }
         }
     }
@@ -3885,7 +3914,7 @@ sub can_edit_resource {
                     $forceedit = 1;
                 }
                 $cfile = $resurl;
-            } elsif (($resurl ne '') && (&is_on_map($resurl))) { 
+            } elsif (($resurl ne '') && (&is_on_map($resurl))) {
                 if ($resurl =~ m{^/adm/$match_domain/$match_username/\d+/smppg|bulletinboard$}) {
                     $incourse = 1;
                     if ($env{'form.forceedit'}) {
@@ -4268,7 +4297,7 @@ sub resizeImage {
 # input: $formname - the contents of the file are in $env{"form.$formname"}
 #                    the desired filename is in $env{"form.$formname.filename"}
 #        $context - possible values: coursedoc, existingfile, overwrite, 
-#                                    canceloverwrite, scantron or ''.
+#                                    canceloverwrite, scantron, toollogo  or ''.
 #                   if 'coursedoc': upload to the current course
 #                   if 'existingfile': write file to tmp/overwrites directory 
 #                   if 'canceloverwrite': delete file written to tmp/overwrites directory
@@ -4280,8 +4309,8 @@ sub resizeImage {
 #                          Section => 4, CODE => 5, FirstQuestion => 9 }).
 #        $allfiles - reference to hash for embedded objects
 #        $codebase - reference to hash for codebase of java objects
-#        $desuname - username for permanent storage of uploaded file
-#        $dsetudom - domain for permanaent storage of uploaded file
+#        $destuname - username for permanent storage of uploaded file
+#        $destudom - domain for permanaent storage of uploaded file
 #        $thumbwidth - width (pixels) of thumbnail to make for uploaded image 
 #        $thumbheight - height (pixels) of thumbnail to make for uploaded image
 #        $resizewidth - width (pixels) to which to resize uploaded image
@@ -4491,11 +4520,24 @@ sub finishuserfileupload {
     if (($thumbwidth =~ /^\d+$/) && ($thumbheight =~ /^\d+$/)) {
         my $input = $filepath.'/'.$file;
         my $output = $filepath.'/'.'tn-'.$file;
+        my $makethumb; 
         my $thumbsize = $thumbwidth.'x'.$thumbheight;
-        my @args = ('convert','-sample',$thumbsize,$input,$output);
-        system({$args[0]} @args);
-        if (-e $filepath.'/'.'tn-'.$file) {
-            $fetchthumb  = 1; 
+        if ($context eq 'toollogo') {
+            my ($fullwidth,$fullheight) = &check_dimensions($input);
+            if ($fullwidth ne '' && $fullheight ne '') {
+                if ($fullwidth > $thumbwidth && $fullheight > $thumbheight) {
+                    $makethumb = 1;
+                }
+            }
+        } else {
+            $makethumb = 1;
+        }
+        if ($makethumb) {
+            my @args = ('convert','-sample',$thumbsize,$input,$output);
+            system({$args[0]} @args);
+            if (-e $filepath.'/'.'tn-'.$file) {
+                $fetchthumb  = 1; 
+            }
         }
     }
  
@@ -4727,6 +4769,30 @@ sub embedded_dependency {
     return;
 }
 
+sub check_dimensions {
+    my ($inputfile) = @_;
+    my ($fullwidth,$fullheight);
+    if (($inputfile =~ m|^[/\w.\-]+$|) && (-e $inputfile)) {
+        my $mm = new File::MMagic;
+        my $mime_type = $mm->checktype_filename($inputfile);
+        if ($mime_type =~ m{^image/}) {
+            if (open(PIPE,"identify $inputfile 2>&1 |")) {
+                my $imageinfo = <PIPE>;
+                if (!close(PIPE)) {
+                    &Apache::lonnet::logthis("Failed to close PIPE opened to retrieve image information for $inputfile");
+                }
+                chomp($imageinfo);
+                my ($fullsize) =
+                    ($imageinfo =~ /^\Q$inputfile\E\s+\w+\s+(\d+x\d+)/);
+                if ($fullsize) {
+                    ($fullwidth,$fullheight) = split(/x/,$fullsize);
+                }
+            }
+        }
+    }
+    return ($fullwidth,$fullheight);
+}
+
 sub bubblesheet_converter {
     my ($cdom,$fullpath,$config,$format) = @_;
     if ((&domain($cdom) ne '') &&
@@ -7954,6 +8020,17 @@ sub is_portfolio_file {
     return;
 }
 
+sub is_coursetool_logo {
+    my ($uri) = @_;
+    if ($env{'request.course.id'}) {
+        my $courseurl = &courseid_to_courseurl($env{'request.course.id'});
+        if ($uri =~ m{^/*uploaded\Q$courseurl\E/toollogo/\d+/[^/]+$}) {
+            return 1;
+        }
+    }
+    return;
+}
+
 sub usertools_access {
     my ($uname,$udom,$tool,$action,$context,$userenvref,$domdefref,$is_advref)=@_;
     my ($access,%tools);
@@ -8582,6 +8659,12 @@ sub allowed {
 
     if ($env{'request.course.id'}) {
 
+        if ($priv eq 'bre') {
+            if (&is_coursetool_logo($uri)) {
+                return 'F';
+            }
+        }
+
 # If this is modifying password (internal auth) domains must match for user and user's role.
 
         if ($priv eq 'mip') {
@@ -8902,13 +8985,8 @@ sub constructaccess {
        if (exists($env{'user.priv.au./'.$ownerdomain.'/./'})) {
           return ($ownername,$ownerdomain,$ownerhome);
        }
-    } else {
-# Co-author for this?
-        if (exists($env{'user.priv.ca./'.$ownerdomain.'/'.$ownername.'./'}) ||
-            exists($env{'user.priv.aa./'.$ownerdomain.'/'.$ownername.'./'}) ) {
-            $ownerhome = &homeserver($ownername,$ownerdomain);
-            return ($ownername,$ownerdomain,$ownerhome);
-        }
+    } elsif (&is_course($ownerdomain,$ownername)) {
+# Course Authoring Space?
         if ($env{'request.course.id'}) {
             if (($ownername eq $env{'course.'.$env{'request.course.id'}.'.num'}) &&
                 ($ownerdomain eq $env{'course.'.$env{'request.course.id'}.'.domain'})) {
@@ -8918,6 +8996,14 @@ sub constructaccess {
                 }
             }
         }
+        return '';
+    } else {
+# Co-author for this?
+        if (exists($env{'user.priv.ca./'.$ownerdomain.'/'.$ownername.'./'}) ||
+            exists($env{'user.priv.aa./'.$ownerdomain.'/'.$ownername.'./'}) ) {
+            $ownerhome = &homeserver($ownername,$ownerdomain);
+            return ($ownername,$ownerdomain,$ownerhome);
+        }
     }
 
 # We don't have any access right now. If we are not possibly going to do anything about this,
@@ -10333,7 +10419,7 @@ sub assignrole {
     if ($role =~ /^cr\//) {
         my $cwosec=$url;
         $cwosec=~s/^\/($match_domain)\/($match_courseid)\/.*/$1\/$2/;
-	unless (&allowed('ccr',$cwosec)) {
+        if ((!&allowed('ccr',$cwosec)) && (!&allowed('ccr',$udom))) {
            my $refused = 1;
            if ($context eq 'requestcourses') {
                if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')) {
@@ -12025,14 +12111,20 @@ sub stat_file {
 # 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.
+# or .rights files in resource space, and .meta, .save, .log, .bak and
+# .rights 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
+# $recurse - if true will also traverse subdirectories recursively
+# $include - reference to hash containing allowed file extensions.  If provided,
+#             files which do not have a matching extension will be ignored.
+# $exclude - reference to hash containing excluded file extensions.  If provided,
+#             files which have a matching extension will be ignored.
+# $nonemptydir - if true, will only populate $fileshashref hash entry for a particular
+#             directory with first file found (with acceptable extension).
+# $addtopdir - if true, set $dirhashref->{'/'} = 1 
 # $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)
@@ -12049,39 +12141,61 @@ sub stat_file {
 #
 
 sub recursedirs {
-    my ($is_home,$context,$docroot,$toppath,$relpath,$dirhashref,$filehashref) = @_;
+    my ($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$relpath,$dirhashref,$filehashref) = @_;
     return unless (ref($dirhashref) eq 'HASH');
+    my $docroot = $perlvar{'lonDocRoot'};
     my $currpath = $docroot.$toppath;
-    if ($relpath) {
+    if ($relpath ne '') {
         $currpath .= "/$relpath";
     }
-    my $savefile;
+    my ($savefile,$checkinc,$checkexc);
     if (ref($filehashref)) {
         $savefile = 1;
     }
+    if (ref($include) eq 'HASH') {
+        $checkinc = 1;
+    }
+    if (ref($exclude) eq 'HASH') {
+        $checkexc = 1;
+    }
     if ($is_home) {
-        if (opendir(my $dirh,$currpath)) {
+        if ((-e $currpath) && (opendir(my $dirh,$currpath))) {
+            my $filecount = 0;
             foreach my $item (sort { lc($a) cmp lc($b) } grep(!/^\.+$/,readdir($dirh))) {
                 next if ($item eq '');
                 if (-d "$currpath/$item") {
                     my $newpath;
-                    if ($relpath) {
+                    if ($relpath ne '') {
                         $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$/)) {
+                    if ($recurse) {
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                    }
+                } elsif (($savefile) || ($relpath eq '')) {
+                    next if ($nonemptydir && $filecount);
+                    if ($checkinc || $checkexc) {
+                        my ($extension) = ($item =~ /\.(\w+)$/);
+                        if ($checkinc) {
+                            next unless ($extension && $include->{$extension});
+                        }
+                        if ($checkexc) {
+                            next if ($extension && $exclude->{$extension});
+                        }
+                    }
+                    if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+                        $dirhashref->{'/'} = 1;
+                    }
+                    if ($savefile) {
+                        if ($relpath eq '') {
+                            $filehashref->{'/'}{$item} = 1;
+                        } else {
                             $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
                         }
                     }
+                    $filecount ++;
                 }
             }
             closedir($dirh);
@@ -12092,6 +12206,7 @@ sub recursedirs {
         my @dir_lines;
         my $dirptr=16384;
         if (ref($dirlistref) eq 'ARRAY') {
+            my $filecount = 0;
             foreach my $dir_line (sort
                               {
                                   my ($afile)=split('&',$a,2);
@@ -12107,28 +12222,57 @@ sub recursedirs {
                     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;
+                    if ($recurse) {
+                        &recursedirs($is_home,$recurse,$include,$exclude,$nonemptydir,$addtopdir,$toppath,$newpath,$dirhashref,$filehashref);
+                    }
+                } elsif (($savefile) || ($relpath eq '')) {
+                    next if ($nonemptydir && $filecount);
+                    if ($checkinc || $checkexc) {
+                        my $extension;
+                        if ($checkinc) {
+                            next unless ($extension && $include->{$extension});
+                        }
+                        if ($checkexc) {
+                            next if ($extension && $exclude->{$extension});
+                        }
+                    }
+                    if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+                        $dirhashref->{'/'} = 1;
+                    }
+                    if ($savefile) {
+                        if ($relpath eq '') {
+                            $filehashref->{'/'}{$item} = 1;
+                        } else {
+                            $filehashref->{&Apache::lonlocal::js_escape($relpath)}{$item} = 1;
                         }
                     }
+                    $filecount ++; 
                 }
             }
         }
     }
+    if ($addtopdir) {
+        if (($relpath eq '') && (!exists($dirhashref->{'/'}))) {
+            $dirhashref->{'/'} = 1;
+        }
+    }
     return;
 }
 
+sub priv_exclude {
+    return {
+             meta => 1,
+             save => 1,
+             log => 1,
+             bak => 1,
+             rights => 1,
+             DS_Store => 1,
+           };
+}
+
 # -------------------------------------------------------- Value of a Condition
 
 # gets the value of a specific preevaluated condition
@@ -12468,7 +12612,7 @@ sub count_supptools {
         my $chome=&homeserver($cnum,$cdom);
         $numexttools = 0;
         unless ($chome eq 'no_host') {
-            my ($supplemental) = &get_supplemental($cnum,$cdom,$reload);
+            my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$reload);
             if (ref($supplemental) eq 'HASH') {
                 if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
                     foreach my $key (keys(%{$supplemental->{'ids'}})) {
@@ -12485,7 +12629,7 @@ sub count_supptools {
 }
 
 sub has_unhidden_suppfiles {
-    my ($cnum,$cdom,$ignorecache,$possdell)=@_;
+    my ($cnum,$cdom,$ignorecache,$possdel)=@_;
     my $hashid=$cnum.':'.$cdom;
     my ($showsupp,$cached);
     unless ($ignorecache) {
@@ -12494,7 +12638,7 @@ sub has_unhidden_suppfiles {
     unless (defined($cached)) {
         my $chome=&homeserver($cnum,$cdom);
         unless ($chome eq 'no_host') {
-            my ($supplemental) = &get_supplemental($cnum,$cdom,$ignorecache,$possdel);
+            my ($supplemental) = &Apache::loncommon::get_supplemental($cnum,$cdom,$ignorecache,$possdel);
             if (ref($supplemental) eq 'HASH') {
                 if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
                     foreach my $key (keys(%{$supplemental->{'ids'}})) {
@@ -12517,35 +12661,6 @@ sub has_unhidden_suppfiles {
     return $showsupp;
 }
 
-sub get_supplemental {
-    my ($cnum,$cdom,$ignorecache,$possdel)=@_;
-    my $hashid=$cnum.':'.$cdom;
-    my ($supplemental,$cached,$set_httprefs);
-    unless ($ignorecache) {
-        ($supplemental,$cached) = &is_cached_new('supplemental',$hashid);
-    }
-    unless (defined($cached)) {
-        my $chome=&homeserver($cnum,$cdom);
-        unless ($chome eq 'no_host') {
-            my ($errors,%ids,%hidden);
-            $errors =
-                &Apache::loncommon::recurse_supplemental($cnum,$cdom,
-                                                         'supplemental.sequence',
-                                                         $errors,$possdel,\%ids,\%hidden);
-            $set_httprefs = 1;
-            if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
-                &Apache::lonnet::appenv({'request.course.suppupdated' => time});
-            }
-            $supplemental = {
-                               ids => \%ids,
-                               hidden => \%hidden,
-                            };
-            &do_cache_new('supplemental',$hashid,$supplemental,600);
-        }
-    }
-    return ($supplemental,$set_httprefs);
-}
-
 #
 # EXT resource caching routines
 #