--- loncom/interface/lonhelper.pm	2008/12/13 04:36:28	1.166.2.1
+++ 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.166.2.1 2008/12/13 04:36:28 raeburn 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
 
@@ -188,6 +190,8 @@ use Apache::lonlocal;
 use Apache::lonnet;
 use Apache::longroup;
 use Apache::lonselstudent;
+
+
 use LONCAPA;
 
 # Register all the tags with the helper, so the helper can 
@@ -259,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;
@@ -281,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;
     }
@@ -506,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 {
@@ -529,7 +539,7 @@ sub process {
     # Phase 1: Post processing for state of previous screen (which is actually
     # the "current state" in terms of the helper variables), if it wasn't the 
     # beginning state.
-    if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next ->")) {
+    if ($self->{STATE} ne "START" || $env{"form.SUBMIT"} eq &mt("Next")) {
 	my $prevState = $self->{STATES}{$self->{STATE}};
         $prevState->postprocess();
     }
@@ -569,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 = "";
@@ -587,82 +597,79 @@ sub display {
 	&Apache::loncommon::browser_and_searcher_javascript().
 	"\n".'</script>';
 
+    # Breadcrumbs
+    my $brcrum = [{'href' => '',
+                   'text' => 'Helper'}];
+    # FIXME: Dynamically add context sensitive breadcrumbs
+    #        depending on the caller,
+    #        e.g. printing, parametrization, etc.
+    # FIXME: Add breadcrumbs to reflect current helper state
+
     $result .= &Apache::loncommon::start_page($self->{TITLE},
-					      $browser_searcher_js);
-    
-    my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
-    my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
+                                              $browser_searcher_js,
+                                              {'bread_crumbs' => $brcrum,});
+
+    my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
+    my $next = HTML::Entities::encode(&mt("Next"), '<>&"');
     # 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);
+        $stateHelp = &Apache::loncommon::help_open_topic($stateHelp);
     }
-    $result .= <<HEADER;
-        <table border="0" width='100%'><tr><td>
-        <h2><i>$stateTitle</i>$stateHelp</h2>
-HEADER
-
-    $result .= "<table cellpadding='10' width='100%'><tr><td rowspan='2' valign='top'>";
 
-    if (!$state->overrideForm()) {
-        $result .= $self->_saveVars();
-    }
-    $result .= $state->render();
-
-    $result .= "</td><td valign='top' align='right'>";
-
-    # Warning: Copy and pasted from below, because it's too much trouble to 
-    # turn this into a subroutine
+    # Prepare buttons
+    my $buttons;
     if (!$state->overrideForm()) {
         if ($self->{STATE} ne $self->{START_STATE}) {
             #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
         }
+        $buttons = '<p>'; # '<fieldset>';
         if ($self->{DONE}) {
             my $returnPage = $self->{RETURN_PAGE};
-            $result .= "<a href=\"$returnPage\">" . &mt("End Helper") . "</a>";
+            $buttons .= '<a href="'.$returnPage.'">'.&mt('End Helper').'</a>';
         }
         else {
-            $result .= '<span class="LC_nobreak"><input name="back" type="button" ';
-            $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
-            $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></span>';
+            $buttons .= '<span class="LC_nobreak">'
+                       .'<input name="back" type="button" '
+                       .'value="'.$previous.'" onclick="history.go(-1)" /> '
+                       .'<input name="SUBMIT" type="submit" value="'.$next.'" />'
+                       .'</span>';
         }
+    $buttons .= '</p>'; # '</fieldset>';
     }
 
-    $result .= "</td></tr><tr><td valign='bottom' align='right'>";
 
-    # Warning: Copy and pasted from above, because it's too much trouble to 
-    # turn this into a subroutine
+
+    $result .= '<h2>'.$stateTitle.$stateHelp.'</h2>';
+
+#   $result .= '<div>';
+
+    # Top buttons
+    $result .= $buttons;
+
+    # Main content of current helper screen
     if (!$state->overrideForm()) {
-        if ($self->{STATE} ne $self->{START_STATE}) {
-            #$result .= '<input name="SUBMIT" type="submit" value="&lt;- Previous" />&nbsp;&nbsp;';
-        }
-        if ($self->{DONE}) {
-            my $returnPage = $self->{RETURN_PAGE};
-            $result .= "<a href=\"$returnPage\">" . &mt('End Helper') . "</a>";
-        }
-        else {
-            $result .= '<span class="LC_nobreak"><input name="back" type="button" ';
-            $result .= 'value="' . $previous . '" onclick="history.go(-1)" /> ';
-            $result .= '<input name="SUBMIT" type="submit" value="' . $next . '" /></span>';
-        }
+        $result .= $self->_saveVars();
     }
+    $result .= $state->render();
+
+    # Bottom buttons
+    $result .= $buttons;
+
 
-    #foreach my $key (keys %{$self->{VARS}}) {
+    #foreach my $key (keys(%{$self->{VARS}})) {
     #    $result .= "|$key| -> " . $self->{VARS}->{$key} . "<br />";
     #}
 
-    $result .= "</td></tr></table>";
+#   $result .= '</div>';
 
     $result .= <<FOOTER;
-              </td>
-            </tr>
-          </table>
         </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();
@@ -1477,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;" .
@@ -1487,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="'
@@ -1508,12 +1515,15 @@ sub postprocess {
     my $self = shift;
     my $chosenValue = $env{'form.' . $self->{'variable'} . '_forminput'};
 
+
     if (!defined($chosenValue) && !$self->{'allowempty'}) {
         $self->{ERROR_MSG} = 
 	    &mt("You must choose one or more choices to continue.");
         return 0;
     }
 
+
+
     if (ref($chosenValue)) {
         $helper->{VARS}->{$self->{'variable'}} = join('|||', @$chosenValue);
     }
@@ -1790,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 {
@@ -1800,7 +1810,7 @@ sub render {
 
     if (!defined($date)) {
 	$date = &get_date_object(time);
-	$date->min(0);
+        $date->set_minute(0);
     }
 
     if ($anytime) {
@@ -1911,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;
 
@@ -2031,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
 
@@ -2064,6 +2082,49 @@ the toplevel default.sequence in the res
   evaluated with "sub { my $helper = shift; my $state = shift;" and
   "}", with the return value used as the mapurl.
 
+=item * <option />: Allows you to add optional elements to the
+  resource chooser currently these can be a checkbox, or a text entry
+  or hidden (see the 'type' attribute below).
+  the following attributes are supported by this tag:
+
+=over 4
+
+=item * type=control-type : determines the type of control displayed.
+  This can be one of the following types: 'checkbox' provides a true/false
+  checkbox.  'text' provides a text entry control. 'hidden' provides a
+  hidden form element that returns the name of the resource for each
+  element of the text box.
+
+=item * text=header-text : provides column header text for the option.
+  
+=item * variable=helpervar : provides a helper variable to contain the
+  value of the input control for each resource.  In general, the result
+  will be a set of values separated by |||  for the checkbox the value between
+  the |||'s will either be empty, if the box is not checked, or the resource
+  name if checked.  For the text entry, the values will be the text in the
+  text box.  This could be empty.  Hidden elements unconditionally provide
+  the resource name for each row of the chooser and allow you to therefore
+  correlate text entries to their resources.
+  The helper variable can be initialized by the user code to pre-load values
+  into the controls:
+
+=over 4
+
+  
+=item * Preloading checkboxes : Set the helper variable to the value you
+   would have gotten from the control if it had been manually set as desired.
+
+=item * Preloading text entries : Set the helper variable to triple pipe
+   separated values where each value is of the form resource-name=value
+
+=item * Preloading hidden fields : These cannot be pre-loaded and will always
+  be pipe separated resource names.
+
+=back
+
+
+=back
+
 =back
 
 =cut
@@ -2100,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');
     }
@@ -2193,20 +2257,42 @@ sub start_option {
     if (!defined($paramHash->{OPTION_TEXTS})) {
 	$paramHash->{OPTION_TEXTS} = [ ];
 	$paramHash->{OPTION_VARS}  = [ ];
+	$paramHash->{OPTION_TYPES} = [ ];
 
     }
+    #  We can have an attribute: type which can have the
+    #  values: "checkbox" or "text" which defaults to 
+    #           checkbox allowing us to change the type of input
+    #           for the option:
+    #
+    my $input_widget_type = 'checkbox';
+    if(defined($token->[2]{'type'})) {
+	my $widget_type  = $token->[2]{'type'};
+	if ($widget_type eq 'text') {          # only accept legal alternatives
+	    $input_widget_type = $widget_type; # Illegals are checks.
+	} elsif ($widget_type eq 'hidden') {
+	    $input_widget_type = $widget_type;
+	}
+    }
+
     # OPTION_TEXTS is a list of the text attribute
     #               values used to create column headings.
     # OPTION_VARS is a list of the variable names, used to create the checkbox
     #             inputs.
+    # OPTION_TYPES is a list of the option types:
+    #
     #  We're ok with empty elements. as place holders
     # Although the 'variable' element should really exist.
     #
 
+
     my $option_texts  = $paramHash->{OPTION_TEXTS};
     my $option_vars   = $paramHash->{OPTION_VARS};
+    my $option_types   = $paramHash->{OPTION_TYPES};
     push(@$option_texts,  $token->[2]{'text'});
     push(@$option_vars,   $token->[2]{'variable'});
+    push(@$option_types,   $input_widget_type);
+
 
     #  Need to create and declare the option variables as well to make them
     # persistent.
@@ -2282,7 +2368,9 @@ BUTTONS
     my $multichoice    = $self->{'multichoice'};
     my $option_vars    = $self->{OPTION_VARS};
     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
@@ -2322,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;
 	}
@@ -2340,17 +2428,52 @@ BUTTONS
 	    my $resource_name =   
                    HTML::Entities::encode($raw_name,"<>&\"'");
 	    if($option_vars) {
+		my $option_num = 0;
 		foreach my $option_var (@$option_vars) {
+		    my $option_type = $option_types->[$option_num];
+		    $option_num++;
 		    my $var_value = "\|\|\|" . $helper->{VARS}->{$option_var} . 
 			"\|\|\|";
 		    my $checked ="";
 		    if($var_value =~ /\Q|||$raw_name|||\E/) {
 			$checked = "checked='checked'";
 		    }
-		    $col .= 
-                        "<td align='center'><input type='checkbox' name ='$option_var".
-			"_forminput' value='".
-			$resource_name . "' $checked /> </td>";
+		    if ($option_type eq 'text') {
+			#
+			# For text's the variable value is a ||| separated set of
+			# resource_name=value 
+			#
+			my @values = split(/\|\|\|/, $helper->{VARS}->{$option_var});
+
+			# Normal practice would be to toss this in a hash but 
+			# the only thing that saves is the compare in the loop
+			# below and for all but one case we'll break out of the loop
+			# before it completes.
+
+			my $text_value = '';    # In case there's no match.
+			foreach my $value (@values) {
+			    my ($res, $skip) = split(/=/, $value);
+			    if($res eq $resource_name) {
+				$text_value = $skip;
+				last;
+			    }
+			}
+			# TODO: add an attribute to <option> that allows the
+			#       programmer to set the width of the tex entry box.
+
+			$col .=
+			    "<td align='center'><input type='text' name ='$option_var".
+			    "_forminput' value='".$text_value."' size='5' /> </td>";
+		    } elsif ($option_type eq 'hidden') {
+ 			$col .= "<td align='center'><input type='hidden' name ='$option_var".
+			    "_forminput' value='".
+			    $resource_name . "'/> </td>";
+		    } else {
+			$col .= 
+			    "<td align='center'><input type='$option_type' name ='$option_var".
+			    "_forminput' value='".
+			    $resource_name . "' $checked /> </td>";
+		    }
 		}
 	    }
 
@@ -2384,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>";
 	}
@@ -2423,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,
@@ -2431,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;
@@ -2446,6 +2578,21 @@ sub postprocess {
         $self->{ERROR_MSG} = 'You must choose at least one resource to continue.';
         return 0;
     }
+    # For each of the attached options.  If it's env var is undefined, set it to
+    # an empty string instead.. an undef'd env var means no choices selected.
+    #
+
+    my $option_vars = $self->{OPTION_VARS};
+    if ($option_vars) {
+	foreach my $var (@$option_vars) {
+	    my $env_name = "form.".$var."_forminput";
+	    if (!defined($env{$env_name})) {
+		$env{$env_name} = '';
+		$helper->{VARS}->{$var} = '';
+	    }
+	}
+    }
+
 
     if (defined($self->{NEXTSTATE})) {
         $helper->changeState($self->{NEXTSTATE});
@@ -2488,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.
@@ -2523,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'};
@@ -2564,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
 
@@ -2579,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",
@@ -2611,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",
@@ -2853,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;
 
@@ -2971,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;
 
@@ -3021,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
@@ -3059,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];
@@ -3110,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;
@@ -3139,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'};
     }
@@ -3146,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]);
     }
