--- loncom/interface/lonhelper.pm	2010/03/10 21:25:50	1.179
+++ loncom/interface/lonhelper.pm	2022/06/27 20:35:51	1.204
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # .helper XML handler to implement the LON-CAPA helper
 #
-# $Id: lonhelper.pm,v 1.179 2010/03/10 21:25:50 droeschl Exp $
+# $Id: lonhelper.pm,v 1.204 2022/06/27 20:35:51 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -64,9 +64,11 @@ messages, resource selections, or date q
 
 The helper tag is required to have one attribute, "title", which is the name
 of the helper itself, such as "Parameter helper". The helper tag may optionally
-have a "requiredpriv" attribute, specifying the priviledge a user must have
+have a "requiredpriv" attribute, specifying the privilege a user must have
 to use the helper, or get denied access. See loncom/auth/rolesplain.tab for
-useful privs. Default is full access, which is often wrong!
+useful privs. You may add the modifier &S at the end of the three letter priv
+if you want to grant access to users for whom the corresponding privilege is 
+section-specific. The default is full access, which is often wrong!
 
 =head2 State tags
 
@@ -261,7 +263,7 @@ sub real_handler {
     my $uri = shift;
     if (!defined($uri)) { $uri = $r->uri(); }
     $env{'request.uri'} = $uri;
-    my $filename = '/home/httpd/html' . $uri;
+    my $filename = $r->dir_config('lonDocRoot').$uri;
     my $fh = Apache::File->new($filename);
     my $file;
     read $fh, $file, 100000000;
@@ -283,7 +285,8 @@ sub real_handler {
 
     my $allowed = $helper->allowedCheck();
     if (!$allowed) {
-        $env{'user.error.msg'} = $env{'request.uri'}.':'.$helper->{REQUIRED_PRIV}.
+        my ($priv,$modifier) = split(/\&/,$helper->{REQUIRED_PRIV});
+        $env{'user.error.msg'} = $env{'request.uri'}.':'.$priv.
             ":0:0:Permission denied to access this helper.";
         return HTTP_NOT_ACCEPTABLE;
     }
@@ -508,8 +511,13 @@ sub allowedCheck {
     if (!defined($self->{REQUIRED_PRIV})) { 
         return 1;
     }
-
-    return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $env{'request.course.id'});
+    my ($priv,$modifier) = split(/\&/,$self->{REQUIRED_PRIV});
+    my $allowed = &Apache::lonnet::allowed($priv,$env{'request.course.id'});
+    if ((!$allowed) && ($modifier eq 'S') && ($env{'request.course.sec'} ne '')) {
+        $allowed = &Apache::lonnet::allowed($priv,$env{'request.course.id'}.'/'.
+                                                  $env{'request.course.sec'});
+    }
+    return $allowed;
 }
 
 sub changeState {
@@ -571,7 +579,7 @@ sub process {
 # 4: Render the current state to the screen as an HTML page.
 sub display {
     my $self = shift;
-
+    my $footer = shift;
     my $state = $self->{STATES}{$self->{STATE}};
 
     my $result = "";
@@ -606,7 +614,7 @@ sub display {
     # FIXME: This should be parameterized, not concatenated - Jeremy
 
 
-    if (!$state->overrideForm()) { $result.='<form name="helpform" method="post">'; }
+    if (!$state->overrideForm()) { $result.='<form name="helpform" method="post" action="">'; }
     if ($stateHelp) {
         $stateHelp = &Apache::loncommon::help_open_topic($stateHelp);
     }
@@ -651,7 +659,7 @@ sub display {
     $result .= $buttons;
 
 
-    #foreach my $key (keys %{$self->{VARS}}) {
+    #foreach my $key (keys(%{$self->{VARS}})) {
     #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
     #}
 
@@ -661,7 +669,7 @@ sub display {
         </form>
 FOOTER
 
-    $result .= &Apache::loncommon::end_page();
+    $result .= $footer.&Apache::loncommon::end_page();
     # Handle writing out the vars to the file
     my $file = Apache::File->new('>'.$self->{FILENAME});
     print $file $self->_varsInFile();
@@ -1476,9 +1484,9 @@ BUTTONS
             HTML::Entities::encode($choice->[1],"<>&\"'") 
             . "'";
         if ($checkedChoices{$choice->[1]}) {
-            $result .= " checked='checked' ";
+            $result .= " checked='checked'";
         }
-        $result .= qq{id="id$id"};
+        $result .= qq{ id="id$id"};
         my $choiceLabel = $choice->[0];
         if ($choice->[3]) {  # if we need to evaluate this choice
             $choiceLabel = "sub { my $helper = shift; my $state = shift;" .
@@ -1486,7 +1494,7 @@ BUTTONS
             $choiceLabel = eval($choiceLabel);
             $choiceLabel = &$choiceLabel($helper, $self);
         }
-        $result .= "/></td><td> ".qq{<label for="id$id">}.
+        $result .= " /></td><td> ".qq{<label for="id$id">}.
 	    $choiceLabel. "</label></td>";
 	if ($choice->[4]) {
 	    $result .='<td><input type="text" size="5" name="'
@@ -1792,7 +1800,7 @@ sub render {
 	if (lc($time) eq 'anytime') {
 	    $anytime=1;
 	    $date = &get_date_object(time);
-	    $date->min(0);
+            $date->set_minute(0);
 	} elsif (defined($time) && $time ne 0) {
 	    $date = &get_date_object($time);
 	} else {
@@ -1802,7 +1810,7 @@ sub render {
 
     if (!defined($date)) {
 	$date = &get_date_object(time);
-	$date->min(0);
+        $date->set_minute(0);
     }
 
     if ($anytime) {
@@ -1913,7 +1921,13 @@ CHECK
 	if ($anytime) {
 	    $result.=' checked="checked" '
 	}
-	$result.="name='${var}anytime'/>".&mt('Any time').'</label>'
+        my $anytimetext = &mt('Any time');
+        if (($var eq 'startreserve') || ($var eq 'endreserve')) {
+            $anytimetext = &mt('Any time before slot starts');
+        } elsif (($var eq 'startunique') || ($var eq 'endunique')) {
+            $anytimetext = &mt('No restriction on uniqueness');     
+        }
+	$result.="name='${var}anytime'/>".$anytimetext.'</label>'
     }
     return $result;
 
@@ -2033,7 +2047,9 @@ be filtered out. The 'addstatus' attribu
 and long status display columns to the display. The 'addparts'
 attribute will add in a part selector beside problems that have more
 than 1 part. The 'includecourse' attribute if true, will include
-the toplevel default.sequence in the results.
+the toplevel default.sequence in the results. The 'modalLink' attribute,
+if true, will cause links to be launched as modal pop-ups, instead of 
+replacing the resource selection listing, currently being displayed. 
 
 =head3 SUB-TAGS
 
@@ -2145,6 +2161,9 @@ sub start_resource {
     $paramHash->{'toponly'} = $token->[2]{'toponly'};
     $paramHash->{'addstatus'} = $token->[2]{'addstatus'};
     $paramHash->{'addparts'} = $token->[2]{'addparts'};
+    $paramHash->{'modalLink'} = $token->[2]{'modallink'};
+    $paramHash->{'nocurrloc'} = $token->[2]{'nocurrloc'};
+    $paramHash->{'suppressNavmap'} = $token->[2]{'suppressNavmap'};
     if ($paramHash->{'addparts'}) {
 	$helper->declareVar($paramHash->{'variable'}.'_part');
     }
@@ -2351,6 +2370,7 @@ BUTTONS
     my $option_texts   = $self->{OPTION_TEXTS};
     my $option_types   = $self->{OPTION_TYPES};
     my $addparts       = $self->{'addparts'};
+    my $modalLink      = $self->{'modalLink'};
     my $headings_done  = 0;
 
     # Evaluate the map url as needed
@@ -2390,7 +2410,7 @@ BUTTONS
 		    $result .= "<th>$text</th>";
 		}
 	    }
-	    $result .= "<th>Select</th>";
+	    $result .= '<th>'.&Apache::lonlocal::mt('Select').'</th>';
 	    $result .= "</tr><tr>"; # Close off the extra row and start a new one.
 	    $headings_done = 1;
 	}
@@ -2450,7 +2470,7 @@ BUTTONS
 			    $resource_name . "'/> </td>";
 		    } else {
 			$col .= 
-			    "<td align='center'><input type=$option_type name ='$option_var".
+			    "<td align='center'><input type='$option_type' name ='$option_var".
 			    "_forminput' value='".
 			    $resource_name . "' $checked /> </td>";
 		    }
@@ -2487,9 +2507,9 @@ BUTTONS
 	    &HTML::Entities::encode(&$valueFunc($resource),"<>&\"'");
 	if ($addparts && (scalar(@{$resource->parts}) > 1)) {
 	    $col .= "<select onclick=\"javascript:updateRadio(this.form,'${var}_forminput','$resource_name');updateHidden(this.form,'$id','${var}');\" name='part_${id}_forminput'>\n";
-	    $col .= "<option value=\"$part\">All Parts</option>\n";
+	    $col .= "<option value=\"$part\">".&Apache::lonlocal::mt('All Parts')."</option>\n";
 	    foreach my $part (@{$resource->parts}) {
-		$col .= "<option value=\"$part\">Part: $part</option>\n";
+		$col .= "<option value=\"$part\">".&Apache::lonlocal::mt('Part: [_1]',$part)."</option>\n";
 	    }
 	    $col .= "</select>";
 	}
@@ -2526,6 +2546,10 @@ RADIO
 	push @$cols, (Apache::lonnavmaps::part_status_summary());
 	
     }
+    my $caller;
+    if ($helper->{TITLE} eq 'Printing Helper') {
+        $caller = 'printout';
+    }
     $result .= 
         &Apache::lonnavmaps::render( { 'cols' => $cols,
                                        'showParts' => 0,
@@ -2534,7 +2558,12 @@ RADIO
 				       'closeAllPages' => $self->{'closeallpages'},
                                        'suppressEmptySequences' => $self->{'suppressEmptySequences'},
 				       'include_top_level_map' => $self->{'include_top_level_map'},
-                                       'iterator_map' => $mapUrl }
+                                       'iterator_map' => $mapUrl,
+                                       'map_no_edit_link' => 1,
+                                       'modalLink' => $modalLink,
+                                       'nocurrloc' => $self->{'nocurrloc'},
+                                       'suppressNavmap' => $self->{'suppressNavmap'},
+                                       'caller' => $caller, }
                                        );
 
     $result .= $buttons;
@@ -2606,6 +2635,12 @@ selection. Defaults to false.
 If true, only active students and course personnel will be
 shown. Defaults to false.
 
+=item * B<sectiononly>:
+
+If true, and user's role is in a specific section, only course personnel 
+will be shown if they also have a section-specific role in the same section.
+Defaults to false.
+
 =item * B<emptyallowed>:
 
 If true, the selection of no users is allowed. Defaults to false.
@@ -2641,6 +2676,7 @@ sub start_student {
     $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
     $paramHash->{'coursepersonnel'} = $token->[2]{'coursepersonnel'};
+    $paramHash->{'sectiononly'} = $token->[2]{'sectiononly'};
     $paramHash->{'activeonly'} = $token->[2]{'activeonly'};
     if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
@@ -2682,14 +2718,17 @@ sub render {
 	delete($defaultUsers{''});
     }
 
+    my $personnel_section;
+    if ($self->{'sectiononly'}) {
+        $personnel_section = $env{'request.course.sec'};
+    }
 
     my ($course_personnel, 
 	$current_members, 
 	$expired_members, 
 	$future_members) = 
-	    &Apache::lonselstudent::get_people_in_class($env{'request.course.sec'});
-
-
+	    &Apache::lonselstudent::get_people_in_class($env{'request.course.sec'},
+	                                                $personnel_section);
 
     # Load up the non-students, if necessary
 
@@ -2697,26 +2736,44 @@ sub render {
 	unshift @$current_members, (@$course_personnel);
     }
 
+    my %titles = &Apache::lonlocal::texthash(
+                   'active'  => 'Select Currently Enrolled Students and Active Course Personnel',
+                   'future'  => 'Select Future Enrolled Students',
+                   'expired' => 'Select Previously Enrolled Students',
+                 );
+
+    if ($env{'request.course.sec'}) {
+        if ($self->{'sectiononly'}) {
+            $titles{'active'} = &mt('Select Currently Enrolled Students and Active Course Personnel in Section: [_1]',
+                                $env{'request.course.sec'});
+        } else {
+            $titles{'active'} = &mt('Select Currently Enrolled Students in Section: [_1], and Active Course Personnel',
+                                    $env{'request.course.sec'});
+        }
+        $titles{'future'} = &mt('Select Future Enrolled Students in Section: [_1]',
+                                $env{'request.course.sec'});
+        $titles{'expired'} = &mt('Select Previously Enrolled Students in Section: [_1]',
+                                 $env{'request.course.sec'});
+    }
 
-    #   Current personel
+    #   Current personnel
 
-    $result .= '<h4>'.&mt('Select Currently Enrolled Students and Active Course Personnel').'</h4>';
+    $result .= '<h4>'.$titles{'active'}.'</h4>';
     $result .= &Apache::lonselstudent::render_student_list( $current_members,
 							    "helpform",
-							    "current", 
+							    "current",
 							    \%defaultUsers,
 							    $self->{'multichoice'},
 							    $self->{'variable'},
 							    1);
 
-
     # If activeonly is not set then we can also give the expired students:
     #
     if (!$self->{'activeonly'} && ((scalar(@$future_members)) > 0)) {
 
 	# And future.
 
-	$result .= '<h4>'.&mt('Select Future Enrolled Students and Future Course Personnel').'</h4>';
+	$result .= '<h4>'.$titles{'future'}.'</h4>';
        
 	$result .= &Apache::lonselstudent::render_student_list( $future_members,
 								"helpform",
@@ -2729,7 +2786,7 @@ sub render {
     if (!$self->{'activeonly'} && ((scalar(@$expired_members)) > 0)) {
 	# Past 
 
-	$result .= '<h4>'.&mt('Select Previously Enrolled Students and Inactive Course Personnel').'</h4>';
+	$result .= '<h4>'.$titles{'expired'}.'</h4>';
 	$result .= &Apache::lonselstudent::render_student_list($expired_members,
 							       "helpform",
 							       "past",
@@ -2971,30 +3028,31 @@ BUTTONS
     }
 
     # Get the list of files in this directory.
-    my @fileList;
+    my (@fileList,$listref,$listerror);
 
     # If the subdirectory is in local CSTR space
     my $metadir;
-    if ($subdir =~ m|/home/([^/]+)/public_html/(.*)|) {
-	my ($user,$domain)= 
-	    &Apache::loncacc::constructaccess($subdir,
-				     $Apache::lonnet::perlvar{'lonDefDomain'});
-	$metadir='/res/'.$domain.'/'.$user.'/'.$2;
-        @fileList = &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/');
-    } elsif ($subdir =~ m|^~([^/]+)/(.*)$|) {
-	$subdir='/home/'.$1.'/public_html/'.$2;
+    my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
+    if ($subdir =~ m{^(?:\Q$londocroot\E)*/priv/[^/]+/[^/]+/(.*)$}) {
+        my $innerpath=$1;
+        unless ($subdir=~m{^\Q$londocroot\E}) {
+           $subdir=$londocroot.$subdir;
+        }
 	my ($user,$domain)= 
-	    &Apache::loncacc::constructaccess($subdir,
-				     $Apache::lonnet::perlvar{'lonDefDomain'});
-	$metadir='/res/'.$domain.'/'.$user.'/'.$2;
-        @fileList = &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/');
+	    &Apache::lonnet::constructaccess($subdir);
+	$metadir='/res/'.$domain.'/'.$user.'/'.$innerpath;
+        ($listref,$listerror) =
+            &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/');
     } else {
         # local library server resource space
-        @fileList = &Apache::lonnet::dirlist($subdir,$env{'user.domain'},$env{'user.name'},undef,undef,'/');
+        ($listref,$listerror) = 
+            &Apache::lonnet::dirlist($subdir,$env{'user.domain'},$env{'user.name'},undef,undef,'/');
     }
 
     # Sort the fileList into order
-    @fileList = sort {lc($a) cmp lc($b)} @fileList;
+    if (ref($listref) eq 'ARRAY') {
+        @fileList = sort {lc($a) cmp lc($b)} @{$listref};
+    }
 
     $result .= $buttons;
 
@@ -3089,7 +3147,7 @@ sub fileState {
     }
     my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'};
     my $subdirpart = $constructionSpaceDir;
-    $subdirpart =~ s/^\/home\/$uname\/public_html//;
+    $subdirpart =~ s{^\Q$docroot/priv/$udom/$uname\E}{};
     my $resdir = $docroot . '/res/' . $udom . '/' . $uname .
         $subdirpart;
 
@@ -3139,7 +3197,7 @@ It takes the standard attributes "variab
 "allowempty" and "nextstate", meaning what they do for most other
 elements.
 
-also takes a boolean 'onlysections' whcih will restrict this to only
+also takes a boolean 'onlysections' which will restrict this to only
 have sections and not include groups
 
 =cut
@@ -3177,22 +3235,27 @@ sub start_section {
 
     # Populate the CHOICES element
     my %choices;
+    my $usersec = $Apache::lonnet::env{'request.course.sec'};
 
-    my $section = Apache::loncoursedata::CL_SECTION();
-    my $classlist = Apache::loncoursedata::get_classlist();
-    foreach my $user (keys(%$classlist)) {
-        my $section_name = $classlist->{$user}[$section];
-        if (!$section_name) {
-            $choices{"No section assigned"} = "";
-        } else {
-            $choices{$section_name} = $section_name;
+    if ($usersec ne '') {
+        $choices{$usersec} = $usersec;
+    } else {
+        my $section = Apache::loncoursedata::CL_SECTION();
+        my $classlist = Apache::loncoursedata::get_classlist();
+        foreach my $user (keys(%$classlist)) {
+            my $section_name = $classlist->{$user}[$section];
+            if (!$section_name) {
+                $choices{"No section assigned"} = "";
+            } else {
+                $choices{$section_name} = $section_name;
+            }
+        }
+ 
+        if (exists($choices{"No section assigned"})) {
+	    push(@{$paramHash->{CHOICES}}, 
+	         ['No section assigned','No section assigned']);
+	    delete($choices{"No section assigned"});
         }
-    } 
-   
-    if (exists($choices{"No section assigned"})) {
-	push(@{$paramHash->{CHOICES}}, 
-	     ['No section assigned','No section assigned']);
-	delete($choices{"No section assigned"});
     }
     for my $section_name (sort {lc($a) cmp lc($b) } (keys(%choices))) {
 	push @{$paramHash->{CHOICES}}, [$section_name, $section_name];
@@ -3228,6 +3291,10 @@ It takes the standard attributes "variab
  "allowempty" and "nextstate", meaning what they do for most other
  elements.
 
+also takes a boolean grouponly, which if true, will restrict choice to
+groups in which user is a member, unless user has the mdg priv in the course,
+in which case all groups will be possible choices. Defaults to false.
+
 =cut
 
 no strict;
@@ -3257,6 +3324,7 @@ sub start_group {
     $helper->declareVar($paramHash->{'variable'});
     $paramHash->{'multichoice'} = $token->[2]{'multichoice'};
     $paramHash->{'allowempty'} = $token->[2]{'allowempty'};
+    $paramHash->{'grouponly'} = $token->[2]{'grouponly'};
     if (defined($token->[2]{'nextstate'})) {
         $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'};
     }
@@ -3264,7 +3332,12 @@ sub start_group {
     # Populate the CHOICES element
     my %choices;
 
-    my %curr_groups = &Apache::longroup::coursegroups();
+    my %curr_groups;
+    if ((!$paramHash->{'grouponly'}) || (&Apache::lonnet::allowed('mdg',$Apache::lonnet::env{'request.course.id'}))) {
+        %curr_groups = &Apache::longroup::coursegroups();
+    } elsif ($Apache::lonnet::env{'request.course.groups'} ne '') {
+        map { $curr_groups{$_} = 1; } split(/:/,$Apache::lonnet::env{'request.course.groups'});
+    }
     foreach my $group_name (sort {lc($a) cmp lc($b)} (keys(%curr_groups))) {
 	push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]);
     }
@@ -3289,6 +3362,10 @@ package Apache::lonhelper::string;
 string elements provide a string entry field for the user. string elements
 take the usual 'variable' and 'nextstate' parameters. string elements
 also pass through 'maxlength' and 'size' attributes to the input tag.
+Since you could have multiple strings in a helper state, each with its own
+validator, all but the last string should have
+noproceed='1' so that _all_ validators are evaluated before the next
+state can be reached.
 
 string honors the defaultvalue tag, if given.
 
@@ -3308,6 +3385,7 @@ BEGIN {
 
 sub new {
     my $ref = Apache::lonhelper::element->new();
+    $ref->{'PROCEED'} = 1;      # By default postprocess goes to next state.
     bless($ref);
 }
 
@@ -3324,20 +3402,33 @@ sub start_string {
     $paramHash->{'nextstate'} = $token->[2]{'nextstate'};
     $paramHash->{'maxlength'} = $token->[2]{'maxlength'};
     $paramHash->{'size'} = $token->[2]{'size'};
-
     return '';
 }
 
 sub end_string {
     my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_;
 
+
     if ($target ne 'helper') {
         return '';
     }
-    Apache::lonhelper::string->new();
+    my $state = Apache::lonhelper::string->new();
+
+
+    if(&Apache::lonxml::get_param('noproceed', $parstack, $safeeval, undef, 1)) {
+	$state->noproceed();
+    }
+
+   
+
     return '';
 }
 
+sub noproceed() {
+    my $self = shift;
+    $self->{PROCEED}  = 0;
+}
+
 sub render {
     my $self = shift;
     my $result = '';
@@ -3346,7 +3437,7 @@ sub render {
         $result .= '<p><font color="#FF0000">' . $self->{ERROR_MSG} . '</font></p>';
     }
 
-    $result .= '<input type="string" name="' . $self->{'variable'} . '_forminput"';
+    $result .= '<input type="text" name="' . $self->{'variable'} . '_forminput"';
 
     if (defined($self->{'size'})) {
         $result .= ' size="' . $self->{'size'} . '"';
@@ -3381,7 +3472,7 @@ sub postprocess {
 	}
     }
 
-    if (defined($self->{'nextstate'})) {
+    if (defined($self->{'nextstate'}) && $self->{PROCEED}) {
         $helper->changeState($self->{'nextstate'});
     }
 
@@ -3608,7 +3699,7 @@ sub render {
     my @results;
 
     # Collect all the results
-    for my $stateName (keys %{$helper->{STATES}}) {
+    for my $stateName (keys(%{$helper->{STATES}})) {
         my $state = $helper->{STATES}->{$stateName};
         
         for my $element (@{$state->{ELEMENTS}}) {
@@ -3749,38 +3840,54 @@ sub render {
     if ($vars->{GRANULARITY} eq 'whole_course') {
         $resourceString .= '<li>'.&mt('for [_1]all resources in the course[_2]','<b>','</b>').'</li>';
 	if ($vars->{TARGETS} eq 'course') {
-	    $level = 14; # general course, see lonparmset.pm perldoc
+	    $level = 18; # general course, see lonparmset.pm perldoc
 	} elsif ($vars->{TARGETS} eq 'section') {
-	    $level = 9;
+	    $level = 12;
 	} elsif ($vars->{TARGETS} eq 'group') {
-	    $level = 6;
+	    $level = 8;
 	} else {
-	    $level = 3;
+	    $level = 4;
 	}
         $affectedResourceId = "0.0";
         $symb = 'a';
         $paramlevel = 'general';
-    } elsif ($vars->{GRANULARITY} eq 'map') {
+    } elsif (($vars->{GRANULARITY} eq 'map') || ($vars->{GRANULARITY} eq 'maprecurse')) {
         my $navmap = Apache::lonnavmaps::navmap->new();
         if (defined($navmap)) {
              my $res = $navmap->getByMapPc($vars->{RESOURCE_ID});
              my $title = $res->compTitle();
              $symb = $res->symb();
-             $resourceString .= '<li>'.&mt('for the map named [_1]',"<b>$title</b>").'</li>';
+             if ($vars->{GRANULARITY} eq 'map') {
+                 $resourceString .= '<li>'.&mt('for the map named [_1]',"<b>$title</b>").'</li>';
+             } else {
+                 $resourceString .= '<li>'.&mt('for the map named [_1] (applies recursively to sub-folders)',"<b>$title</b>").'</li>';
+             }
         } else {
             $resourceString .= '<li>'.&mt('for the map ID [_1] (name unavailable)','<b>'.$vars->{RESOURCE_ID}.'</b>').'</li>';
             &Apache::lonnet::logthis('Retrieval of map title failed in lonhelper.pm - could not create navmap object for course.');
 
         }
-	if ($vars->{TARGETS} eq 'course') {
-	    $level = 13; # general course, see lonparmset.pm perldoc
-	} elsif ($vars->{TARGETS} eq 'section') {
-	    $level = 8;
-	} elsif ($vars->{TARGETS} eq 'group') {
-	    $level = 5;
-	} else {
-	    $level = 2;
-	}
+        if ($vars->{GRANULARITY} eq 'maprecurse') {
+            if ($vars->{TARGETS} eq 'course') {
+                $level = 17; # general course, see lonparmset.pm perldoc
+            } elsif ($vars->{TARGETS} eq 'section') {
+                $level = 11;
+            } elsif ($vars->{TARGETS} eq 'group') {
+                $level = 7;
+            } else {
+                $level = 3;
+            }
+        } else {
+	    if ($vars->{TARGETS} eq 'course') {
+	        $level = 16; # general course, see lonparmset.pm perldoc
+	    } elsif ($vars->{TARGETS} eq 'section') {
+	        $level = 10;
+	    } elsif ($vars->{TARGETS} eq 'group') {
+	        $level = 6;
+	    } else {
+	        $level = 2;
+	    }
+        }
         $affectedResourceId = $vars->{RESOURCE_ID};
         $paramlevel = 'map';
     } else {
@@ -3797,11 +3904,11 @@ sub render {
             &Apache::lonnet::logthis('Retrieval of resource title failed in lonhelper.pm - could not create navmap object for course.');
         }
 	if ($vars->{TARGETS} eq 'course') {
-	    $level = 10; # general course, see lonparmset.pm perldoc
+	    $level = 13; # general course, see lonparmset.pm perldoc
 	} elsif ($vars->{TARGETS} eq 'section') {
-	    $level = 7;
+	    $level = 9;
 	} elsif ($vars->{TARGETS} eq 'group') {
-	    $level = 4;
+	    $level = 5;
 	} else {
 	    $level = 1;
 	}
@@ -3817,7 +3924,7 @@ sub render {
     if ($vars->{GRANULARITY} eq 'resource') {
 	$result .= "<input type='hidden' name='symb' value='".
 	    HTML::Entities::encode($symb,"'<>&\"") . "' />\n";
-    } elsif ($vars->{GRANULARITY} eq 'map') {
+    } elsif (($vars->{GRANULARITY} eq 'map') || ($vars->{GRANULARITY} eq 'maprecurse')) {
 	$result .= "<input type='hidden' name='pschp' value='".
 	    $affectedResourceId."' />\n";
     }