--- loncom/interface/loncommon.pm	2022/09/13 12:22:14	1.1388
+++ loncom/interface/loncommon.pm	2022/11/17 13:21:09	1.1397
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1388 2022/09/13 12:22:14 raeburn Exp $
+# $Id: loncommon.pm,v 1.1397 2022/11/17 13:21:09 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -72,6 +72,7 @@ use Apache::lonuserstate();
 use Apache::courseclassifier();
 use LONCAPA qw(:DEFAULT :match);
 use LONCAPA::LWPReq;
+use LONCAPA::map();
 use HTTP::Request;
 use DateTime::TimeZone;
 use DateTime::Locale;
@@ -4284,6 +4285,30 @@ sub syllabuswrapper {
 
 # -----------------------------------------------------------------------------
 
+sub aboutme_on {
+    my ($uname,$udom)=@_;
+    unless ($uname) { $uname=$env{'user.name'}; }
+    unless ($udom)  { $udom=$env{'user.domain'}; }
+    return if ($udom eq 'public' && $uname eq 'public');
+    my $hashkey=$uname.':'.$udom;
+    my ($aboutme,$cached)=&Apache::lonnet::is_cached_new('aboutme',$hashkey);
+    if ($cached) {
+        return $aboutme;
+    }
+    $aboutme = &Apache::lonnet::usertools_access($uname,$udom,'aboutme');
+    &Apache::lonnet::do_cache_new('aboutme',$hashkey,$aboutme,3600);
+    return $aboutme;
+}
+
+sub devalidate_aboutme_cache {
+    my ($uname,$udom)=@_;
+    if (!$udom)  { $udom =$env{'user.domain'}; }
+    if (!$uname) { $uname=$env{'user.name'};   }
+    return if ($udom eq 'public' && $uname eq 'public');
+    my $id=$uname.':'.$udom;
+    &Apache::lonnet::devalidate_cache_new('aboutme',$id);
+}
+
 sub track_student_link {
     my ($linktext,$sname,$sdom,$target,$start,$only_body) = @_;
     my $link ="/adm/trackstudent?";
@@ -14287,7 +14312,9 @@ sub process_extracted_files {
                             my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
                                       $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'.
                                       $title;
-                            if (($outer !~ /\D/) && ($mapinner{$outer} !~ /\D/) && ($newidx !~ /\D/)) {
+                            if (($outer !~ /\D/) &&
+                                (($mapinner{$outer} eq 'default') || ($mapinner{$outer} !~ /\D/)) &&
+                                ($newidx !~ /\D/)) {
                                 if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
                                     mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
                                 }
@@ -17060,7 +17087,6 @@ sub construct_course {
 #
     unless (($args->{'nonstandard'}) || ($args->{'firstres'} eq 'blank')
 	    || ($cloneid)) {
-	use LONCAPA::map;
 	$outcome .= &mt('Setting first resource').': ';
 
 	my $map = '/uploaded/'.$$crsudom.'/'.$$crsunum.'/default.sequence';
@@ -18320,30 +18346,54 @@ sub needs_coursereinit {
         if ($blocked) {
             return ();
         }
-        my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
-        if ($lastchange > $env{'request.course.tied'}) {
-            my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
-            if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
-                my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
-                if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
-                    &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
-                                             $curr_reqd_hash{'internal.releaserequired'}});
-                    my ($switchserver,$switchwarning) =
-                        &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
-                                                $curr_reqd_hash{'internal.releaserequired'});
-                    if ($switchwarning ne '' || $switchserver ne '') {
-                        return ('switch',$switchwarning,$switchserver);
-                    }
+        my $update;
+        my $lastmainchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
+        my $lastsuppchange = &Apache::lonnet::get_suppchange($cdom,$cnum);
+        if ($lastmainchange > $env{'request.course.tied'}) {
+            my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+            if ($needswitch) {
+                return ('switch',$switchwarning,$switchserver);
+            }
+            $update = 'main';
+        }
+        if ($lastsuppchange > $env{'request.course.suppupdated'}) {
+            if ($update) {
+                $update = 'both';
+            } else {
+                my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+                if ($needswitch) {
+                    return ('switch',$switchwarning,$switchserver);
+                } else {
+                    $update = 'supp';
                 }
             }
-            return ('update');
+            return ($update);
+        }
+    }
+    return ();
+}
+
+sub switch_for_update {
+    my ($loncaparev,$cdom,$cnum) = @_;
+    my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
+    if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
+        my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
+        if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
+            &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
+                                    $curr_reqd_hash{'internal.releaserequired'}});
+            my ($switchserver,$switchwarning) =
+                &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
+                                        $curr_reqd_hash{'internal.releaserequired'});
+            if ($switchwarning ne '' || $switchserver ne '') {
+                return ('switch',$switchwarning,$switchserver);
+            }
         }
     }
     return ();
 }
 
 sub update_content_constraints {
-    my ($cdom,$cnum,$chome,$cid,$keeporder) = @_;
+    my ($cdom,$cnum,$chome,$cid) = @_;
     my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
     my ($reqdmajor,$reqdminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'});
     my (%checkresponsetypes,%checkcrsrestypes);
@@ -18391,25 +18441,7 @@ sub update_content_constraints {
         }
         undef($navmap);
     }
-    my (@resources,@order,@resparms,@zombies);
-    if ($keeporder) {
-        use LONCAPA::map;
-        @resources = @LONCAPA::map::resources;
-        @order = @LONCAPA::map::order;
-        @resparms = @LONCAPA::map::resparms;
-        @zombies = @LONCAPA::map::zombies;
-    }
-    my $suppmap = 'supplemental.sequence';
-    my ($suppcount,$supptools,$errors) = (0,0,0);
-    ($suppcount,$supptools,$errors) = &recurse_supplemental($cnum,$cdom,$suppmap,
-                                                            $suppcount,$supptools,$errors);
-    if ($keeporder) {
-        @LONCAPA::map::resources = @resources;
-        @LONCAPA::map::order = @order;
-        @LONCAPA::map::resparms = @resparms;
-        @LONCAPA::map::zombies = @zombies;
-    }
-    if ($supptools) {
+    if (&Apache::lonnet::count_supptools($cnum,$cdom,1)) {
         my ($major,$minor) = split(/\./,$checkcrsrestypes{'exttool'});
         if (($major > $reqdmajor) || ($major == $reqdmajor && $minor > $reqdminor)) {
             ($reqdmajor,$reqdminor) = ($major,$minor);
@@ -18435,7 +18467,7 @@ sub allmaps_incourse {
     if ($lastchange > $env{'request.course.tied'}) {
         my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum");
         unless ($ferr) {
-            &update_content_constraints($cdom,$cnum,$chome,$cid,1);
+            &update_content_constraints($cdom,$cnum,$chome,$cid);
         }
     }
     my $navmap = Apache::lonnavmaps::navmap->new();
@@ -18470,32 +18502,147 @@ sub parse_supplemental_title {
     return $title;
 }
 
+sub get_supplemental {
+    my ($cnum,$cdom,$ignorecache,$possdel)=@_;
+    my $hashid=$cnum.':'.$cdom;
+    my ($supplemental,$cached,$set_httprefs);
+    unless ($ignorecache) {
+        ($supplemental,$cached) = &Apache::lonnet::is_cached_new('supplemental',$hashid);
+    }
+    unless (defined($cached)) {
+        my $chome=&Apache::lonnet::homeserver($cnum,$cdom);
+        unless ($chome eq 'no_host') {
+            my @order = @LONCAPA::map::order;
+            my @resources = @LONCAPA::map::resources;
+            my @resparms = @LONCAPA::map::resparms;
+            my @zombies = @LONCAPA::map::zombies;
+            my ($errors,%ids,%hidden);
+            $errors =
+                &recurse_supplemental($cnum,$cdom,'supplemental.sequence',
+                                      $errors,$possdel,\%ids,\%hidden);
+            @LONCAPA::map::order = @order;
+            @LONCAPA::map::resources = @resources;
+            @LONCAPA::map::resparms = @resparms;
+            @LONCAPA::map::zombies = @zombies;
+            $set_httprefs = 1;
+            if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+            }
+            $supplemental = {
+                               ids => \%ids,
+                               hidden => \%hidden,
+                            };
+            &Apache::lonnet::do_cache_new('supplemental',$hashid,$supplemental,600);
+        }
+    }
+    return ($supplemental,$set_httprefs);
+}
+
 sub recurse_supplemental {
-    my ($cnum,$cdom,$suppmap,$numfiles,$numexttools,$errors) = @_;
-    if ($suppmap) {
+    my ($cnum,$cdom,$suppmap,$errors,$possdel,$suppids,$hiddensupp,$hidden) = @_;
+    if (($suppmap) && (ref($suppids) eq 'HASH') && (ref($hiddensupp) eq 'HASH')) {
+        my $mapnum;
+        if ($suppmap eq 'supplemental.sequence') {
+            $mapnum = 0;
+        } else {
+            ($mapnum) = ($suppmap =~ /^supplemental_(\d+)\.sequence$/);
+        }
         my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap);
         if ($fatal) {
             $errors ++;
         } else {
-            if ($#LONCAPA::map::resources > 0) {
-                foreach my $res (@LONCAPA::map::resources) {
-                    my ($title,$src,$ext,$type,$status)=split(/\:/,$res);
+            my @order = @LONCAPA::map::order;
+            if (@order > 0) {
+                my @resources = @LONCAPA::map::resources;
+                my @resparms = @LONCAPA::map::resparms;
+                foreach my $idx (@order) {
+                    my ($title,$src,$ext,$type,$status)=split(/\:/,$resources[$idx]);
                     if (($src ne '') && ($status eq 'res')) {
+                        my $id = $mapnum.':'.$idx;
+                        push(@{$suppids->{$src}},$id);
+                        if (($hidden) || (&get_supp_parameter($resparms[$idx],'parameter_hiddenresource') =~ /^yes/i)) {
+                            $hiddensupp->{$id} = 1;
+                        }
                         if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
-                            ($numfiles,$numexttools,$errors) = &recurse_supplemental($cnum,$cdom,$1,
-                                                                   $numfiles,$numexttools,$errors);
+                            $errors = &recurse_supplemental($cnum,$cdom,$1,$errors,$possdel,$suppids,
+                                                            $hiddensupp,$hiddensupp->{$id});
                         } else {
-                            if ($src =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) {
-                                $numexttools ++;
+                            my $allowed;
+                            if (($env{'request.role.adv'}) || (!$hiddensupp->{$id})) {
+                                $allowed = 1;
+                            } elsif ($possdel) {
+                                foreach my $item (@{$suppids->{$src}}) {
+                                    next if ($item eq $id);
+                                    unless ($hiddensupp->{$item}) {
+                                       $allowed = 1;
+                                       last;
+                                    }
+                                }
+                                if ((!$allowed) && (exists($env{'httpref.'.$src}))) {
+                                    &Apache::lonnet::delenv('httpref.'.$src);
+                                }
+                            }
+                            if ($allowed && (!exists($env{'httpref.'.$src}))) {
+                                &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
                             }
-                            $numfiles ++;
                         }
                     }
                 }
             }
         }
     }
-    return ($numfiles,$numexttools,$errors);
+    return $errors;
+}
+
+sub set_supp_httprefs {
+    my ($cnum,$cdom,$supplemental,$possdel) = @_;
+    if (ref($supplemental) eq 'HASH') {
+        if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
+            foreach my $src (keys(%{$supplemental->{'ids'}})) {
+                next if ($src =~ /\.sequence$/);
+                if (ref($supplemental->{'ids'}->{$src}) eq 'ARRAY') {
+                    my $allowed;
+                    if ($env{'request.role.adv'}) {
+                        $allowed = 1;
+                    } else {
+                        foreach my $id (@{$supplemental->{'ids'}->{$src}}) {
+                            unless ($supplemental->{'hidden'}->{$id}) {
+                                $allowed = 1;
+                                last;
+                            }
+                        }
+                    }
+                    if (exists($env{'httpref.'.$src})) {
+                        if ($possdel) {
+                            unless ($allowed) {
+                                &Apache::lonnet::delenv('httpref.'.$src);
+                            }
+                        }
+                    } elsif ($allowed) {
+                        &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
+                    }
+                }
+            }
+            if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+                &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+            }
+        }
+    }
+}
+
+sub get_supp_parameter {
+    my ($resparm,$name)=@_;
+    return if ($resparm eq '');
+    my $value=undef;
+    my $ptype=undef;
+    foreach (split('&&&',$resparm)) {
+        my ($thistype,$thisname,$thisvalue)=split('___',$_);
+        if ($thisname eq $name) {
+            $value=$thisvalue;
+            $ptype=$thistype;
+        }
+    }
+    return $value;
 }
 
 sub symb_to_docspath {
@@ -18568,6 +18715,67 @@ sub symb_to_docspath {
     return $path;
 }
 
+sub validate_folderpath {
+    my ($supplementalflag,$allowed,$coursenum,$coursedom) = @_;
+    if ($env{'form.folderpath'} ne '') {
+        my @items = split(/\&/,$env{'form.folderpath'});
+        my ($badpath,$changed,$got_supp,$supppath,%supphidden,%suppids);
+        for (my $i=0; $i<@items; $i++) {
+            my $odd = $i%2;
+            if (($odd) && (!$supplementalflag) && ($items[$i] !~ /^[^:]*:(|\d+):(|1):(|1):(|1):(|1)$/)) {
+                $badpath = 1;
+            } elsif ($odd && $supplementalflag) {
+                my $idx = $i-1;
+                if ($items[$i] =~ /^([^:]*)::(|1):::$/) {
+                    my $esc_name = $1;
+                    if ((!$allowed) || ($items[$idx] eq 'supplemental')) {
+                        $supppath .= '&'.$esc_name;
+                        $changed = 1;
+                    } else {
+                        $supppath .= '&'.$items[$i];
+                    }
+                } elsif (($allowed) && ($items[$idx] ne 'supplemental')) {
+                    $changed = 1;
+                    my $is_hidden;
+                    unless ($got_supp) {
+                        my ($supplemental) = &get_supplemental($coursenum,$coursedom);
+                        if (ref($supplemental) eq 'HASH') {
+                            if (ref($supplemental->{'hidden'}) eq 'HASH') {
+                                %supphidden = %{$supplemental->{'hidden'}};
+                            }
+                            if (ref($supplemental->{'ids'}) eq 'HASH') {
+                                %suppids = %{$supplemental->{'ids'}};
+                            }
+                        }
+                        $got_supp = 1;
+                    }
+                    if (ref($suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}) eq 'ARRAY') {
+                        my $mapid = $suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}->[0];
+                        if ($supphidden{$mapid}) {
+                            $is_hidden = 1;
+                        }
+                    }
+                    $supppath .= '&'.$items[$i].'::'.$is_hidden.':::';
+                } else {
+                    $supppath .= '&'.$items[$i];
+                }
+            } elsif ((!$odd) && ($items[$i] !~ /^(default|supplemental)(|_\d+)$/)) {
+                $badpath = 1;
+            } elsif ($supplementalflag) {
+                $supppath .= '&'.$items[$i];
+            }
+            last if ($badpath);
+        }
+        if ($badpath) {
+            delete($env{'form.folderpath'});
+        } elsif ($changed && $supplementalflag) {
+            $supppath =~ s/^\&//;
+            $env{'form.folderpath'} = $supppath;
+        }
+    }
+    return;
+}
+
 sub captcha_display {
     my ($context,$lonhost,$defdom) = @_;
     my ($output,$error);
@@ -18690,7 +18898,7 @@ sub create_captcha {
             $output = '<input type="hidden" name="crypt" value="'.$md5sum.'" />'."\n".
                       '<span class="LC_nobreak">'.
                       &mt('Type in the letters/numbers shown below').'&nbsp;'.
-                      '<input type="text" size="5" name="code" value="" autocomplete="off" />'.
+                      '<input type="text" size="5" name="code" value="" autocomplete="new-password" />'.
                       '</span><br />'.
                       '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
             last;