--- loncom/interface/loncommon.pm	2023/12/31 23:03:40	1.1425
+++ loncom/interface/loncommon.pm	2024/08/22 15:16:05	1.1433
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1425 2023/12/31 23:03:40 raeburn Exp $
+# $Id: loncommon.pm,v 1.1433 2024/08/22 15:16:05 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2437,6 +2437,66 @@ sub show_crsfiles_js {
 END
 }
 
+sub crsauthor_rights {
+    my ($rightsfile,$path,$docroot,$cnum,$cdom) = @_;
+    my $sourcerights = "$path/$rightsfile";
+    my $now = time;
+    if (!-e $sourcerights) {
+        my $cid = $cdom.'_'.$cnum;
+        if (!-e "$docroot/priv/$cdom") {
+            mkdir("$docroot/priv/$cdom",0755);
+        }
+        if (!-e "$docroot/priv/$cdom/$cnum") {
+            mkdir("$docroot/priv/$cdom/$cnum",0755);
+        }
+        if (open(my $fh,">$sourcerights")) {
+            print $fh <<END;
+<accessrule effect="deny" realm="" type="course" role="" />
+<accessrule effect="allow" realm="$cid" type="course" role="" />
+END
+            close($fh);
+        }
+    }
+    if (!-e "$sourcerights.meta") {
+        if (open(my $fh,">$sourcerights.meta")) {
+            my $author=$env{'environment.firstname'}.' '.
+                       $env{'environment.middlename'}.' '.
+                       $env{'environment.lastname'}.' '.
+                       $env{'environment.generation'};
+            $author =~ s/\s+$//;
+            print $fh <<"END";
+
+<abstract></abstract>
+<author>$author</author>
+<authorspace>$cnum:$cdom</authorspace>
+<copyright>private</copyright>
+<creationdate>$now</creationdate>
+<customdistributionfile></customdistributionfile>
+<dependencies></dependencies>
+<domain>$cdom</domain>
+<highestgradelevel>0</highestgradelevel>
+<keywords></keywords>
+<language>notset </language>
+<lastrevisiondate>$now</lastrevisiondate>
+<lowestgradelevel>0</lowestgradelevel>
+<mime>rights</mime>
+<modifyinguser>$env{'user.name'}:$env{'user.domain'}</modifyinguser>
+<notes></notes>
+<obsolete></obsolete>
+<obsoletereplacement></obsoletereplacement>
+<owner>$cnum:$cdom</owner>
+<rule>deny:::course,allow:$cid::course</rule>
+<sourceavail></sourceavail>
+<standards></standards>
+<subject></subject>
+<title>Course Authoring Rights</title>
+END
+            close($fh);
+        }
+    }
+    return;
+}
+
 =pod
 
 =item * &iframe_wrapper_headjs()
@@ -6476,7 +6536,7 @@ Input: (optional) filename from which br
        If page header is being requested for use in a frameset, then
        the second (option) argument -- frameset will be true, and
        the target attribute set for links should be target="_parent".
-       If $title is supplied as the thitd arg, that will be used to 
+       If $title is supplied as the third arg, that will be used to 
        the left of the breadcrumbs tail for the current path.
 
 Returns: HTML div with CSTR path and recent box
@@ -6596,20 +6656,32 @@ sub nocodemirror {
 Input: $uri (optional)
 
 Returns: %editors hash in which keys are editors
-         permitted in current Authoring Space.
+         permitted in current Authoring Space,
+         or in current course for web pages
+         created in a course.
+
          Value for each key is 1. Possible keys
-         are: edit, xml, and daxe. If no specific
+         are: edit, xml, and daxe.
+
+         For a regular Authoring Space, if no specific
          set of editors has been set for the Author
          who owns the Authoring Space, then the
          domain default will be used.  If no domain
          default has been set, then the keys will be
          edit and xml.
 
+         For a course author, or for web pages created
+         in a course, if no specific set of editors has
+         been set for the course, then the domain
+         course default will be used. If no domain
+         course default has been set, then the keys
+         will be edit and xml.
+
 =cut
 
 sub permitted_editors {
     my ($uri) = @_;
-    my ($is_author,$is_coauthor,$auname,$audom,%editors);
+    my ($is_author,$is_coauthor,$is_course,$auname,$audom,%editors);
     if ($env{'request.role'} =~ m{^au\./}) {
         $is_author = 1;
     } elsif ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)}) {
@@ -6623,20 +6695,33 @@ sub permitted_editors {
             }
         }
     } elsif ($env{'request.course.id'}) {
-        if ($env{'request.editurl'} =~ m{^/priv/($match_domain)/($match_username)/}) {
+        my ($cdom,$cnum);
+        $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+        if (($env{'request.editurl'} =~ m{^/priv/\Q$cdom/$cnum\E/}) ||
+            ($env{'request.editurl'} =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}) ||
+            ($uri =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/})) {
+            $is_course = 1;
+        } elsif ($env{'request.editurl'} =~ m{^/priv/($match_domain)/($match_username)/}) {
             ($audom,$auname) = ($1,$2);
         } elsif ($env{'request.uri'} =~ m{^/priv/($match_domain)/($match_username)/}) {
             ($audom,$auname) = ($1,$2);
         } elsif (($uri eq '/daxesave') &&
+                 (($env{'form.path'} =~ m{^/daxeopen/priv/\Q$cdom/$cnum\E/}) ||
+                  ($env{'form.path'} =~ m{^/daxeopen/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}))) {
+            $is_course = 1;
+        } elsif (($uri eq '/daxesave') &&
                  ($env{'form.path'} =~ m{^/daxeopen/priv/($match_domain)/($match_username)/})) {
             ($audom,$auname) = ($1,$2);
         }