@@ -3171,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.
 
@@ -3190,6 +3385,7 @@ BEGIN {
 
 sub new {
     my $ref = Apache::lonhelper::element->new();
+    $ref->{'PROCEED'} = 1;      # By default postprocess goes to next state.
     bless($ref);
 }
 
@@ -3206,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 = '';
@@ -3228,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'} . '"';
@@ -3263,7 +3472,7 @@ sub postprocess {
 	}
     }
 
-    if (defined($self->{'nextstate'})) {
+    if (defined($self->{'nextstate'}) && $self->{PROCEED}) {
         $helper->changeState($self->{'nextstate'});
     }
 
@@ -3421,7 +3630,7 @@ snippets and collecting the results. Fin
 helper, going to a provided page.
 
 If the parameter "restartCourse" is true, this will override the buttons and
-will make a "Finish Helper" button that will re-initialize the course for them,
+will make a Save button (Finish Helper) that will re-initialize the course for them,
 which is useful for the Course Initialization helper so the users never see
 the old values taking effect.
 
@@ -3490,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}}) {
@@ -3528,7 +3737,7 @@ sub render {
 
     my $actionURL = $self->{EXIT_PAGE};
     my $targetURL = '';
-    my $finish=&mt('Finish');
+	my $finish=&mt('Save');
     if ($self->{'restartCourse'}) {
 	$actionURL = '/adm/roles';
 	$targetURL = '/adm/menu';
@@ -3540,21 +3749,17 @@ sub render {
 	if ($env{'course.'.$env{'request.course.id'}.'.clonedfrom'}) {
 	    $targetURL = '/adm/parmset?overview=1';
 	}
-	my $finish=&mt('Finish Course Initialization');
     }
-    my $previous = HTML::Entities::encode(&mt("<- Previous"), '<>&"');
-    my $next = HTML::Entities::encode(&mt("Next ->"), '<>&"');
-    my $target = " target='loncapaclient'";
-    if (($env{'browser.interface'} eq 'textual') ||
-        ($env{'environment.remote'} eq 'off')) {  $target='';  }
-    $result .= "<center>\n" .
-	"<form action='".$actionURL."' method='post' $target>\n" .
+    my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
+    my $next = HTML::Entities::encode(&mt("Next"), '<>&"');
+    $result .= "<p>\n" .
+	"<form action='".$actionURL."' method='post' >\n" .
 	"<input type='button' onclick='history.go(-1)' value='$previous' />" .
 	"<input type='hidden' name='orgurl' value='$targetURL' />" .
 	"<input type='hidden' name='selectrole' value='1' />\n" .
 	"<input type='hidden' name='" . $env{'request.role'} . 
 	"' value='1' />\n<input type='submit' value='" . $finish . "' />\n" .
-	"</form></center>";
+	"</form></p>\n";
 
     return $result;
 }
@@ -3633,50 +3838,77 @@ sub render {
     
     # Print the granularity, depending on the action
     if ($vars->{GRANULARITY} eq 'whole_course') {
-        $resourceString .= '<li>'.&mt('for <b>all resources in the course</b>').'</li>';
+        $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();
-        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->{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 (defined($navmap)) {
+             my $res = $navmap->getByMapPc($vars->{RESOURCE_ID});
+             my $title = $res->compTitle();
+             $symb = $res->symb();
+             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->{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 {
-        my $navmap = Apache::lonnavmaps::navmap->new();
-        my $res = $navmap->getById($vars->{RESOURCE_ID});
         my $part = $vars->{RESOURCE_ID_part};
 	if ($part ne 'All Parts' && $part) { $parm_name=~s/^0/$part/; } else { $part=&mt('All Parts'); }
-        $symb = $res->symb();
-        my $title = $res->compTitle();
-        $resourceString .= '<li>'.&mt('for the resource named [_1] part [_2]',"<b>$title</b>","<b>$part</b>").'</li>';
+        my $navmap = Apache::lonnavmaps::navmap->new();
+        if (defined($navmap)) {
+            my $res = $navmap->getById($vars->{RESOURCE_ID});
+            $symb = $res->symb();
+            my $title = $res->compTitle();
+            $resourceString .= '<li>'.&mt('for the resource named [_1], part [_2]',"<b>$title</b>","<b>$part</b>").'</li>';
+        } else {
+            $resourceString .= '<li>'.&mt('for the resource ID [_1] (name unavailable), part [_2]','<b>'.$vars->{RESOURCE_ID}.'</b>',"<b>$part</b>").'</li>';
+            &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;
 	}
@@ -3684,7 +3916,7 @@ sub render {
         $paramlevel = 'full';
     }
 
-    my $result = "<form name='helpform' method='POST' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
+    my $result = "<form name='helpform' method='post' action='/adm/parmset#$affectedResourceId&$parm_name&$level'>\n";
     $result .= "<input type='hidden' name='action' value='settable' />\n";
     $result .= "<input type='hidden' name='dis' value='helper' />\n";
     $result .= "<input type='hidden' name='pscat' value='".
@@ -3692,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";
     }
@@ -3701,7 +3933,10 @@ sub render {
     $result .= "<input type='hidden' name='psprt' value='".
 	HTML::Entities::encode($part,"'<>&\"") . "' />\n";
 
-    $result .= '<p>'.&mt('Confirm that this information is correct, then click &quot;Finish Helper&quot; to complete setting the parameter.').'<ul>';
+    $result .= '<p class="LC_info">'
+              .&mt('Confirm that this information is correct, then click &quot;Save&quot; to complete setting the parameter.')
+              .'</p>'
+              .'<ul>';
     
     # Print the type of manipulation:
     my $extra;
@@ -3749,7 +3984,7 @@ sub render {
     
     # Print targets
     if ($vars->{TARGETS} eq 'course') {
-        $result .= '<li>'.&mt('for <b>all students in course</b>').'</li>';
+        $result .= '<li>'.&mt('for [_1]all students in course[_2]','<b>','</b>').'</li>';
     } elsif ($vars->{TARGETS} eq 'section') {
         my $section = $vars->{SECTION_NAME};
         $result .= '<li>'.&mt('for section [_1]',"<b>$section</b>").'</li>';
@@ -3777,7 +4012,18 @@ sub render {
         my $showdate = &Apache::lonlocal::locallocaltime($vars->{PARM_DATE});
 	$result .= '<li>'.&mt('to [_1] ([_2])',"<b>".$showdate."</b>",Apache::lonnavmaps::timeToHumanString($vars->{PARM_DATE}))."</li>\n";
     }
+
+	$result .= '</ul>';
  
+# FIXME: Make previous button working
+#        Found to be dysfunctional when used to change the selected student
+#   my $previous = HTML::Entities::encode(&mt("Back"), '<>&"');
+    my $buttons .= '<p><span class="LC_nobreak">'
+#                 .'<input name="back" type="button"'
+#                 .' value="'.$previous.'" onclick="history.go(-1)" />'
+                  .' <input type="submit" value="'.&mt('Save').'" />' # Finish Helper
+                  .'</span></p>'."\n";
+
     # print pres_marker
     $result .= "\n<input type='hidden' name='pres_marker'" .
         " value='$affectedResourceId&$parm_name&$level' />\n";
@@ -3787,7 +4033,7 @@ sub render {
     $result .= "\n<input type='hidden' value='$symb' name='pssymb' />";
     $result .= "\n<input type='hidden' value='$paramlevel' name='parmlev' />";
 
-    $result .= "<br /><br /><center><input type='submit' value='".&mt('Finish Helper')."' /></center></form>\n";
+    $result .= $buttons;
 
     return $result;
 }