-        if (($audom ne '') && ($auname ne '')) {
-            if (($env{'user.domain'} eq $audom) &&
-                ($env{'user.name'} eq $auname)) {
-                $is_author = 1;
-            } else {
-                $is_coauthor = 1;
+        unless ($is_course) {
+            if (($audom ne '') && ($auname ne '')) {
+                if (($env{'user.domain'} eq $audom) &&
+                    ($env{'user.name'} eq $auname)) {
+                    $is_author = 1;
+                } else {
+                    $is_coauthor = 1;
+                }
             }
         }
     }
@@ -6656,6 +6741,19 @@ sub permitted_editors {
                          xml => 1,
                        );
         }
+    } elsif ($is_course) {
+        if (exists($env{'course.'.$env{'request.course.id'}.'.internal.crseditors'})) {
+            map { $editors{$_} = 1; } split(/,/,$env{'course.'.$env{'request.course.id'}.'.internal.crseditors'});
+        } else {
+            my %domdefaults = &Apache::lonnet::get_domain_defaults($env{'course.'.$env{'request.course.id'}.'.domain'});
+            if (exists($domdefaults{'crseditors'})) {
+                map { $editors{$_} = 1; } split(/,/,$domdefaults{'crseditors'});
+            } else {
+                %editors = ( edit => 1,
+                             xml => 1,
+                           );
+            }
+        }
     } else {
         %editors = ( edit => 1,
                      xml => 1,
@@ -6890,21 +6988,33 @@ sub bodytag {
         $bodytag .= Apache::lonhtmlcommon::scripttag(
             Apache::lonmenu::utilityfunctions($httphost), 'start');
 
+        my $collapsible;
         if ($args->{'collapsible_header'} ne '') {
-            my $alttext = &mt('menu state: collapsed');
-            my $tooltip = &mt('display standard menus');
+            $collapsible = 1;
+            my ($menustate,$tiptext,$divclass);
+            if ($args->{'start_collapsed'}) {
+                $menustate = 'collapsed';
+                $tiptext = 'display';
+                $divclass = 'hidden';
+            } else {
+                $menustate = 'expanded';
+                $tiptext = 'hide';
+                $divclass = 'shown';
+            }
+            my $alttext = &mt('menu state: '.$menustate);
+            my $tooltip = &mt($tiptext.' standard menus');
             $bodytag .= <<"END";
 <div id="LC_expandingContainer" style="display:inline;">
 <div id="LC_collapsible" class="LC_collapse_trigger" style="position: absolute;top: -5px;left: 0px; z-index:101; display:inline;">
-<a href="#" style="text-decoration:none;"><img class="LC_collapsible_indicator" alt="$alttext" title="$tooltip" src="/res/adm/pages/collapsed.png" style="border:0;margin:0;padding:0;max-width:100%;height:auto" /></a></div>
-<div class="LC_menus_content hidden">
+<a href="#" style="text-decoration:none;"><img class="LC_collapsible_indicator" alt="$alttext" title="$tooltip" src="/res/adm/pages/$menustate.png" style="border:0;margin:0;padding:0;max-width:100%;height:auto" /></a></div>
+<div class="LC_menus_content $divclass">
 END
         }
         unless ($args->{'no_primary_menu'}) {
             my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref,
                                                               $args->{'links_disabled'},
                                                               $args->{'links_target'},
-                                                              $args->{'collapsible_header'});
+                                                              $collapsible);
 
             if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
                 if ($dc_info) {
@@ -7161,7 +7271,7 @@ form, .inline {
 }
 
 .LC_menus_content.shown{
-  display: inline;
+  display: block;
 }
 
 .LC_menus_content.hidden {
@@ -9504,8 +9614,12 @@ OFFLOAD
 	$title = 'The LearningOnline Network with CAPA';
     }
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
-    $result .= '<title> LON-CAPA '.$title.'</title>'
-	.'<link rel="stylesheet" type="text/css" href="'.$url.'"';
+    if ($title =~ /^LON-CAPA\s+/) {
+        $result .= '<title> '.$title.'</title>';
+    } else {
+        $result .= '<title> LON-CAPA '.$title.'</title>';
+    }
+    $result .= "\n".'<link rel="stylesheet" type="text/css" href="'.$url.'"';
     if (!$args->{'frameset'}) {
         $result .= ' /';
     }
@@ -17962,6 +18076,12 @@ sub init_user_environment {
                 } else {
                     $userenv{'editors'} = 'edit,xml';
                 }
+                if ($userenv{'authorarchive'}) {
+                    $userenv{'canarchive'} = 1;
+                } elsif (($userenv{'authorarchive'} eq '') &&
+                         ($domdef{'archive'})) {
+                    $userenv{'canarchive'} = 1;
+                }
             }
 
             $userenv{'canrequest.author'} =