--- loncom/interface/loncommon.pm	2003/10/09 22:07:08	1.127
+++ loncom/interface/loncommon.pm	2006/03/19 22:48:53	1.314
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.127 2003/10/09 22:07:08 matthew Exp $
+# $Id: loncommon.pm,v 1.314 2006/03/19 22:48:53 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -25,13 +25,6 @@
 #
 # http://www.lon-capa.org/
 #
-# YEAR=2001
-# 2/13-12/7 Guy Albertelli
-# 12/21 Gerd Kortemeyer
-# 12/25,12/28 Gerd Kortemeyer
-# YEAR=2002
-# 1/4 Gerd Kortemeyer
-# 6/24,7/2 H. K. Ng
 
 # Makes a table out of the previous attempts
 # Inputs result_from_symbread, user, domain, course_id
@@ -62,26 +55,25 @@ redundancy from other modules and increa
 package Apache::loncommon;
 
 use strict;
-use Apache::lonnet();
+use Apache::lonnet;
 use GDBM_File;
 use POSIX qw(strftime mktime);
 use Apache::Constants qw(:common :http :methods);
-use Apache::lonmsg();
 use Apache::lonmenu();
 use Apache::lonlocal;
+use HTML::Entities;
 
 my $readit;
 
-=pod 
-
-=head1 Global Variables
-
-=cut
+##
+## Global Variables
+##
 
 # ----------------------------------------------- Filetypes/Languages/Copyright
 my %language;
 my %supported_language;
 my %cprtag;
+my %scprtag;
 my %fe; my %fd;
 my %category_extensions;
 
@@ -90,50 +82,19 @@ my %category_extensions;
 my %designhash;
 
 # ---------------------------------------------- Thesaurus variables
-
-# FIXME: I don't think it's necessary to document these things;
-# they're privately used - Jeremy
-
-=pod
-
-=over 4
-
-=item * %Keywords  
-
-A hash used by &keyword to determine if a word is considered a keyword.
-
-=item * $thesaurus_db_file
-
-Scalar containing the full path to the thesaurus database.                 
-
-=back
-
-=cut
+#
+# %Keywords:
+#      A hash used by &keyword to determine if a word is considered a keyword.
+# $thesaurus_db_file 
+#      Scalar containing the full path to the thesaurus database.
 
 my %Keywords;
 my $thesaurus_db_file;
 
-# ----------------------------------------------------------------------- BEGIN
-
-# FIXME: I don't think this needs to be documented, it prepares
-# private data structures - Jeremy
-=pod
-
-=head1 General Subroutines
-
-=over 4
-
-=item * BEGIN() 
-
-Initialize values from language.tab, copyright.tab, filetypes.tab,
-thesaurus.tab, and filecategories.tab.
-
-=back
-
-=cut
-
-# ----------------------------------------------------------------------- BEGIN
-
+#
+# Initialize values from language.tab, copyright.tab, filetypes.tab,
+# thesaurus.tab, and filecategories.tab.
+#
 BEGIN {
     # Variable initialization
     $thesaurus_db_file = $Apache::lonnet::perlvar{'lonTabDir'}."/thesaurus.db";
@@ -141,32 +102,48 @@ BEGIN {
     unless ($readit) {
 # ------------------------------------------------------------------- languages
     {
-	my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.
-				 '/language.tab');
-	if ($fh) {
-	    while (<$fh>) {
-		next if /^\#/;
-		chomp;
-		my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$_));
-		$language{$key}=$val.' - '.$enc;
-		if ($sup) {
-		    $supported_language{$key}=$sup;
-		}
-	    }
-	}
+        my $langtabfile = $Apache::lonnet::perlvar{'lonTabDir'}.
+                                   '/language.tab';
+        if ( open(my $fh,"<$langtabfile") ) {
+            while (<$fh>) {
+                next if /^\#/;
+                chomp;
+                my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$_));
+                $language{$key}=$val.' - '.$enc;
+                if ($sup) {
+                    $supported_language{$key}=$sup;
+                }
+            }
+            close($fh);
+        }
     }
 # ------------------------------------------------------------------ copyrights
     {
-	my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonIncludes'}.
-				  '/copyright.tab');
-	if ($fh) {
-	    while (<$fh>) {
-		next if /^\#/;
-		chomp;
-		my ($key,$val)=(split(/\s+/,$_,2));
-		$cprtag{$key}=$val;
-	    }
-	}
+        my $copyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}.
+                                  '/copyright.tab';
+        if ( open (my $fh,"<$copyrightfile") ) {
+            while (<$fh>) {
+                next if /^\#/;
+                chomp;
+                my ($key,$val)=(split(/\s+/,$_,2));
+                $cprtag{$key}=$val;
+            }
+            close($fh);
+        }
+    }
+# ------------------------------------------------------------------ source copyrights
+    {
+        my $sourcecopyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}.
+                                  '/source_copyright.tab';
+        if ( open (my $fh,"<$sourcecopyrightfile") ) {
+            while (<$fh>) {
+                next if /^\#/;
+                chomp;
+                my ($key,$val)=(split(/\s+/,$_,2));
+                $scprtag{$key}=$val;
+            }
+            close($fh);
+        }
     }
 
 # -------------------------------------------------------------- domain designs
@@ -175,18 +152,20 @@ BEGIN {
     my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors';
     opendir(DIR,$designdir);
     while ($filename=readdir(DIR)) {
+	if ($filename!~/\.tab$/) { next; }
 	my ($domain)=($filename=~/^(\w+)\./);
-    {
-	my $fh=Apache::File->new($designdir.'/'.$filename);
-	if ($fh) {
-	    while (<$fh>) {
-		next if /^\#/;
-		chomp;
-		my ($key,$val)=(split(/\=/,$_));
-		if ($val) { $designhash{$domain.'.'.$key}=$val; }
+	{
+	    my $designfile = $designdir.'/'.$filename;
+	    if ( open (my $fh,"<$designfile") ) {
+		while (<$fh>) {
+		    next if /^\#/;
+		    chomp;
+		    my ($key,$val)=(split(/\=/,$_));
+		    if ($val) { $designhash{$domain.'.'.$key}=$val; }
+		}
+		close($fh);
 	    }
 	}
-    }
 
     }
     closedir(DIR);
@@ -194,32 +173,35 @@ BEGIN {
 
 # ------------------------------------------------------------- file categories
     {
-	my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.
-				  '/filecategories.tab');
-	if ($fh) {
-	    while (<$fh>) {
-		next if /^\#/;
-		chomp;
-		my ($extension,$category)=(split(/\s+/,$_,2));
-		push @{$category_extensions{lc($category)}},$extension;
-	    }
-	}
+        my $categoryfile = $Apache::lonnet::perlvar{'lonTabDir'}.
+                                  '/filecategories.tab';
+        if ( open (my $fh,"<$categoryfile") ) {
+            while (<$fh>) {
+                next if /^\#/;
+                chomp;
+                my ($extension,$category)=(split(/\s+/,$_,2));
+                push @{$category_extensions{lc($category)}},$extension;
+            }
+            close($fh);
+        }
+
     }
 # ------------------------------------------------------------------ file types
     {
-	my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.
-	       '/filetypes.tab');
-	if ($fh) {
+        my $typesfile = $Apache::lonnet::perlvar{'lonTabDir'}.
+               '/filetypes.tab';
+        if ( open (my $fh,"<$typesfile") ) {
             while (<$fh>) {
-		next if (/^\#/);
-		chomp;
-		my ($ending,$emb,$descr)=split(/\s+/,$_,3);
-		if ($descr ne '') { 
-		    $fe{$ending}=lc($emb);
-		    $fd{$ending}=$descr;
-		}
-	    }
-	}
+                next if (/^\#/);
+                chomp;
+                my ($ending,$emb,$descr)=split(/\s+/,$_,3);
+                if ($descr ne '') {
+                    $fe{$ending}=lc($emb);
+                    $fd{$ending}=$descr;
+                }
+            }
+            close($fh);
+        }
     }
     &Apache::lonnet::logthis(
               "<font color=yellow>INFO: Read file types</font>");
@@ -245,8 +227,6 @@ containing javascript with two functions
 C<opensearcher>. Returned string does not contain E<lt>scriptE<gt>
 tags.
 
-=over 4
-
 =item * openbrowser(formname,elementname,only,omit) [javascript]
 
 inputs: formname, elementname, only, omit
@@ -255,10 +235,10 @@ formname and elementname indicate the na
 the element that the results of the browsing selection are to be placed in. 
 
 Specifying 'only' will restrict the browser to displaying only files
-with the given extension.  Can be a comma seperated list.
+with the given extension.  Can be a comma separated list.
 
 Specifying 'omit' will restrict the browser to NOT displaying files
-with the given extension.  Can be a comma seperated list.
+with the given extension.  Can be a comma separated list.
 
 =item * opensearcher(formname, elementname) [javascript]
 
@@ -267,43 +247,59 @@ Inputs: formname, elementname
 formname and elementname specify the name of the html form and the name
 of the element the selection from the search results will be placed in.
 
-=back
-
 =cut
 
 sub browser_and_searcher_javascript {
+    my ($mode)=@_;
+    if (!defined($mode)) { $mode='edit'; }
+    my $resurl=&lastresurl();
     return <<END;
+// <!-- BEGIN LON-CAPA Internal
     var editbrowser = null;
-    function openbrowser(formname,elementname,only,omit) {
-        var url = '/res/?';
+    function openbrowser(formname,elementname,only,omit,titleelement) {
+        var url = '$resurl/?';
         if (editbrowser == null) {
             url += 'launch=1&';
         }
         url += 'catalogmode=interactive&';
-        url += 'mode=edit&';
+        url += 'mode=$mode&';
         url += 'form=' + formname + '&';
         if (only != null) {
             url += 'only=' + only + '&';
-        } 
+        } else {
+            url += 'only=&';
+	}
         if (omit != null) {
             url += 'omit=' + omit + '&';
-        }
+        } else {
+            url += 'omit=&';
+	}
+        if (titleelement != null) {
+            url += 'titleelement=' + titleelement + '&';
+        } else {
+	    url += 'titleelement=&';
+	}
         url += 'element=' + elementname + '';
         var title = 'Browser';
-        var options = 'scrollbars=1,resizable=1,menubar=0';
+        var options = 'scrollbars=1,resizable=1,menubar=1,location=1';
         options += ',width=700,height=600';
         editbrowser = open(url,title,options,'1');
         editbrowser.focus();
     }
     var editsearcher;
-    function opensearcher(formname,elementname) {
+    function opensearcher(formname,elementname,titleelement) {
         var url = '/adm/searchcat?';
         if (editsearcher == null) {
             url += 'launch=1&';
         }
         url += 'catalogmode=interactive&';
-        url += 'mode=edit&';
+        url += 'mode=$mode&';
         url += 'form=' + formname + '&';
+        if (titleelement != null) {
+            url += 'titleelement=' + titleelement + '&';
+        } else {
+	    url += 'titleelement=&';
+	}
         url += 'element=' + elementname + '';
         var title = 'Search';
         var options = 'scrollbars=1,resizable=1,menubar=0';
@@ -311,14 +307,35 @@ sub browser_and_searcher_javascript {
         editsearcher = open(url,title,options,'1');
         editsearcher.focus();
     }
+// END LON-CAPA Internal -->
 END
 }
 
+sub lastresurl {
+    if ($env{'environment.lastresurl'}) {
+	return $env{'environment.lastresurl'}
+    } else {
+	return '/res';
+    }
+}
+
+sub storeresurl {
+    my $resurl=&Apache::lonnet::clutter(shift);
+    unless ($resurl=~/^\/res/) { return 0; }
+    $resurl=~s/\/$//;
+    &Apache::lonnet::put('environment',{'lastresurl' => $resurl});
+    &Apache::lonnet::appenv('environment.lastresurl' => $resurl);
+    return 1;
+}
+
 sub studentbrowser_javascript {
    unless (
-            (($ENV{'request.course.id'}) && 
-             (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'})))
-         || ($ENV{'request.role'}=~/^(au|dc|su)/)
+            (($env{'request.course.id'}) && 
+             (&Apache::lonnet::allowed('srm',$env{'request.course.id'})
+	      || &Apache::lonnet::allowed('srm',$env{'request.course.id'}.
+					  '/'.$env{'request.course.sec'})
+	      ))
+         || ($env{'request.role'}=~/^(au|dc|su)/)
           ) { return ''; }  
    return (<<'ENDSTDBRW');
 <script type="text/javascript" language="Javascript" >
@@ -347,14 +364,16 @@ ENDSTDBRW
 
 sub selectstudent_link {
    my ($form,$unameele,$udomele)=@_;
-   if ($ENV{'request.course.id'}) {  
-       unless (&Apache::lonnet::allowed('srm',$ENV{'request.course.id'})) {
+   if ($env{'request.course.id'}) {  
+       if (!&Apache::lonnet::allowed('srm',$env{'request.course.id'})
+	   && !&Apache::lonnet::allowed('srm',$env{'request.course.id'}.
+					'/'.$env{'request.course.sec'})) {
 	   return '';
        }
        return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele.
         '","'.$udomele.'");'."'>".&mt('Select User')."</a>";
    }
-   if ($ENV{'request.role'}=~/^(au|dc|su)/) {
+   if ($env{'request.role'}=~/^(au|dc|su)/) {
        return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele.
         '","'.$udomele.'",1);'."'>".&mt('Select User')."</a>";
    }
@@ -362,10 +381,11 @@ sub selectstudent_link {
 }
 
 sub coursebrowser_javascript {
-   return (<<'ENDSTDBRW');
+    my ($domainfilter)=@_;
+   return (<<ENDSTDBRW);
 <script type="text/javascript" language="Javascript" >
     var stdeditbrowser;
-    function opencrsbrowser(formname,uname,udom) {
+    function opencrsbrowser(formname,uname,udom,desc,extra_element,multflag) {
         var url = '/adm/pickcourse?';
         var filter;
         if (filter != null) {
@@ -373,8 +393,24 @@ sub coursebrowser_javascript {
                url += 'filter='+filter+'&';
 	   }
         }
+        var domainfilter='$domainfilter';
+        if (domainfilter != null) {
+           if (domainfilter != '') {
+               url += 'domainfilter='+domainfilter+'&';
+	   }
+        }
         url += 'form=' + formname + '&cnumelement='+uname+
-                                    '&cdomelement='+udom;
+	                            '&cdomelement='+udom+
+                                    '&cnameelement='+desc;
+        if (extra_element !=null && extra_element != '' && formname == 'rolechoice') {
+            url += '&roleelement='+extra_element;
+            if (domainfilter == null || domainfilter == '') {
+                url += '&domainfilter='+extra_element;
+            }
+        }
+        if (multflag !=null && multflag != '') {
+            url += '&multiple='+multflag;
+        }
         var title = 'Course_Browser';
         var options = 'scrollbars=1,resizable=1,menubar=0';
         options += ',width=700,height=600';
@@ -386,10 +422,35 @@ ENDSTDBRW
 }
 
 sub selectcourse_link {
-   my ($form,$unameele,$udomele)=@_;
+   my ($form,$unameele,$udomele,$desc,$extra_element,$multflag)=@_;
     return "<a href='".'javascript:opencrsbrowser("'.$form.'","'.$unameele.
-        '","'.$udomele.'");'."'>".&mt('Select Course')."</a>";
+        '","'.$udomele.'","'.$desc.'","'.$extra_element.'","'.$multflag.'");'."'>".&mt('Select Course')."</a>";
+}
+
+sub check_uncheck_jscript {
+    my $jscript = <<"ENDSCRT";
+function checkAll(field) {
+    if (field.length > 0) {
+        for (i = 0; i < field.length; i++) {
+            field[i].checked = true ;
+        }
+    } else {
+        field.checked = true
+    }
+}
+ 
+function uncheckAll(field) {
+    if (field.length > 0) {
+        for (i = 0; i < field.length; i++) {
+            field[i].checked = false ;
+        }     } else {
+        field.checked = false ;
+    }
 }
+ENDSCRT
+    return $jscript;
+}
+
 
 =pod
 
@@ -471,7 +532,7 @@ sub linked_select_forms {
     my $first = "document.$formname.$firstselectname";
     # output the javascript to do the changing
     my $result = '';
-    $result.="<script>\n";
+    $result.="<script type=\"text/javascript\">\n";
     $result.="var select2data = new Object();\n";
     $" = '","';
     my $debug = '';
@@ -507,6 +568,7 @@ function select1_changed() {
     // in with the nuclear
     for (i=0;i<values.length; i++) {
         $second.options[i] = new Option(values[i]);
+        $second.options[i].value = values[i];
         $second.options[i].text = texts[i];
         if (values[i] == select2def) {
             $second.options[i].selected = true;
@@ -519,7 +581,7 @@ END
     $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1_changed()\">\n";
     foreach my $value (sort(keys(%$hashref))) {
         $result.="    <option value=\"$value\" ";
-        $result.=" selected=\"true\" " if ($value eq $firstdefault);
+        $result.=" selected=\"selected\" " if ($value eq $firstdefault);
         $result.=">".&mt($hashref->{$value}->{'text'})."</option>\n";
     }
     $result .= "</select>\n";
@@ -529,7 +591,7 @@ END
     my $seconddefault = $hashref->{$firstdefault}->{'default'};
     foreach my $value (sort(keys(%select2))) {
         $result.="    <option value=\"$value\" ";        
-        $result.=" selected=\"true\" " if ($value eq $seconddefault);
+        $result.=" selected=\"selected\" " if ($value eq $seconddefault);
         $result.=">".&mt($select2{$value})."</option>\n";
     }
     $result .= "</select>\n";
@@ -565,8 +627,8 @@ sub help_open_topic {
     my ($topic, $text, $stayOnPage, $width, $height) = @_;
     $text = "" if (not defined $text);
     $stayOnPage = 0 if (not defined $stayOnPage);
-    if ($ENV{'browser.interface'} eq 'textual' ||
-	$ENV{'environment.remote'} eq 'off' ) {
+    if ($env{'browser.interface'} eq 'textual' ||
+	$env{'environment.remote'} eq 'off' ) {
 	$stayOnPage=1;
     }
     $width = 350 if (not defined $width);
@@ -577,6 +639,8 @@ sub help_open_topic {
     my $template = "";
     my $link;
 
+    $topic=~s/\W/\_/g;
+
     if (!$stayOnPage)
     {
 	$link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
@@ -595,8 +659,10 @@ sub help_open_topic {
     }
 
     # Add the graphic
+    my $title = &mt('Online Help');
+    my $helpicon=&lonhttpdurl("/adm/help/gif/smallHelp.gif");
     $template .= <<"ENDTEMPLATE";
- <a href="$link"><image src="/adm/help/gif/smallHelp.gif" border="0" alt="(Help: $topic)" /></a>
+ <a href="$link" title="$title"><img src="$helpicon" border="0" alt="(Help: $topic)" /></a>
 ENDTEMPLATE
     if ($text ne '') { $template.='</td></tr></table>' };
     return $template;
@@ -623,22 +689,184 @@ sub helpLatexCheatsheet {
 	.'</td></tr></table>';
 }
 
-=pod
+sub help_open_menu {
+    my ($color,$topic,$component_help,$function,$faq,$bug,$stayOnPage,$width,$height,$text) = @_;
+    $text = "" if (not defined $text);
+    $stayOnPage = 0 if (not defined $stayOnPage);
+    if ($env{'browser.interface'} eq 'textual' ||
+        $env{'environment.remote'} eq 'off' ) {
+        $stayOnPage=1;
+    }
+    $width = 620 if (not defined $width);
+    $height = 600 if (not defined $height);
+    my $link='';
+    my $title = &mt('Get help');
+    my $origurl = $ENV{'REQUEST_URI'};
+    $origurl=~s|^/~|/priv/|;
+    my $timestamp = time;
+    foreach (\$color,\$function,\$topic,\$component_help,\$faq,\$bug,\$origurl) {
+        $$_ = &Apache::lonnet::escape($$_);
+    }
+    if (!$stayOnPage) {
+         $link = "javascript:helpMenu('open')";
+    } else {
+        $link = "javascript:helpMenu('display')";
+    }
+    my $banner_link = "/adm/helpmenu?page=banner&color=$color&function=$function&topic=$topic&component_help=$component_help&faq=$faq&bug=$bug&origurl=$origurl&stamp=$timestamp&stayonpage=$stayOnPage";
+    my $details_link = "/adm/helpmenu?page=body&color=$color&function=$function&topic=$topic&component_help=$component_help&faq=$faq&bug=$bug&origurl=$origurl&stamp=$timestamp";
+    my $template;
+    if ($text ne "") {
+	$template .= 
+  "<table bgcolor='#CC3300' cellspacing='1' cellpadding='1' border='0'><tr>".
+  "<td bgcolor='#CC6600'><a href=\"$link\"><font color='#FFFFFF' size='2'>$text</font></a>";
+    }
+    my $nothing=&Apache::lonhtmlcommon::javascript_nothing();
+    my $html=&Apache::lonxml::xmlbegin();
+    my $helpicon=&lonhttpdurl("/adm/lonIcons/helpgateway.gif");
+    $template .= <<"ENDTEMPLATE";
+ <script type="text/javascript">
+// <!-- BEGIN LON-CAPA Internal
+// <![CDATA[
+function helpMenu(target) {
+    var caller = this;
+    if (target == 'open') {
+        var newWindow = null;
+        try {
+            newWindow =  window.open($nothing,"helpmenu","HEIGHT=$height,WIDTH=$width,resizable=yes,scrollbars=yes" )
+        }
+        catch(error) {
+            writeHelp(caller);
+            return;
+        }
+        if (newWindow) {
+            caller = newWindow;
+        }
+    }
+    writeHelp(caller);
+    return;
+}
+function writeHelp(caller) {
+    caller.document.writeln('$html<head><title>LON-CAPA Help Menu</title><meta http-equiv="pragma" content="no-cache"></head>')
+    caller.document.writeln("<frameset rows='105,*' border='0'><frame name='bannerframe'  src='$banner_link'><frame name='bodyframe' src='$details_link'></frameset>")
+    caller.document.writeln("</html>")
+    caller.document.close()
+    caller.focus()
+}
+// ]]>
+// END LON-CAPA Internal -->
+ </script>
+ <a href="$link" title="$title"><img src="$helpicon" border="0" alt="(Help Menu)" /></a>
+ENDTEMPLATE
+    if ($component_help) {
+	if (!$text) {
+	    $template=&help_open_topic($component_help,undef,$stayOnPage,
+				       $width,$height).' '.$template;
+	} else {
+	    my $help_text;
+	    $help_text=&Apache::lonnet::unescape($topic);
+	    $template='<table><tr><td>'.
+		&help_open_topic($component_help,$help_text,$stayOnPage,
+				 $width,$height).'</td><td>'.$template.
+				 '</td></tr></table>';
+	}
+    }
+    if ($text ne '') { $template.='</td></tr></table>' };
+    return $template;
+}
 
-=item * csv_translate($text) 
+sub help_open_bug {
+    my ($topic, $text, $stayOnPage, $width, $height) = @_;
+    unless ($env{'user.adv'}) { return ''; }
+    unless ($Apache::lonnet::perlvar{'BugzillaHost'}) { return ''; }
+    $text = "" if (not defined $text);
+    $stayOnPage = 0 if (not defined $stayOnPage);
+    if ($env{'browser.interface'} eq 'textual' ||
+	$env{'environment.remote'} eq 'off' ) {
+	$stayOnPage=1;
+    }
+    $width = 600 if (not defined $width);
+    $height = 600 if (not defined $height);
 
-Translate $text to allow it to be output as a 'comma seperated values' 
-format.
+    $topic=~s/\W+/\+/g;
+    my $link='';
+    my $template='';
+    my $url=$Apache::lonnet::perlvar{'BugzillaHost'}.'enter_bug.cgi?product=LON-CAPA&bug_file_loc='.
+	&Apache::lonnet::escape($ENV{'REQUEST_URI'}).'&component='.$topic;
+    if (!$stayOnPage)
+    {
+	$link = "javascript:void(open('$url', 'Bugzilla', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
+    }
+    else
+    {
+	$link = $url;
+    }
+    # Add the text
+    if ($text ne "")
+    {
+	$template .= 
+  "<table bgcolor='#AA3333' cellspacing='1' cellpadding='1' border='0'><tr>".
+  "<td bgcolor='#FF5555'><a href=\"$link\"><font color='#FFFFFF' size='2'>$text</font></a>";
+    }
 
-=cut
+    # Add the graphic
+    my $title = &mt('Report a Bug');
+    my $bugicon=&lonhttpdurl("/adm/lonMisc/smallBug.gif");
+    $template .= <<"ENDTEMPLATE";
+ <a href="$link" title="$title"><img src="$bugicon" border="0" alt="(Bug: $topic)" /></a>
+ENDTEMPLATE
+    if ($text ne '') { $template.='</td></tr></table>' };
+    return $template;
 
-sub csv_translate {
-    my $text = shift;
-    $text =~ s/\"/\"\"/g;
-    $text =~ s/\n//g;
-    return $text;
 }
 
+sub help_open_faq {
+    my ($topic, $text, $stayOnPage, $width, $height) = @_;
+    unless ($env{'user.adv'}) { return ''; }
+    unless ($Apache::lonnet::perlvar{'FAQHost'}) { return ''; }
+    $text = "" if (not defined $text);
+    $stayOnPage = 0 if (not defined $stayOnPage);
+    if ($env{'browser.interface'} eq 'textual' ||
+	$env{'environment.remote'} eq 'off' ) {
+	$stayOnPage=1;
+    }
+    $width = 350 if (not defined $width);
+    $height = 400 if (not defined $height);
+
+    $topic=~s/\W+/\+/g;
+    my $link='';
+    my $template='';
+    my $url=$Apache::lonnet::perlvar{'FAQHost'}.'/fom/cache/'.$topic.'.html';
+    if (!$stayOnPage)
+    {
+	$link = "javascript:void(open('$url', 'FAQ-O-Matic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
+    }
+    else
+    {
+	$link = $url;
+    }
+
+    # Add the text
+    if ($text ne "")
+    {
+	$template .= 
+  "<table bgcolor='#337733' cellspacing='1' cellpadding='1' border='0'><tr>".
+  "<td bgcolor='#448844'><a href=\"$link\"><font color='#FFFFFF' size='2'>$text</font></a>";
+    }
+
+    # Add the graphic
+    my $title = &mt('View the FAQ');
+    my $faqicon=&lonhttpdurl("/adm/lonMisc/smallFAQ.gif");
+    $template .= <<"ENDTEMPLATE";
+ <a href="$link" title="$title"><img src="$faqicon" border="0" alt="(FAQ: $topic)" /></a>
+ENDTEMPLATE
+    if ($text ne '') { $template.='</td></tr></table>' };
+    return $template;
+
+}
+
+###############################################################
+###############################################################
+
 =pod
 
 =item * change_content_javascript():
@@ -671,8 +899,8 @@ pretty much any HTML.
 
 sub change_content_javascript {
     # If we're on Netscape 4, we need to use Layer-based code
-    if ($ENV{'browser.type'} eq 'netscape' &&
-	$ENV{'browser.version'} =~ /^4\./) {
+    if ($env{'browser.type'} eq 'netscape' &&
+	$env{'browser.version'} =~ /^4\./) {
 	return (<<NETSCAPE4);
 	function change(name, content) {
 	    doc = document.layers[name+"___escape"].layers[0].document;
@@ -709,8 +937,8 @@ the area will originally contain, which
 sub changable_area {
     my ($name, $origContent) = @_;
 
-    if ($ENV{'browser.type'} eq 'netscape' &&
-	$ENV{'browser.version'} =~ /^4\./) {
+    if ($env{'browser.type'} eq 'netscape' &&
+	$env{'browser.version'} =~ /^4\./) {
 	# If this is netscape 4, we need to use the Layer tag
 	return "<ilayer width='100%' id='${name}___escape' overflow='none'><layer width='100%' id='$name' overflow='none'>$origContent</layer></ilayer>";
     } else {
@@ -722,6 +950,174 @@ sub changable_area {
 
 =back
 
+=head1 Excel and CSV file utility routines
+
+=over 4
+
+=cut
+
+###############################################################
+###############################################################
+
+=pod
+
+=item * csv_translate($text) 
+
+Translate $text to allow it to be output as a 'comma separated values' 
+format.
+
+=cut
+
+###############################################################
+###############################################################
+sub csv_translate {
+    my $text = shift;
+    $text =~ s/\"/\"\"/g;
+    $text =~ s/\n/ /g;
+    return $text;
+}
+
+###############################################################
+###############################################################
+
+=pod
+
+=item * define_excel_formats
+
+Define some commonly used Excel cell formats.
+
+Currently supported formats:
+
+=over 4
+
+=item header
+
+=item bold
+
+=item h1
+
+=item h2
+
+=item h3
+
+=item h4
+
+=item i
+
+=item date
+
+=back
+
+Inputs: $workbook
+
+Returns: $format, a hash reference.
+
+=cut
+
+###############################################################
+###############################################################
+sub define_excel_formats {
+    my ($workbook) = @_;
+    my $format;
+    $format->{'header'} = $workbook->add_format(bold      => 1, 
+                                                bottom    => 1,
+                                                align     => 'center');
+    $format->{'bold'} = $workbook->add_format(bold=>1);
+    $format->{'h1'}   = $workbook->add_format(bold=>1, size=>18);
+    $format->{'h2'}   = $workbook->add_format(bold=>1, size=>16);
+    $format->{'h3'}   = $workbook->add_format(bold=>1, size=>14);
+    $format->{'h4'}   = $workbook->add_format(bold=>1, size=>12);
+    $format->{'i'}    = $workbook->add_format(italic=>1);
+    $format->{'date'} = $workbook->add_format(num_format=>
+                                            'mm/dd/yyyy hh:mm:ss');
+    return $format;
+}
+
+###############################################################
+###############################################################
+
+=pod
+
+=item * create_workbook
+
+Create an Excel worksheet.  If it fails, output message on the
+request object and return undefs.
+
+Inputs: Apache request object
+
+Returns (undef) on failure, 
+    Excel worksheet object, scalar with filename, and formats 
+    from &Apache::loncommon::define_excel_formats on success
+
+=cut
+
+###############################################################
+###############################################################
+sub create_workbook {
+    my ($r) = @_;
+        #
+    # Create the excel spreadsheet
+    my $filename = '/prtspool/'.
+        $env{'user.name'}.'_'.$env{'user.domain'}.'_'.
+        time.'_'.rand(1000000000).'.xls';
+    my $workbook  = Spreadsheet::WriteExcel->new('/home/httpd'.$filename);
+    if (! defined($workbook)) {
+        $r->log_error("Error creating excel spreadsheet $filename: $!");
+        $r->print('<p>'.&mt("Unable to create new Excel file.  ".
+                            "This error has been logged.  ".
+                            "Please alert your LON-CAPA administrator").
+                  '</p>');
+        return (undef);
+    }
+    #
+    $workbook->set_tempdir('/home/httpd/perl/tmp');
+    #
+    my $format = &Apache::loncommon::define_excel_formats($workbook);
+    return ($workbook,$filename,$format);
+}
+
+###############################################################
+###############################################################
+
+=pod
+
+=item * create_text_file
+
+Create a file to write to and eventually make available to the usre.
+If file creation fails, outputs an error message on the request object and 
+return undefs.
+
+Inputs: Apache request object, and file suffix
+
+Returns (undef) on failure, 
+    Filehandle and filename on success.
+
+=cut
+
+###############################################################
+###############################################################
+sub create_text_file {
+    my ($r,$suffix) = @_;
+    if (! defined($suffix)) { $suffix = 'txt'; };
+    my $fh;
+    my $filename = '/prtspool/'.
+        $env{'user.name'}.'_'.$env{'user.domain'}.'_'.
+        time.'_'.rand(1000000000).'.'.$suffix;
+    $fh = Apache::File->new('>/home/httpd'.$filename);
+    if (! defined($fh)) {
+        $r->log_error("Couldn't open $filename for output $!");
+        $r->print("Problems occured in creating the output file.  ".
+                  "This error has been logged.  ".
+                  "Please alert your LON-CAPA administrator.");
+    }
+    return ($fh,$filename)
+}
+
+
+=pod 
+
+=back
+
 =cut
 
 ###############################################################
@@ -747,11 +1143,68 @@ sub get_domains {
     my @domains;
     my %seen;
     foreach (sort values(%Apache::lonnet::hostdom)) {
-        push (@domains,$_) unless $seen{$_}++;
+	push (@domains,$_) unless $seen{$_}++;
     }
     return @domains;
 }
 
+# ------------------------------------------
+
+sub domain_select {
+    my ($name,$value,$multiple)=@_;
+    my %domains=map { 
+	$_ => $_.' '.$Apache::lonnet::domaindescription{$_} 
+    } &get_domains;
+    if ($multiple) {
+	$domains{''}=&mt('Any domain');
+	return &multiple_select_form($name,$value,4,\%domains);
+    } else {
+	return &select_form($name,$value,%domains);
+    }
+}
+
+#-------------------------------------------
+
+=pod
+
+=item * multiple_select_form($name,$value,$size,$hash,$order)
+
+Returns a string containing a <select> element int multiple mode
+
+
+Args:
+  $name - name of the <select> element
+  $value - sclara or array ref of values that should already be selected
+  $size - number of rows long the select element is
+  $hash - the elements should be 'option' => 'shown text'
+          (shown text should already have been &mt())
+  $order - (optional) array ref of the order to show the elments in
+
+=cut
+
+#-------------------------------------------
+sub multiple_select_form {
+    my ($name,$value,$size,$hash,$order)=@_;
+    my %selected = map { $_ => 1 } ref($value)?@{$value}:($value);
+    my $output='';
+    if (! defined($size)) {
+        $size = 4;
+        if (scalar(keys(%$hash))<4) {
+            $size = scalar(keys(%$hash));
+        }
+    }
+    $output.="\n<select name='$name' size='$size' multiple='1'>";
+    my @order = ref($order) ? @$order
+                            : sort(keys(%$hash));
+    foreach my $key (@order) {
+        $output.='<option value="'.$key.'" ';
+        $output.='selected="selected" ' if ($selected{$key});
+        $output.='>'.$hash->{$key}."</option>\n";
+    }
+    $output.="</select>\n";
+    return $output;
+}
+
 #-------------------------------------------
 
 =pod
@@ -768,15 +1221,57 @@ See lonrights.pm for an example invocati
 sub select_form {
     my ($def,$name,%hash) = @_;
     my $selectform = "<select name=\"$name\" size=\"1\">\n";
-    foreach (sort keys %hash) {
+    my @keys;
+    if (exists($hash{'select_form_order'})) {
+	@keys=@{$hash{'select_form_order'}};
+    } else {
+	@keys=sort(keys(%hash));
+    }
+    foreach (@keys) {
         $selectform.="<option value=\"$_\" ".
-            ($_ eq $def ? 'selected' : '').
+            ($_ eq $def ? 'selected="selected" ' : '').
                 ">".&mt($hash{$_})."</option>\n";
     }
     $selectform.="</select>";
     return $selectform;
 }
 
+sub gradeleveldescription {
+    my $gradelevel=shift;
+    my %gradelevels=(0 => 'Not specified',
+		     1 => 'Grade 1',
+		     2 => 'Grade 2',
+		     3 => 'Grade 3',
+		     4 => 'Grade 4',
+		     5 => 'Grade 5',
+		     6 => 'Grade 6',
+		     7 => 'Grade 7',
+		     8 => 'Grade 8',
+		     9 => 'Grade 9',
+		     10 => 'Grade 10',
+		     11 => 'Grade 11',
+		     12 => 'Grade 12',
+		     13 => 'Grade 13',
+		     14 => '100 Level',
+		     15 => '200 Level',
+		     16 => '300 Level',
+		     17 => '400 Level',
+		     18 => 'Graduate Level');
+    return &mt($gradelevels{$gradelevel});
+}
+
+sub select_level_form {
+    my ($deflevel,$name)=@_;
+    unless ($deflevel) { $deflevel=0; }
+    my $selectform = "<select name=\"$name\" size=\"1\">\n";
+    for (my $i=0; $i<=18; $i++) {
+        $selectform.="<option value=\"$i\" ".
+            ($i==$deflevel ? 'selected="selected" ' : '').
+                ">".&gradeleveldescription($i)."</option>\n";
+    }
+    $selectform.="</select>";
+    return $selectform;
+}
 
 #-------------------------------------------
 
@@ -801,7 +1296,7 @@ sub select_dom_form {
     my $selectdomain = "<select name=\"$name\" size=\"1\">\n";
     foreach (@domains) {
         $selectdomain.="<option value=\"$_\" ".
-            ($_ eq $defdom ? 'selected' : '').
+            ($_ eq $defdom ? 'selected="selected" ' : '').
                 ">$_</option>\n";
     }
     $selectdomain.="</select>";
@@ -893,14 +1388,18 @@ Outputs:
 
 =back
 
+=back 
+
 =cut
 
 ###############################################################
 ###############################################################
 sub decode_user_agent {
+    my ($r)=@_;
     my @browsertype=split(/\&/,$Apache::lonnet::perlvar{"lonBrowsDet"});
     my %mathcap=split(/\&/,$$Apache::lonnet::perlvar{"lonMathML"});
     my $httpbrowser=$ENV{"HTTP_USER_AGENT"};
+    if (!$httpbrowser && $r) { $httpbrowser=$r->header_in('User-Agent'); }
     my $clientbrowser='unknown';
     my $clientversion='0';
     my $clientmathml='';
@@ -931,12 +1430,6 @@ sub decode_user_agent {
             $clientunicode,$clientos,);
 }
 
-=pod
-
-=back
-
-=cut
-
 ###############################################################
 ##    Authentication changing form generation subroutines    ##
 ###############################################################
@@ -977,6 +1470,8 @@ See loncreateuser.pm for invocation and
 
 =back
 
+=back 
+
 =cut
 
 #-------------------------------------------
@@ -1004,10 +1499,30 @@ END
         $Javascript_toUpperCase = "";
     }
 
+    my $radioval = "'nochange'";
+    if (exists($in{'curr_authtype'}) &&
+        defined($in{'curr_authtype'}) &&
+        $in{'curr_authtype'} ne '') {
+        $radioval = "'$in{'curr_authtype'}arg'";
+    }
+    my $argfield = 'null';
+    if ( grep/^mode$/,(keys %in) ) {
+        if ($in{'mode'} eq 'modifycourse')  {
+            if ( grep/^curr_authtype$/,(keys %in) ) {
+                $radioval = "'$in{'curr_authtype'}'";
+            }
+            if ( grep/^curr_autharg$/,(keys %in) ) {
+                unless ($in{'curr_autharg'} eq '') {
+                    $argfield = "'$in{'curr_autharg'}'";
+                }
+            }
+        }
+    }
+
     $result.=<<"END";
 var current = new Object();
-current.radiovalue = 'nochange';
-current.argfield = null;
+current.radiovalue = $radioval;
+current.argfield = $argfield;
 
 function changed_radio(choice,currentform) {
     var choicearg = choice + 'arg';
@@ -1067,10 +1582,10 @@ END
 
 sub authform_authorwarning{
     my $result='';
-    $result=<<"END";
-<i>As a general rule, only authors or co-authors should be filesystem
-authenticated (which allows access to the server filesystem).</i>
-END
+    $result='<i>'.
+        &mt('As a general rule, only authors or co-authors should be '.
+            'filesystem authenticated '.
+            '(which allows access to the server filesystem).')."</i>\n";
     return $result;
 }
 
@@ -1080,12 +1595,11 @@ sub authform_nochange{
               kerb_def_dom => 'MSU.EDU',
               @_,
           );
-    my $result='';
-    $result.=<<"END";
-<input type="radio" name="login" value="nochange" checked="checked"
-       onclick="javascript:changed_radio('nochange',$in{'formname'});" />
-Do not change login data
-END
+    my $result = '<label>'.&mt('[_1] Do not change login data',
+                     '<input type="radio" name="login" value="nochange" '.
+                     'checked="checked" onclick="'.
+            "javascript:changed_radio('nochange',$in{'formname'});".'" />').
+	    '</label>';
     return $result;
 }
 
@@ -1096,24 +1610,36 @@ sub authform_kerberos{
               kerb_def_auth => 'krb4',
               @_,
               );
-    my $result='';
-    my $check4;
-    my $check5;
+    my ($check4,$check5,$krbarg);
     if ($in{'kerb_def_auth'} eq 'krb5') {
        $check5 = " checked=\"on\"";
     } else {
        $check4 = " checked=\"on\"";
     }
-    $result.=<<"END";
-<input type="radio" name="login" value="krb" 
-       onclick="javascript:changed_radio('krb',$in{'formname'});"
-       onchange="javascript:changed_radio('krb',$in{'formname'});" />
-Kerberos authenticated with domain
-<input type="text" size="10" name="krbarg" value="$in{'kerb_def_dom'}"
-       onchange="javascript:changed_text('krb',$in{'formname'});" />
-<input type="radio" name="krbver" value="4" $check4 />Version 4
-<input type="radio" name="krbver" value="5" $check5 />Version 5
-END
+    $krbarg = $in{'kerb_def_dom'};
+
+    my $krbcheck = "";
+    if ( grep/^curr_authtype$/,(keys %in) ) {
+        if ($in{'curr_authtype'} =~ m/^krb/) {
+            $krbcheck = " checked=\"on\"";
+            if ( grep/^curr_autharg$/,(keys %in) ) {
+                $krbarg = $in{'curr_autharg'};
+            }
+        }
+    }
+
+    my $jscall = "javascript:changed_radio('krb',$in{'formname'});";
+    my $result .= &mt
+        ('[_1] Kerberos authenticated with domain [_2] '.
+         '[_3] Version 4 [_4] Version 5 [_5]',
+         '<label><input type="radio" name="login" value="krb" '.
+             'onclick="'.$jscall.'" onchange="'.$jscall.'"'.$krbcheck.' />',
+         '</label><input type="text" size="10" name="krbarg" '.
+             'value="'.$krbarg.'" '.
+             'onchange="'.$jscall.'" />',
+         '<label><input type="radio" name="krbver" value="4" '.$check4.' />',
+         '</label><label><input type="radio" name="krbver" value="5" '.$check5.' />',
+	 '</label>');
     return $result;
 }
 
@@ -1123,15 +1649,25 @@ sub authform_internal{
                 kerb_def_dom => 'MSU.EDU',
                 @_,
                 );
-    my $result='';
-    $result.=<<"END";
-<input type="radio" name="login" value="int"
-       onchange="javascript:changed_radio('int',$args{'formname'});"
-       onclick="javascript:changed_radio('int',$args{'formname'});" />
-Internally authenticated (with initial password 
-<input type="text" size="10" name="intarg" value=""
-       onchange="javascript:changed_text('int',$args{'formname'});" />)
-END
+
+    my $intcheck = "";
+    my $intarg = 'value=""';
+    if ( grep/^curr_authtype$/,(keys %args) ) {
+        if ($args{'curr_authtype'} eq 'int') {
+            $intcheck = " checked=\"on\"";
+            if ( grep/^curr_autharg$/,(keys %args) ) {
+                $intarg = "value=\"$args{'curr_autharg'}\"";
+            }
+        }
+    }
+
+    my $jscall = "javascript:changed_radio('int',$args{'formname'});";
+    my $result.=&mt
+        ('[_1] Internally authenticated (with initial password [_2])',
+         '<label><input type="radio" name="login" value="int" '.$intcheck.
+             ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+         '</label><input type="text" size="10" name="intarg" '.$intarg.
+             ' onchange="'.$jscall.'" />');
     return $result;
 }
 
@@ -1141,15 +1677,24 @@ sub authform_local{
               kerb_def_dom => 'MSU.EDU',
               @_,
               );
-    my $result='';
-    $result.=<<"END";
-<input type="radio" name="login" value="loc"
-       onchange="javascript:changed_radio('loc',$in{'formname'});"
-       onclick="javascript:changed_radio('loc',$in{'formname'});" />
-Local Authentication with argument
-<input type="text" size="10" name="locarg" value=""
-       onchange="javascript:changed_text('loc',$in{'formname'});" />
-END
+
+    my $loccheck = "";
+    my $locarg = 'value=""';
+    if ( grep/^curr_authtype$/,(keys %in) ) {
+        if ($in{'curr_authtype'} eq 'loc') {
+            $loccheck = " checked=\"on\"";
+            if ( grep/^curr_autharg$/,(keys %in) ) {
+                $locarg = "value=\"$in{'curr_autharg'}\"";
+            }
+        }
+    }
+
+    my $jscall = "javascript:changed_radio('loc',$in{'formname'});";
+    my $result.=&mt('[_1] Local Authentication with argument [_2]',
+                    '<label><input type="radio" name="login" value="loc" '.$loccheck.
+                        ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+                    '</label><input type="text" size="10" name="locarg" '.$locarg.
+                        ' onchange="'.$jscall.'" />');
     return $result;
 }
 
@@ -1159,24 +1704,16 @@ sub authform_filesystem{
               kerb_def_dom => 'MSU.EDU',
               @_,
               );
-    my $result='';
-    $result.=<<"END";
-<input type="radio" name="login" value="fsys" 
-       onchange="javascript:changed_radio('fsys',$in{'formname'});"
-       onclick="javascript:changed_radio('fsys',$in{'formname'});" />
-Filesystem authenticated (with initial password 
-<input type="text" size="10" name="fsysarg" value=""
-       onchange="javascript:changed_text('fsys',$in{'formname'});">)
-END
+    my $jscall = "javascript:changed_radio('fsys',$in{'formname'});";
+    my $result.= &mt
+        ('[_1] Filesystem Authenticated (with initial password [_2])',
+         '<label><input type="radio" name="login" value="fsys" '.
+         'onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+         '</label><input type="text" size="10" name="fsysarg" value="" '.
+                  'onchange="'.$jscall.'" />');
     return $result;
 }
 
-=pod
-
-=back
-
-=cut
-
 ###############################################################
 ##    Get Authentication Defaults for Domain                 ##
 ###############################################################
@@ -1331,7 +1868,7 @@ sub keyword {
 
 =item * get_related_words
 
-Look up a word in the thesaurus.  Takes a scalar arguement and returns
+Look up a word in the thesaurus.  Takes a scalar argument and returns
 an array of words.  If the keyword is not in the thesaurus, an empty array
 will be returned.  The order of the words returned is determined by the
 database which holds them.
@@ -1379,23 +1916,28 @@ sub get_related_words {
 
 =over 4
 
-=item * plainname($uname,$udom)
+=item * plainname($uname,$udom,$first)
 
 Takes a users logon name and returns it as a string in
-"first middle last generation" form
+"first middle last generation" form 
+if $first is set to 'lastname' then it returns it as
+'lastname generation, firstname middlename' if their is a lastname
 
 =cut
 
+
 ###############################################################
 sub plainname {
-    my ($uname,$udom)=@_;
-    my %names=&Apache::lonnet::get('environment',
-                    ['firstname','middlename','lastname','generation'],
-					 $udom,$uname);
-    my $name=$names{'firstname'}.' '.$names{'middlename'}.' '.
-	$names{'lastname'}.' '.$names{'generation'};
+    my ($uname,$udom,$first)=@_;
+    my %names=&getnames($uname,$udom);
+    my $name=&Apache::lonnet::format_name($names{'firstname'},
+					  $names{'middlename'},
+					  $names{'lastname'},
+					  $names{'generation'},$first);
+    $name=~s/^\s+//;
     $name=~s/\s+$//;
     $name=~s/\s+/ /g;
+    if ($name !~ /\S/) { $name=$uname.'@'.$udom; }
     return $name;
 }
 
@@ -1418,8 +1960,7 @@ if the user does not
 
 sub nickname {
     my ($uname,$udom)=@_;
-    my %names=&Apache::lonnet::get('environment',
-  ['nickname','firstname','middlename','lastname','generation'],$udom,$uname);
+    my %names=&getnames($uname,$udom);
     my $name=$names{'nickname'};
     if ($name) {
        $name='&quot;'.$name.'&quot;'; 
@@ -1432,6 +1973,20 @@ sub nickname {
     return $name;
 }
 
+sub getnames {
+    my ($uname,$udom)=@_;
+    my $id=$uname.':'.$udom;
+    my ($names,$cached)=&Apache::lonnet::is_cached_new('namescache',$id);
+    if ($cached) {
+	return %{$names};
+    } else {
+	my %loadnames=&Apache::lonnet::get('environment',
+                    ['firstname','middlename','lastname','generation','nickname'],
+					 $udom,$uname);
+	&Apache::lonnet::do_cache_new('namescache',$id,\%loadnames);
+	return %loadnames;
+    }
+}
 
 # ------------------------------------------------------------------ Screenname
 
@@ -1445,17 +2000,21 @@ Gets a users screenname and returns it a
 
 sub screenname {
     my ($uname,$udom)=@_;
-    my %names=
- &Apache::lonnet::get('environment',['screenname'],$udom,$uname);
+    if ($uname eq $env{'user.name'} &&
+	$udom eq $env{'user.domain'}) {return $env{'environment.screenname'};}
+    my %names=&Apache::lonnet::get('environment',['screenname'],$udom,$uname);
     return $names{'screenname'};
 }
 
+
 # ------------------------------------------------------------- Message Wrapper
 
 sub messagewrapper {
-    my ($link,$un,$do)=@_;
+    my ($link,$username,$domain)=@_;
     return 
-"<a href='/adm/email?compose=individual&recname=$un&recdom=$do'>$link</a>";
+        '<a href="/adm/email?compose=individual&'.
+        'recname='.$username.'&recdom='.$domain.'" '.
+        'title="'.&mt('Send message').'">'.$link.'</a>';
 }
 # --------------------------------------------------------------- Notes Wrapper
 
@@ -1467,8 +2026,9 @@ sub noteswrapper {
 # ------------------------------------------------------------- Aboutme Wrapper
 
 sub aboutmewrapper {
-    my ($link,$username,$domain)=@_;
-    return "<a href='/adm/$domain/$username/aboutme'>$link</a>";
+    my ($link,$username,$domain,$target)=@_;
+    return '<a href="/adm/'.$domain.'/'.$username.'/aboutme"'.
+	($target?' target="$target"':'').' title="'.&mt('View this users personal page').'">'.$link.'</a>';
 }
 
 # ------------------------------------------------------------ Syllabus Wrapper
@@ -1479,7 +2039,25 @@ sub syllabuswrapper {
     if ($fontcolor) { 
         $linktext='<font color="'.$fontcolor.'">'.$linktext.'</font>'; 
     }
-    return "<a href='/public/$domain/$coursedir/syllabus'>$linktext</a>";
+    return qq{<a href="/public/$domain/$coursedir/syllabus">$linktext</a>};
+}
+
+sub track_student_link {
+    my ($linktext,$sname,$sdom,$target,$start) = @_;
+    my $link ="/adm/trackstudent?";
+    my $title = 'View recent activity';
+    if (defined($sname) && $sname !~ /^\s*$/ &&
+        defined($sdom)  && $sdom  !~ /^\s*$/) {
+        $link .= "selected_student=$sname:$sdom";
+        $title .= ' of this student';
+    } 
+    if (defined($target) && $target !~ /^\s*$/) {
+        $target = qq{target="$target"};
+    } else {
+        $target = '';
+    }
+    if ($start) { $link.='&amp;start='.$start; }
+    return qq{<a href="$link" title="$title" $target>$linktext</a>};
 }
 
 =pod
@@ -1515,6 +2093,16 @@ sub languagedescription {
 	    ($supported_language{$code}?' ('.&mt('interface available').')':'');
 }
 
+sub plainlanguagedescription {
+    my $code=shift;
+    return $language{$code};
+}
+
+sub supportedlanguagecode {
+    my $code=shift;
+    return $supported_language{$code};
+}
+
 =pod
 
 =item * copyrightids() 
@@ -1536,7 +2124,31 @@ returns description of a specified copyr
 =cut
 
 sub copyrightdescription {
-    return $cprtag{shift(@_)};
+    return &mt($cprtag{shift(@_)});
+}
+
+=pod
+
+=item * source_copyrightids() 
+
+returns list of all source copyrights
+
+=cut
+
+sub source_copyrightids {
+    return sort(keys(%scprtag));
+}
+
+=pod
+
+=item * source_copyrightdescription() 
+
+returns description of a specified source copyright id
+
+=cut
+
+sub source_copyrightdescription {
+    return &mt($scprtag{shift(@_)});
 }
 
 =pod
@@ -1576,6 +2188,14 @@ sub fileembstyle {
     return $fe{lc(shift(@_))};
 }
 
+
+sub filecategoryselect {
+    my ($name,$value)=@_;
+    return &select_form($value,$name,
+			'' => &mt('Any category'),
+			map { $_,$_ } sort(keys(%category_extensions)));
+}
+
 =pod
 
 =item * filedescription() 
@@ -1585,7 +2205,9 @@ returns description for a specified file
 =cut
 
 sub filedescription {
-    return $fd{lc(shift(@_))};
+    my $file_description = $fd{lc(shift())};
+    $file_description =~ s:([\[\]]):~$1:g;
+    return &mt($file_description);
 }
 
 =pod
@@ -1599,7 +2221,9 @@ extra formatting
 
 sub filedescriptionex {
     my $ex=shift;
-    return '.'.$ex.' '.$fd{lc($ex)};
+    my $file_description = $fd{lc($ex)};
+    $file_description =~ s:([\[\]]):~$1:g;
+    return '.'.$ex.' '.&mt($file_description);
 }
 
 # End of .tab access
@@ -1624,8 +2248,8 @@ sub display_languages {
 	$languages{$_}=1;
     }
     &get_unprocessed_cgi($ENV{'QUERY_STRING'},['displaylanguage']);
-    if ($ENV{'form.displaylanguage'}) {
-	foreach (split(/\s*(\,|\;|\:)\s*/,$ENV{'form.displaylanguage'})) {
+    if ($env{'form.displaylanguage'}) {
+	foreach (split(/\s*(\,|\;|\:)\s*/,$env{'form.displaylanguage'})) {
 	    $languages{$_}=1;
         }
     }
@@ -1634,24 +2258,24 @@ sub display_languages {
 
 sub preferred_languages {
     my @languages=();
-    if ($ENV{'environment.languages'}) {
-	@languages=split(/\s*(\,|\;|\:)\s*/,$ENV{'environment.languages'});
-    }
-    if ($ENV{'course.'.$ENV{'request.course.id'}.'.languages'}) {
+    if ($env{'course.'.$env{'request.course.id'}.'.languages'}) {
 	@languages=(@languages,split(/\s*(\,|\;|\:)\s*/,
-	         $ENV{'course.'.$ENV{'request.course.id'}.'.languages'}));
+	         $env{'course.'.$env{'request.course.id'}.'.languages'}));
+    }
+    if ($env{'environment.languages'}) {
+	@languages=split(/\s*(\,|\;|\:)\s*/,$env{'environment.languages'});
     }
     my $browser=(split(/\;/,$ENV{'HTTP_ACCEPT_LANGUAGE'}))[0];
     if ($browser) {
 	@languages=(@languages,split(/\s*(\,|\;|\:)\s*/,$browser));
     }
-    if ($Apache::lonnet::domain_lang_def{$ENV{'user.domain'}}) {
+    if ($Apache::lonnet::domain_lang_def{$env{'user.domain'}}) {
 	@languages=(@languages,
-		$Apache::lonnet::domain_lang_def{$ENV{'user.domain'}});
+		$Apache::lonnet::domain_lang_def{$env{'user.domain'}});
     }
-    if ($Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}}) {
+    if ($Apache::lonnet::domain_lang_def{$env{'request.role.domain'}}) {
 	@languages=(@languages,
-		$Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}});
+		$Apache::lonnet::domain_lang_def{$env{'request.role.domain'}});
     }
     if ($Apache::lonnet::domain_lang_def{
 	                          $Apache::lonnet::perlvar{'lonDefDomain'}}) {
@@ -1751,7 +2375,7 @@ sub get_previous_attempt {
 	       } else {
 		  $value=$returnhash{$version.':'.$_};
 	       }
-	       $prevattempts.='<td>'.$value.'&nbsp;</td>';   
+	       $prevattempts.='<td>'.&Apache::lonnet::unescape($value).'&nbsp;</td>';   
 	    }
 	 }
       }
@@ -1763,6 +2387,7 @@ sub get_previous_attempt {
 	} else {
 	  $value=$lasthash{$_};
 	}
+	$value=&Apache::lonnet::unescape($value);
 	if ($_ =~/$regexp$/ && (defined &$gradesub)) {$value = &$gradesub($value)}
 	$prevattempts.='<td>'.$value.'&nbsp;</td>';
       }
@@ -1819,22 +2444,19 @@ show a snapshot of what student was look
 =cut
 
 sub get_student_view {
-  my ($symb,$username,$domain,$courseid,$target) = @_;
+  my ($symb,$username,$domain,$courseid,$target,$moreenv) = @_;
   my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb);
-  my (%old,%moreenv);
+  my (%form);
   my @elements=('symb','courseid','domain','username');
   foreach my $element (@elements) {
-    $old{$element}=$ENV{'form.grade_'.$element};
-    $moreenv{'form.grade_'.$element}=eval '$'.$element #'
+      $form{'grade_'.$element}=eval '$'.$element #'
   }
-  if ($target eq 'tex') {$moreenv{'form.grade_target'} = 'tex';}
-  &Apache::lonnet::appenv(%moreenv);
-  $feedurl=&Apache::lonnet::clutter($feedurl);
-  my $userview=&Apache::lonnet::ssi_body($feedurl);
-  &Apache::lonnet::delenv('form.grade_');
-  foreach my $element (@elements) {
-    $ENV{'form.grade_'.$element}=$old{$element};
+  if (defined($moreenv)) {
+      %form=(%form,%{$moreenv});
   }
+  if (defined($target)) { $form{'grade_target'} = $target; }
+  $feedurl=&Apache::lonnet::clutter($feedurl);
+  my $userview=&Apache::lonnet::ssi_body($feedurl,%form);
   $userview=~s/\<body[^\>]*\>//gi;
   $userview=~s/\<\/body\>//gi;
   $userview=~s/\<html\>//gi;
@@ -1857,19 +2479,14 @@ show a snapshot of how student was answe
 sub get_student_answers {
   my ($symb,$username,$domain,$courseid,%form) = @_;
   my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb);
-  my (%old,%moreenv);
+  my (%moreenv);
   my @elements=('symb','courseid','domain','username');
   foreach my $element (@elements) {
-    $old{$element}=$ENV{'form.grade_'.$element};
-    $moreenv{'form.grade_'.$element}=eval '$'.$element #'
-  }
-  $moreenv{'form.grade_target'}='answer';
-  &Apache::lonnet::appenv(%moreenv);
-  my $userview=&Apache::lonnet::ssi('/res/'.$feedurl,%form);
-  &Apache::lonnet::delenv('form.grade_');
-  foreach my $element (@elements) {
-    $ENV{'form.grade_'.$element}=$old{$element};
+    $moreenv{'grade_'.$element}=eval '$'.$element #'
   }
+  $moreenv{'grade_target'}='answer';
+  %moreenv=(%form,%moreenv);
+  my $userview=&Apache::lonnet::ssi('/res/'.$feedurl,%moreenv);
   return $userview;
 }
 
@@ -1877,7 +2494,7 @@ sub get_student_answers {
 
 =item * &submlink()
 
-Inputs: $text $uname $udom $symb
+Inputs: $text $uname $udom $symb $target
 
 Returns: A link to grades.pm such as to see the SUBM view of a student
 
@@ -1885,15 +2502,64 @@ Returns: A link to grades.pm such as to
 
 ###############################################
 sub submlink {
-    my ($text,$uname,$udom,$symb)=@_;
+    my ($text,$uname,$udom,$symb,$target)=@_;
+    if (!($uname && $udom)) {
+	(my $cursymb, my $courseid,$udom,$uname)=
+	    &Apache::lonxml::whichuser($symb);
+	if (!$symb) { $symb=$cursymb; }
+    }
+    if (!$symb) { $symb=&Apache::lonnet::symbread(); }
+    $symb=&Apache::lonnet::escape($symb);
+    if ($target) { $target="target=\"$target\""; }
+    return '<a href="/adm/grades?&command=submission&'.
+	'symb='.$symb.'&student='.$uname.
+	'&userdom='.$udom.'" '.$target.'>'.$text.'</a>';
+}
+##############################################
+
+=pod
+
+=item * &pgrdlink()
+
+Inputs: $text $uname $udom $symb $target
+
+Returns: A link to grades.pm such as to see the PGRD view of a student
+
+=cut
+
+###############################################
+sub pgrdlink {
+    my $link=&submlink(@_);
+    $link=~s/(&command=submission)/$1&showgrading=yes/;
+    return $link;
+}
+##############################################
+
+=pod
+
+=item * &pprmlink()
+
+Inputs: $text $uname $udom $symb $target
+
+Returns: A link to parmset.pm such as to see the PPRM view of a
+student and a specific resource
+
+=cut
+
+###############################################
+sub pprmlink {
+    my ($text,$uname,$udom,$symb,$target)=@_;
     if (!($uname && $udom)) {
 	(my $cursymb, my $courseid,$udom,$uname)=
 	    &Apache::lonxml::whichuser($symb);
 	if (!$symb) { $symb=$cursymb; }
     }
-    if (!$symb) { $symb=&symbread(); }
-    return '<a href="/adm/grades?symb='.$symb.'&student='.$uname.
-	'&userdom='.$udom.'&command=submission">'.$text.'</a>';
+    if (!$symb) { $symb=&Apache::lonnet::symbread(); }
+    $symb=&Apache::lonnet::escape($symb);
+    if ($target) { $target="target=\"$target\""; }
+    return '<a href="/adm/parmset?&command=set&'.
+	'symb='.$symb.'&uname='.$uname.
+	'&udom='.$udom.'" '.$target.'>'.$text.'</a>';
 }
 ##############################################
 
@@ -1923,22 +2589,7 @@ sub maketime {
     my %th=@_;
     return POSIX::mktime(
         ($th{'seconds'},$th{'minutes'},$th{'hours'},
-         $th{'day'},$th{'month'}-1,$th{'year'}-1900,0,0,$th{'dlsav'}));
-}
-
-
-#########################################
-#
-# Retro-fixing of un-backward-compatible time format
-
-sub unsqltime {
-    my $timestamp=shift;
-    if ($timestamp=~/^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/) {
-       $timestamp=&maketime(
-	   'year'=>$1,'month'=>$2,'day'=>$3,
-           'hours'=>$4,'minutes'=>$5,'seconds'=>$6);
-    }
-    return $timestamp;
+         $th{'day'},$th{'month'}-1,$th{'year'}-1900,0,0,-1));
 }
 
 #########################################
@@ -1946,9 +2597,9 @@ sub unsqltime {
 sub findallcourses {
     my %courses=();
     my $now=time;
-    foreach (keys %ENV) {
+    foreach (keys %env) {
 	if ($_=~/^user\.role\.\w+\.\/(\w+)\/(\w+)/) {
-	    my ($starttime,$endtime)=$ENV{$_};
+	    my ($starttime,$endtime)=$env{$_};
             my $active=1;
             if ($starttime) {
 		if ($now<$starttime) { $active=0; }
@@ -1985,9 +2636,9 @@ sub determinedomain {
    if (! $domain) {
         # Determine domain if we have not been given one
         $domain = $Apache::lonnet::perlvar{'lonDefDomain'};
-        if ($ENV{'user.domain'}) { $domain=$ENV{'user.domain'}; }
-        if ($ENV{'request.role.domain'}) { 
-            $domain=$ENV{'request.role.domain'}; 
+        if ($env{'user.domain'}) { $domain=$env{'user.domain'}; }
+        if ($env{'request.role.domain'}) { 
+            $domain=$env{'request.role.domain'}; 
         }
     }
     return $domain;
@@ -2009,10 +2660,8 @@ sub domainlogo {
     my $domain = &determinedomain(shift);    
      # See if there is a logo
     if (-e '/home/httpd/html/adm/lonDomLogos/'.$domain.'.gif') {
-	my $lonhttpdPort=$Apache::lonnet::perlvar{'lonhttpdPort'};
-	if (!defined($lonhttpdPort)) { $lonhttpdPort='8080'; }
-        return '<img src="http://'.$ENV{'HTTP_HOST'}.':'.$lonhttpdPort.
-	    '/adm/lonDomLogos/'.$domain.'.gif" />';
+	my $logo=&lonhttpdurl("/adm/lonDomLogos/$domain.gif");
+        return '<img src="'.$logo.'" alt="'.$domain.'" />';
     } elsif(exists($Apache::lonnet::domaindescription{$domain})) {
         return $Apache::lonnet::domaindescription{$domain};
     } else {
@@ -2034,7 +2683,7 @@ Returns: value of designparamter $which
 ##############################################
 sub designparm {
     my ($which,$domain)=@_;
-    if ($ENV{'browser.blackwhite'} eq 'on') {
+    if ($env{'browser.blackwhite'} eq 'on') {
 	if ($which=~/\.(font|alink|vlink|link)$/) {
 	    return '#000000';
 	}
@@ -2045,8 +2694,8 @@ sub designparm {
 	    return '#CCCCCC';
 	}
     }
-    if ($ENV{'environment.color.'.$which}) {
-	return $ENV{'environment.color.'.$which};
+    if ($env{'environment.color.'.$which}) {
+	return $env{'environment.color.'.$which};
     }
     $domain=&determinedomain($domain);
     if ($designhash{$domain.'.'.$which}) {
@@ -2088,6 +2737,10 @@ Inputs:
 =item * $forcereg, if page should register as content page (relevant for 
             text interface only)
 
+=item * $customtitle, overrides the $title in some way ????
+
+=item * $notopbar, if true, keep the 'what is this' info but remove the
+                   navigational links
 =back
 
 Returns: A uniform header for LON-CAPA web pages.  
@@ -2098,21 +2751,10 @@ other decorations will be returned.
 =cut
 
 sub bodytag {
-    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg)=@_;
+    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,$customtitle,
+	$notopbar)=@_;
     $title=&mt($title);
-    unless ($function) {
-	$function='student';
-        if ($ENV{'request.role'}=~/^(cc|in|ta|ep)/) {
-	    $function='coordinator';
-        }
-	if ($ENV{'request.role'}=~/^(su|dc|ad|li)/) {
-            $function='admin';
-        }
-        if (($ENV{'request.role'}=~/^(au|ca)/) ||
-            ($ENV{'REQUEST_URI'}=~/^(\/priv|\~)/)) {
-            $function='author';
-        }
-    }
+    $function = &get_users_function() if (!$function);
     my $img=&designparm($function.'.img',$domain);
     my $pgbg=&designparm($function.'.pgbg',$domain);
     my $tabbg=&designparm($function.'.tabbg',$domain);
@@ -2123,16 +2765,17 @@ sub bodytag {
     my $sidebg=&designparm($function.'.sidebg',$domain);
 # Accessibility font enhance
     unless ($addentries) { $addentries=''; }
-    if ($ENV{'browser.fontenhance'} eq 'on') {
-	$addentries.=' style="font-size: x-large"';
+    my $addstyle='';
+    if ($env{'browser.fontenhance'} eq 'on') {
+	$addstyle=' font-size: x-large;';
     }
  # role and realm
     my ($role,$realm)
-       =&Apache::lonnet::plaintext((split(/\./,$ENV{'request.role'}))[0]);
+       =&Apache::lonnet::plaintext((split(/\./,$env{'request.role'}))[0]);
 # realm
-    if ($ENV{'request.course.id'}) {
+    if ($env{'request.course.id'}) {
 	$realm=
-         $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+         $env{'course.'.$env{'request.course.id'}.'.description'};
     }
     unless ($realm) { $realm='&nbsp;'; }
 # Set messages
@@ -2142,29 +2785,122 @@ sub bodytag {
     if (!defined($lonhttpdPort)) { $lonhttpdPort='8080'; }
 # construct main body tag
     my $bodytag = <<END;
+<style type="text/css">
+h1, h2, h3, th { font-family: Arial, Helvetica, sans-serif }
+a:focus { color: red; background: yellow } 
+table.thinborder { border-collapse: collapse; }
+table.thinborder tr th, table.thinborder tr td { border-style: solid; border-width: 1px}
+form, .inline { display: inline; }
+.center { text-align: center; }
+.filename {font-family: monospace;}
+</style>
 <body bgcolor="$pgbg" text="$font" alink="$alink" vlink="$vlink" link="$link"
-$addentries>
+style="margin-top: 0px;$addstyle" $addentries>
 END
+    &Apache::lontexconvert::jsMath_reset();
+    if ($env{'environment.texengine'} eq 'jsMath') {
+	$bodytag.=&Apache::lontexconvert::jsMath_header();
+    }
+
     my $upperleft='<img src="http://'.$ENV{'HTTP_HOST'}.':'.
-                   $lonhttpdPort.$img.'" />';
+                   $lonhttpdPort.$img.'" alt="'.$function.'" />';
     if ($bodyonly) {
         return $bodytag;
-    } elsif ($ENV{'browser.interface'} eq 'textual') {
+    } elsif ($env{'browser.interface'} eq 'textual') {
 # Accessibility
+          
         return $bodytag.&Apache::lonmenu::menubuttons($forcereg,'web',
                                                       $forcereg).
                '<h1>LON-CAPA: '.$title.'</h1>';
-    } elsif ($ENV{'environment.remote'} eq 'off') {
+    } elsif ($env{'environment.remote'} eq 'off') {
 # No Remote
-        return $bodytag.&Apache::lonmenu::menubuttons($forcereg,'web',
-                                                      $forcereg).
-               '<table bgcolor="'.$pgbg.'" width="100%" border="0" cellspacing="3" cellpadding="3"><tr><td bgcolor="'.$tabbg.'"><font size="+3" color="'.$font.'"><b>'.$title.
-'</b></font></td></tr></table>';
+	my $roleinfo=(<<ENDROLE);
+<td bgcolor="$tabbg" align="right">
+<font size="2" face="Arial, Helvetica, sans-serif">
+    $env{'environment.firstname'}
+    $env{'environment.middlename'}
+    $env{'environment.lastname'}
+    $env{'environment.generation'}
+    </font>&nbsp;
+<br />
+<font size="2" face="Arial, Helvetica, sans-serif">$role</font>&nbsp;
+<br />
+<font size="2" face="Arial, Helvetica, sans-serif">$realm</font>&nbsp;
+</td>
+ENDROLE
+        my $titleinfo = '<font face="Arial, Helvetica, sans-serif" size="+3" color="'.
+		$font.'"><b>'.$title.'</b></font>';
+        if ($customtitle) {
+            $titleinfo = $customtitle;
+        }
+
+	if ($env{'request.state'} eq 'construct') {
+	    my ($uname,$thisdisfn)=
+		($env{'request.filename'} =~ m|^/home/([^/]+)/public_html/(.*)|);
+	    my $formaction='/priv/'.$uname.'/'.$thisdisfn;
+	    $formaction=~s/\/+/\//g;
+            unless ($customtitle) {  #this is for resources; directories have customtitle, and crumbs and select recent are created in lonpubdir.pm  
+                my $parentpath = '';
+                my $lastitem = '';
+                if ($thisdisfn =~ m-(.+/)([^/]*)$-) {
+                    $parentpath = $1;
+                    $lastitem = $2;
+                } else {
+                    $lastitem = $thisdisfn;
+                }
+	        $titleinfo = &Apache::loncommon::help_open_menu('','','','',3,'Authoring').
+                      '<font face="Arial, Helvetica, sans-serif"><b>Construction Space</b>:</font>&nbsp;'. 
+                      '<form name="dirs" method="post" action="'.$formaction
+		    .'" target="_top"><tt><b>'
+		    .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,'_top','/priv','','+1',1)."<font size=\"+1\">$lastitem</font></b></tt><br />"
+		    .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()')
+		    .'</form>'
+		    .&Apache::lonmenu::constspaceform();
+
+            }
+	    $forcereg=1;
+        }
+        my $titletable = '<table bgcolor="'.$pgbg.'" width="100%" border="0" '.
+                         'cellspacing="3" cellpadding="3">'.
+                         '<tr><td bgcolor="'.$tabbg.'">'.
+                         $titleinfo.'</td>'.$roleinfo.'</tr></table>';
+        if ($env{'request.state'} eq 'construct') {
+            if ($notopbar) {
+                $bodytag .= $titletable;
+            } else {
+                $bodytag .= &Apache::lonmenu::menubuttons($forcereg,'web',$forcereg,$titletable);
+            }
+	} else {
+            if ($notopbar) {
+                $bodytag .= $titletable;
+            } else {
+                $bodytag .= &Apache::lonmenu::menubuttons($forcereg,'web',$forcereg).
+                        $titletable;
+            }
+        }
+        return $bodytag;
     }
 
 #
 # Top frame rendering, Remote is up
 #
+    my $titleinfo = '&nbsp;<font size="5" face="Arial, Helvetica, sans-serif"><b>'.$title.'</b></font>';
+    if ($customtitle) {
+        $titleinfo = $customtitle;
+    }
+    #
+    # Extra info if you are the DC
+    my $dc_info = '';
+    if ($env{'user.adv'} && exists($env{'user.role.dc./'.
+                        $env{'course.'.$env{'request.course.id'}.
+                                 '.domain'}.'/'})) {
+        my $cid = $env{'request.course.id'};
+        $dc_info.= $cid.' '.$env{'course.'.$cid.'.internal.coursecode'};
+        $dc_info = '('.$dc_info.')';
+    }
+    # Explicit link to get inline menu
+    my $menu='<br /><font size="2" face="Arial, Helvetica, sans-serif">&nbsp;<a href="/adm/remote?action=collapse">'.&mt('Switch to Inline Menu Mode').'</a></font>';
+    #
     return(<<ENDBODY);
 $bodytag
 <table width="100%" cellspacing="0" border="0" cellpadding="0">
@@ -2174,33 +2910,610 @@ $upperleft</td>
 </tr>
 <tr>
 <td rowspan="3" bgcolor="$tabbg">
-&nbsp;<font size="5"><b>$title</b></font>
-<td bgcolor="$tabbg"  align="right">
-<font size="2">
-    $ENV{'environment.firstname'}
-    $ENV{'environment.middlename'}
-    $ENV{'environment.lastname'}
-    $ENV{'environment.generation'}
+$titleinfo $dc_info $menu
+</td><td bgcolor="$tabbg" align="right">
+<font size="2" face="Arial, Helvetica, sans-serif">
+    $env{'environment.firstname'}
+    $env{'environment.middlename'}
+    $env{'environment.lastname'}
+    $env{'environment.generation'}
     </font>&nbsp;
 </td>
 </tr>
 <tr><td bgcolor="$tabbg" align="right">
-<font size="2">$role</font>&nbsp;
+<font size="2" face="Arial, Helvetica, sans-serif">$role</font>&nbsp;
 </td></tr>
 <tr>
-<td bgcolor="$tabbg" align="right"><font size="2">$realm</font>&nbsp;</td></tr>
-</table><br>
+<td bgcolor="$tabbg" align="right"><font size="2" face="Arial, Helvetica, sans-serif">$realm</font>&nbsp;</td></tr>
+</table><br />
 ENDBODY
 }
 
 ###############################################
+###############################################
+
+=pod
+
+=back
+
+=head1 HTML Helpers
+
+=over 4
+
+=item * &endbodytag()
+
+Returns a uniform footer for LON-CAPA web pages.
+
+Inputs: none
+
+=back
+
+=cut
+
+sub endbodytag {
+    my $endbodytag='</body>';
+    $endbodytag=&Apache::lontexconvert::jsMath_process()."\n".$endbodytag;
+    return $endbodytag;
+}
+
+=pod
+
+=over 4
+
+=item * &headtag()
+
+Returns a uniform footer for LON-CAPA web pages.
+
+Inputs: $title - optional title for the head
+        $head_extra - optional extra HTML to put inside the <head>
+
+=back
+
+=cut
+
+sub headtag {
+    my ($title,$head_extra,$args) = @_;
+    
+    my $result =
+	'<head>'.
+	&Apache::lonxml::fontsettings().
+	&Apache::lonhtmlcommon::htmlareaheaders();
+    
+    if (ref($args->{'redirect'})) {
+	my ($time,$url) = @{$args->{'redirect'}};
+	$result.=<<ADDMETA
+<meta http-equiv="pragma" content="no-cache" />
+<meta HTTP-EQUIV="Refresh" CONTENT="2; url=$url" />
+ADDMETA
+    }
+    if (!defined($title)) {
+	$title = 'The LearningOnline Network with CAPA';
+    }
+    
+    $result .= '<title>'.&mt($title).'</title>'.$head_extra;
+    
+    return $result;
+}
+
+=pod
+
+=over 4
+
+=item * &endheadtag()
+
+Returns a uniform </head> for LON-CAPA web pages.
+
+Inputs: none
+
+=back
+
+=cut
+
+sub endheadtag {
+    return '</head>';
+}
+
+=pod
+
+=over 4
+
+=item * &head()
+
+Returns a uniform complete <head>..</head> section for LON-CAPA web pages.
+
+Inputs: $title - optional title for the page
+        $head_extra - optional extra HTML to put inside the <head>
+=back
+
+=cut
+
+sub head {
+    my ($title,$head_extra) = @_;
+    return &headtag($title,$head_extra).&endheadtag();
+}
+
+=pod
+
+=over 4
+
+=item * &start_page()
+
+Returns a complete <html> .. <body> section for LON-CAPA web pages.
+
+Inputs: $title - optional title for the page
+        $head_extra - optional extra HTML to incude inside the <head>
+        %args - additional optional args supported are:
+                  only_body   -> is true will set &bodytag() onlybodytag arg on
+                  no_nav_bar  -> is true will set &bodytag() notopbar arg on
+                  add_entries -> additional attributes to add to the  <body>
+                  domain      -> force to color decorate a page for a 
+                                 specific domain
+                  function    -> force usage of a specific rolish color scheme
+                  redirect    -> ...
+
+=back
+
+=cut
+
+sub start_page {
+    my ($title,$head_extra,$args) = @_;
+    my %head_args;
+    if (defined($args->{'redirect'})) {
+	$head_args{'redirect'} = $args->{'redirect'};
+    }
+
+    return 
+	&Apache::lonxml::xmlbegin().
+	&headtag($title,$head_extra,\%head_args).&endheadtag().
+	&bodytag($title, $args->{'function'}, $args->{'add_entries'},
+		 $args->{'only_body'},
+		 undef,undef,undef,$args->{'no_nav_bar'});
+}
+
+=pod
+
+=over 4
+
+=item * &head()
+
+Returns a complete </body></html> section for LON-CAPA web pages.
+
+Inputs: None
+
+=back
+
+=cut
+
+sub end_page {
+    return &endbodytag."\n</html>";
+}
+###############################################
+
+=pod
+
+=over 4
+
+=item get_users_function
+
+Used by &bodytag to determine the current users primary role.
+Returns either 'student','coordinator','admin', or 'author'.
+
+=cut
+
+###############################################
+sub get_users_function {
+    my $function = 'student';
+    if ($env{'request.role'}=~/^(cc|in|ta|ep)/) {
+        $function='coordinator';
+    }
+    if ($env{'request.role'}=~/^(su|dc|ad|li)/) {
+        $function='admin';
+    }
+    if (($env{'request.role'}=~/^(au|ca)/) ||
+        ($ENV{'REQUEST_URI'}=~/^(\/priv|\~)/)) {
+        $function='author';
+    }
+    return $function;
+}
+
+###############################################
+
+=pod
+
+=item check_user_status
+
+Determines current status of supplied role for a
+specific user. Roles can be active, previous or future.
+
+Inputs: 
+user's domain, user's username, course's domain,
+course's number, optional section/group.
+
+Outputs:
+role status: active, previous or future. 
+
+=cut
+
+sub check_user_status {
+    my ($udom,$uname,$cdom,$crs,$role,$secgrp) = @_;
+    my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname);
+    my @uroles = keys %userinfo;
+    my $srchstr;
+    my $active_chk = 'none';
+    if (@uroles > 0) {
+        if (($role eq 'cc') || ($secgrp eq '') || (!defined($secgrp))) {
+            $srchstr = '/'.$cdom.'/'.$crs.'_'.$role;
+        } else {
+            $srchstr = '/'.$cdom.'/'.$crs.'/'.$secgrp.'_'.$role;         }
+        if (grep/^$srchstr$/,@uroles) {
+            my $role_end = 0;
+            my $role_start = 0;
+            $active_chk = 'active';
+            if ($userinfo{$srchstr} =~ m/^($role)_(\d+)/) {
+                $role_end = $2;
+                if ($userinfo{$srchstr} =~ m/^($role)_($role_end)_(\d+)$/) {
+                    $role_start = $3;
+                }
+            }
+            if ($role_start > 0) {
+                if (time < $role_start) {
+                    $active_chk = 'future';
+                }
+            }
+            if ($role_end > 0) {
+                if (time > $role_end) {
+                    $active_chk = 'previous';
+                }
+            }
+        }
+    }
+    return $active_chk;
+}
+
+###############################################
+
+=pod
+
+=item get_sections
+
+Determines all the sections for a course including
+sections with students and sections containing other roles.
+Incoming parameters: domain, course number, reference to 
+section hash (keys to be section/group IDs), reference to 
+array containing roles for which sections should be gathered
+(optional). If the fourth argument is undefined, sections
+are gathered for any role.
+ 
+Returns number of sections.
+
+=cut
+
+###############################################
+sub get_sections {
+    my ($cdom,$cnum,$sectioncount,$possible_roles) = @_;
+    if (!($cdom && $cnum)) { return 0; }
+    my $numsections = 0;
+
+    if (!defined($possible_roles) || (grep/^st$/,@$possible_roles)) {
+	my ($classlist) = &Apache::loncoursedata::get_classlist($cdom,$cnum);
+	my $sec_index = &Apache::loncoursedata::CL_SECTION();
+	my $status_index = &Apache::loncoursedata::CL_STATUS();
+	while (my ($student,$data) = each %$classlist) {
+	    my ($section,$status) = ($data->[$sec_index],
+				     $data->[$status_index]);
+	    unless ($section eq '-1' || $section =~ /^\s*$/) {
+		if (!defined($$sectioncount{$section})) { $numsections++; }
+		$$sectioncount{$section}++;
+	    }
+	}
+    }
+    my %courseroles = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum);
+    foreach my $user (sort(keys(%courseroles))) {
+	if ($user !~ /^(\w{2})/) { next; }
+	my ($role) = ($user =~ /^(\w{2})/);
+	if ($possible_roles && !(grep(/^$role$/,@$possible_roles))) { next; }
+	my $section;
+	if ($role eq 'cr' &&
+	    $user =~ m-^$role/[^/]*/[^/]*/[^/]*:[^:]*:[^:]*:(\w+)-) {
+	    $section=$1;
+	}
+	if ($user =~ /^$role:[^:]*:[^:]*:(\w+)/) { $section=$1; }
+	if (!defined($section) || $section eq '-1') { next; }
+	if (!defined($$sectioncount{$section})) { $numsections++; } 
+	$$sectioncount{$section}++;
+    }
+    return $numsections;
+}
+
+###############################################
+                                                                                  
+=pod
+                                                                                  
+=item coursegroups
+
+Retrieve information about groups in a course,
+
+Input:
+1. Reference to hash to populate with group information. 
+2. Optional course domain
+3. Optional course number
+4. Optional group name
+
+Course domain and number will be taken from user's
+environment if not supplied. Optional group name will'
+be passed to lonnet::get_coursegroups() as a regexp to
+use in the call to the dump function.
+
+Output
+Returns number of groups in the course (subject to the
+optional group name filter).
+
+Side effects:
+Populates the referenced curr_groups hash, with key,
+value pairs. Keys are group names, corresponding values
+are scalars containing group information in XML. This
+can be sent to &get_group_settings() to be parsed.     
+
+=cut 
+
+###############################################
+
+sub coursegroups {
+    my ($curr_groups,$cdom,$cnum,$group) = @_;
+    my $numgroups;
+    if (!defined($cdom) || !defined($cnum)) {
+        my $cid =  $env{'request.course.id'};
+        $cdom = $env{'course.'.$cid.'.domain'};
+        $cnum = $env{'course.'.$cid.'.num'};
+    }
+    %{$curr_groups} = &Apache::lonnet::get_coursegroups($cdom,$cnum,$group);
+    my ($tmp) = keys(%{$curr_groups});
+    if ($tmp=~/^error:/) {
+        unless ($tmp eq 'error: 2 tie(GDBM) Failed while attempting dump') {
+            &logthis('Error retrieving groups: '.$tmp.' in '.$cnum.':'.
+                                                                   $cdom);
+        }
+        $numgroups = 0;
+    } else {
+        $numgroups = keys(%{$curr_groups});
+    }
+    return $numgroups;
+}
+
+###############################################
+
+=pod
+
+=item get_group_settings
+
+Uses TokeParser to extract group information from the
+XML used to describe course groups.
+
+Input:
+Scalar containing XML  - as retrieved from &coursegroups().
+
+Output:
+Hash containing group information as key=values for (a), and
+hash of hashes for (b)
+
+Keys (in two categories):
+(a) groupname, creator, creation, modified, startdate,enddate.
+Corresponding values are name of the group, creator of the group
+(username:domain), UNIX time for date group was created, and
+settings were last modified, and default start and end access
+times for group members.
+
+(b) functions returned in hash of hashes.
+Outer hash key is functions.
+Inner hash keys are chat,discussion,email,files,homepage,roster.
+Corresponding values are either on or off, depending on
+whether this type of functionality is available for the group.
+
+=cut
+                                                                                 
+###############################################
+
+sub get_group_settings {
+    my ($groupinfo)=@_;
+    my $parser=HTML::TokeParser->new(\$groupinfo);
+    my $token;
+    my $tool = '';
+    my $role = '';
+    my %content=();
+    while ($token=$parser->get_token) {
+        if ($token->[0] eq 'S')  {
+            my $entry=$token->[1];
+            if ($entry eq 'functions' || $entry eq 'autosec') {
+                %{$content{$entry}} = ();
+                $tool = $entry;
+            } elsif ($entry eq 'role') {
+                if ($tool eq 'autosec') {
+                    $role = $token->[2]{id};
+                }
+            } else {
+                my $value=$parser->get_text('/'.$entry);
+                if ($entry eq 'name') {
+                    if ($tool eq 'functions') {
+                        my $function = $token->[2]{id};
+                        $content{$tool}{$function} = $value;
+                    }
+                } elsif ($entry eq 'groupname') {
+                    $content{$entry}=&Apache::lonnet::unescape($value);
+                } elsif (($entry eq 'roles') || ($entry eq 'types') ||
+                         ($entry eq 'sectionpick') || ($entry eq 'defpriv')) {
+                    push(@{$content{$entry}},$value);
+                } elsif ($entry eq 'section') {
+                    if ($tool eq 'autosec'  && $role ne '') {
+                        push(@{$content{$tool}{$role}},$value);
+                    }
+                } else {
+                    $content{$entry}=$value;
+                }
+            }
+        } elsif ($token->[0] eq 'E') {
+            if ($token->[1] eq 'functions' || $token->[1] eq 'autosec') {
+                $tool = '';
+            } elsif ($token->[1] eq 'role') {
+                $role = '';
+            }
+
+        }
+    }
+    return %content;
+}
+
+sub check_group_access {
+    my ($group) = @_;
+    my $access = 1;
+    my $now = time;
+    my ($start,$end) = split(/\./,$env{'user.role.gr/'.$env{'request.course,id'}.'/'.$group});
+    if (($end!=0) && ($end<$now)) { $access = 0; }
+    if (($start!=0) && ($start>$now)) { $access=0; }
+    return $access;
+}
+
+###############################################
+
+=pod
+                                                                                
+=item get_course_users
+                                                                                
+Retrieves usernames:domains for users in the specified course
+with specific role(s), and access status. 
+
+Incoming parameters:
+1. course domain
+2. course number
+3. access status: users must have - either active, 
+previous, future, or all.
+4. reference to array of permissible roles
+5. reference to array of section restrictions (optional)
+6. reference to results object (hash of hashes).
+7. reference to optional userdata hash
+Keys of top level hash are roles.
+Keys of inner hashes are username:domain, with 
+values set to access type.
+Optional userdata hash returns an array with arguments in the 
+same order as loncoursedata::get_classlist() for student data.
+
+Entries for end, start, section and status are blank because
+of the possibility of multiple values for non-student roles.
+
+=cut
+                                                                                
+###############################################
+                                                                                
+sub get_course_users {
+    my ($cdom,$cnum,$types,$roles,$sections,$users,$userdata) = @_;
+    my %idx = ();
+
+    $idx{udom} = &Apache::loncoursedata::CL_SDOM();
+    $idx{uname} =  &Apache::loncoursedata::CL_SNAME();
+    $idx{end} = &Apache::loncoursedata::CL_END();
+    $idx{start} = &Apache::loncoursedata::CL_START();
+    $idx{id} = &Apache::loncoursedata::CL_ID();
+    $idx{section} = &Apache::loncoursedata::CL_SECTION();
+    $idx{fullname} = &Apache::loncoursedata::CL_FULLNAME();
+    $idx{status} = &Apache::loncoursedata::CL_STATUS();
+
+    if (grep(/^st$/,@{$roles})) {
+        my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist($cdom,$cnum);
+        my $now = time;
+        foreach my $student (keys(%{$classlist})) {
+            my $match = 0;
+            if ((ref($sections) eq 'ARRAY') && (@{$sections} > 0)) {
+		unless(grep(/^\Q$$classlist{$student}[$idx{section}]\E$/,
+			    @{$sections})) {
+		    next;
+		}
+            } 
+            if (defined($$types{'active'})) {
+                if ($$classlist{$student}[$idx{status}] eq 'Active') {
+                    push(@{$$users{st}{$student}},'active');
+                    $match = 1;
+                }
+            }
+            if (defined($$types{'previous'})) {
+                if ($$classlist{$student}[$idx{end}] <= $now) {
+                    push(@{$$users{st}{$student}},'previous');
+                    $match = 1;
+                }
+            }
+            if (defined($$types{'future'})) {
+                if (($$classlist{$student}[$idx{start}] > $now) && ($$classlist{$student}[$idx{end}] > $now) || ($$classlist{$student}[$idx{end}] == 0) || ($$classlist{$student}[$idx{end}] eq '')) {
+                    push(@{$$users{st}{$student}},'future');
+                    $match = 1;
+                }
+            }
+            if ($match && defined($userdata)) {
+                $$userdata{$student} = $$classlist{$student};
+            }
+        }
+    }
+    if ((@{$roles} > 0) && (@{$roles} ne "st")) {
+        my @coursepersonnel = &Apache::lonnet::getkeys('nohist_userroles',$cdom,$cnum);
+        foreach my $person (@coursepersonnel) {
+            my $match = 0;
+            my ($role,$user) = ($person =~ /^([^:]*):([^:]+:[^:]+)/);
+            $user =~ s/:$//;
+            if (($role) && (grep(/^\Q$role\E$/,@{$roles}))) {
+                my ($uname,$udom,$usec) = split(/:/,$user);
+                if ($usec ne '' && (ref($sections) eq 'ARRAY') && 
+		    @{$sections} > 0) {
+		    unless(grep(/^\Q$usec\E$/,@{$sections})) {
+			next;
+		    }
+                }
+                if ($uname ne '' && $udom ne '') {
+                    my $status = &check_user_status($udom,$uname,$cdom,$cnum,$role);
+                    foreach my $type (keys(%{$types})) { 
+                        if ($status eq $type) {
+                            @{$$users{$role}{$user}} = $type;
+                            $match = 1;
+                        }
+                    }
+                    if ($match && defined($userdata) &&
+                        !exists($$userdata{$uname.':'.$udom})) {
+			&get_user_info($udom,$uname,\%idx,$userdata);
+                    }
+                }
+            }
+        }
+        if (grep(/^ow$/,@{$roles})) {
+            if ((defined($cdom)) && (defined($cnum))) {
+                my %csettings = &Apache::lonnet::get('environment',['internal.courseowner'],$cdom,$cnum);
+                if ( defined($csettings{'internal.courseowner'}) ) {
+                    my $owner = $csettings{'internal.courseowner'};
+                    @{$$users{'ow'}{$owner.':'.$cdom}} = 'any';
+                    if (defined($userdata) && 
+			!exists($$userdata{$owner.':'.$cdom})) {
+			&get_user_info($cdom,$owner,\%idx,$userdata);
+		    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+sub get_user_info {
+    my ($udom,$uname,$idx,$userdata) = @_;
+    $$userdata{$uname.':'.$udom}[$$idx{fullname}] = 
+	&plainname($uname,$udom,'lastname');
+    $$userdata{$uname.':'.$udom}[$$idx{uname}] = $uname;
+    $$userdata{$uname.':'.$udom}[$$idx{udom}] = $udom;
+    return;
+}
+
+###############################################
 
 sub get_posted_cgi {
     my $r=shift;
 
     my $buffer;
-    
-    $r->read($buffer,$r->header_in('Content-length'),0);
+    if ($r->header_in('Content-length')) {
+	$r->read($buffer,$r->header_in('Content-length'),0);
+    }
     unless ($buffer=~/^(\-+\w+)\s+Content\-Disposition\:\s*form\-data/si) {
 	my @pairs=split(/&/,$buffer);
 	my $pair;
@@ -2225,8 +3538,8 @@ sub get_posted_cgi {
 		if ($name) {
 		    chomp($value);
 		    if ($fname) {
-			$ENV{"form.$name.filename"}=$fname;
-			$ENV{"form.$name.mimetype"}=$fmime;
+			$env{"form.$name.filename"}=$fname;
+			$env{"form.$name.mimetype"}=$fmime;
 		    } else {
 			$value=~s/\s+$//s;
 		    }
@@ -2258,7 +3571,7 @@ sub get_posted_cgi {
 	    }
 	}
     }
-    $ENV{'request.method'}=$ENV{'REQUEST_METHOD'};
+    $env{'request.method'}=$ENV{'REQUEST_METHOD'};
     $r->method_number(M_GET);
     $r->method('GET');
     $r->headers_in->unset('Content-length');
@@ -2268,14 +3581,14 @@ sub get_posted_cgi {
 
 =item * get_unprocessed_cgi($query,$possible_names)
 
-Modify the %ENV hash to contain unprocessed CGI form parameters held in
+Modify the %env hash to contain unprocessed CGI form parameters held in
 $query.  The parameters listed in $possible_names (an array reference),
-will be set in $ENV{'form.name'} if they do not already exist.
+will be set in $env{'form.name'} if they do not already exist.
 
 Typically called with $ENV{'QUERY_STRING'} as the first parameter.  
 $possible_names is an ref to an array of form element names.  As an example:
 get_unprocessed_cgi($ENV{'QUERY_STRING'},['uname','udom']);
-will result in $ENV{'form.uname'} and $ENV{'form.udom'} being set.
+will result in $env{'form.uname'} and $env{'form.udom'} being set.
 
 =cut
 
@@ -2288,8 +3601,7 @@ sub get_unprocessed_cgi {
     if (!defined($possible_names) || (grep {$_ eq $name} @$possible_names)) {
       $value =~ tr/+/ /;
       $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
-      &Apache::lonxml::debug("Seting :$name: to :$value:");
-      unless (defined($ENV{'form.'.$name})) { &add_to_env('form.'.$name,$value) };
+      unless (defined($env{'form.'.$name})) { &add_to_env('form.'.$name,$value) };
     }
   }
 }
@@ -2303,12 +3615,12 @@ returns cache-controlling header code
 =cut
 
 sub cacheheader {
-  unless ($ENV{'request.method'} eq 'GET') { return ''; }
-  my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime);
-  my $output .='<meta HTTP-EQUIV="Expires" CONTENT="'.$date.'" />
+    unless ($env{'request.method'} eq 'GET') { return ''; }
+    my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime);
+    my $output .='<meta HTTP-EQUIV="Expires" CONTENT="'.$date.'" />
                 <meta HTTP-EQUIV="Cache-control" CONTENT="no-cache" />
                 <meta HTTP-EQUIV="Pragma" CONTENT="no-cache" />';
-  return $output;
+    return $output;
 }
 
 =pod
@@ -2320,27 +3632,38 @@ specifies header code to not have cache
 =cut
 
 sub no_cache {
-  my ($r) = @_;
-  unless ($ENV{'request.method'} eq 'GET') { return ''; }
-  #my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime);
-  $r->no_cache(1);
-  $r->header_out("Pragma" => "no-cache");
-  #$r->header_out("Expires" => $date);
+    my ($r) = @_;
+    if ($ENV{'REQUEST_METHOD'} ne 'GET' &&
+	$env{'request.method'} ne 'GET') { return ''; }
+    my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime(time));
+    $r->no_cache(1);
+    $r->header_out("Expires" => $date);
+    $r->header_out("Pragma" => "no-cache");
 }
 
 sub content_type {
-  my ($r,$type,$charset) = @_;
-  unless ($charset) {
-      $charset=&Apache::lonlocal::current_encoding;
-  }
-  $r->content_type($type.($charset?'; charset='.$charset:''));
+    my ($r,$type,$charset) = @_;
+    if ($r) {
+	#  Note that printout.pl calls this with undef for $r.
+	&no_cache($r);
+    }
+    if ($env{'browser.mathml'} && $type eq 'text/html') { $type='text/xml'; }
+    unless ($charset) {
+	$charset=&Apache::lonlocal::current_encoding;
+    }
+    if ($charset) { $type.='; charset='.$charset; }
+    if ($r) {
+	$r->content_type($type);
+    } else {
+	print("Content-type: $type\n\n");
+    }
 }
 
 =pod
 
 =item * add_to_env($name,$value) 
 
-adds $name to the %ENV hash with value
+adds $name to the %env hash with value
 $value, if $name already exists, the entry is converted to an array
 reference and $value is added to the array.
 
@@ -2348,23 +3671,49 @@ reference and $value is added to the arr
 
 sub add_to_env {
   my ($name,$value)=@_;
-  if (defined($ENV{$name})) {
-    if (ref($ENV{$name})) {
+  if (defined($env{$name})) {
+    if (ref($env{$name})) {
       #already have multiple values
-      push(@{ $ENV{$name} },$value);
+      push(@{ $env{$name} },$value);
     } else {
       #first time seeing multiple values, convert hash entry to an arrayref
-      my $first=$ENV{$name};
-      undef($ENV{$name});
-      push(@{ $ENV{$name} },$first,$value);
+      my $first=$env{$name};
+      undef($env{$name});
+      push(@{ $env{$name} },$first,$value);
     }
   } else {
-    $ENV{$name}=$value;
+    $env{$name}=$value;
   }
 }
 
 =pod
 
+=item * get_env_multiple($name) 
+
+gets $name from the %env hash, it seemlessly handles the cases where multiple
+values may be defined and end up as an array ref.
+
+returns an array of values
+
+=cut
+
+sub get_env_multiple {
+    my ($name) = @_;
+    my @values;
+    if (defined($env{$name})) {
+        # exists is it an array
+        if (ref($env{$name})) {
+            @values=@{ $env{$name} };
+        } else {
+            $values[0]=$env{$name};
+        }
+    }
+    return(@values);
+}
+
+
+=pod
+
 =back 
 
 =head1 CSV Upload/Handling functions
@@ -2374,24 +3723,27 @@ sub add_to_env {
 =item * upfile_store($r)
 
 Store uploaded file, $r should be the HTTP Request object,
-needs $ENV{'form.upfile'}
+needs $env{'form.upfile'}
 returns $datatoken to be put into hidden field
 
 =cut
 
 sub upfile_store {
     my $r=shift;
-    $ENV{'form.upfile'}=~s/\r/\n/gs;
-    $ENV{'form.upfile'}=~s/\f/\n/gs;
-    $ENV{'form.upfile'}=~s/\n+/\n/gs;
-    $ENV{'form.upfile'}=~s/\n+$//gs;
+    $env{'form.upfile'}=~s/\r/\n/gs;
+    $env{'form.upfile'}=~s/\f/\n/gs;
+    $env{'form.upfile'}=~s/\n+/\n/gs;
+    $env{'form.upfile'}=~s/\n+$//gs;
 
-    my $datatoken=$ENV{'user.name'}.'_'.$ENV{'user.domain'}.
-	'_enroll_'.$ENV{'request.course.id'}.'_'.time.'_'.$$;
+    my $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
+	'_enroll_'.$env{'request.course.id'}.'_'.time.'_'.$$;
     {
-	my $fh=Apache::File->new('>'.$r->dir_config('lonDaemons').
-				 '/tmp/'.$datatoken.'.tmp');
-	print $fh $ENV{'form.upfile'};
+        my $datafile = $r->dir_config('lonDaemons').
+                           '/tmp/'.$datatoken.'.tmp';
+        if ( open(my $fh,">$datafile") ) {
+            print $fh $env{'form.upfile'};
+            close($fh);
+        }
     }
     return $datatoken;
 }
@@ -2401,8 +3753,8 @@ sub upfile_store {
 =item * load_tmp_file($r)
 
 Load uploaded file from tmp, $r should be the HTTP Request object,
-needs $ENV{'form.datatoken'},
-sets $ENV{'form.upfile'} to the contents of the file
+needs $env{'form.datatoken'},
+sets $env{'form.upfile'} to the contents of the file
 
 =cut
 
@@ -2410,13 +3762,14 @@ sub load_tmp_file {
     my $r=shift;
     my @studentdata=();
     {
-	my $fh;
-	if ($fh=Apache::File->new($r->dir_config('lonDaemons').
-				  '/tmp/'.$ENV{'form.datatoken'}.'.tmp')) {
-	    @studentdata=<$fh>;
-	}
+        my $studentfile = $r->dir_config('lonDaemons').
+                              '/tmp/'.$env{'form.datatoken'}.'.tmp';
+        if ( open(my $fh,"<$studentfile") ) {
+            @studentdata=<$fh>;
+            close($fh);
+        }
     }
-    $ENV{'form.upfile'}=join('',@studentdata);
+    $env{'form.upfile'}=join('',@studentdata);
 }
 
 =pod
@@ -2425,14 +3778,19 @@ sub load_tmp_file {
 
 Separate uploaded file into records
 returns array of records,
-needs $ENV{'form.upfile'} and $ENV{'form.upfiletype'}
+needs $env{'form.upfile'} and $env{'form.upfiletype'}
 
 =cut
 
 sub upfile_record_sep {
-    if ($ENV{'form.upfiletype'} eq 'xml') {
+    if ($env{'form.upfiletype'} eq 'xml') {
     } else {
-	return split(/\n/,$ENV{'form.upfile'});
+	my @records;
+	foreach my $line (split(/\n/,$env{'form.upfile'})) {
+	    if ($line=~/^\s*$/) { next; }
+	    push(@records,$line);
+	}
+	return @records;
     }
 }
 
@@ -2440,30 +3798,35 @@ sub upfile_record_sep {
 
 =item * record_sep($record)
 
-Separate a record into fields $record should be an item from the upfile_record_sep(), needs $ENV{'form.upfiletype'}
+Separate a record into fields $record should be an item from the upfile_record_sep(), needs $env{'form.upfiletype'}
 
 =cut
 
+sub takeleft {
+    my $index=shift;
+    return substr('0000'.$index,-4,4);
+}
+
 sub record_sep {
     my $record=shift;
     my %components=();
-    if ($ENV{'form.upfiletype'} eq 'xml') {
-    } elsif ($ENV{'form.upfiletype'} eq 'space') {
+    if ($env{'form.upfiletype'} eq 'xml') {
+    } elsif ($env{'form.upfiletype'} eq 'space') {
         my $i=0;
         foreach (split(/\s+/,$record)) {
             my $field=$_;
             $field=~s/^(\"|\')//;
             $field=~s/(\"|\')$//;
-            $components{$i}=$field;
+            $components{&takeleft($i)}=$field;
             $i++;
         }
-    } elsif ($ENV{'form.upfiletype'} eq 'tab') {
+    } elsif ($env{'form.upfiletype'} eq 'tab') {
         my $i=0;
-        foreach (split(/\t+/,$record)) {
+        foreach (split(/\t/,$record)) {
             my $field=$_;
             $field=~s/^(\"|\')//;
             $field=~s/(\"|\')$//;
-            $components{$i}=$field;
+            $components{&takeleft($i)}=$field;
             $i++;
         }
     } else {
@@ -2481,33 +3844,62 @@ sub record_sep {
                 $field=~s/^\s*$delimiter//;
                 $field=~s/$delimiter\s*$//;
             }
-            $components{$i}=$field;
+            $components{&takeleft($i)}=$field;
 	    $i++;
         }
     }
     return %components;
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * upfile_select_html()
 
-return HTML code to select file and specify its type
+Return HTML code to select a file from the users machine and specify 
+the file type.
 
 =cut
 
+######################################################
+######################################################
 sub upfile_select_html {
-    return (<<'ENDUPFORM');
-<input type="file" name="upfile" size="50" />
-<br />Type: <select name="upfiletype">
-<option value="csv">CSV (comma separated values, spreadsheet)</option>
-<option value="space">Space separated</option>
-<option value="tab">Tabulator separated</option>
-<option value="xml">HTML/XML</option>
-</select>
-ENDUPFORM
+    my %Types = (
+                 csv   => &mt('CSV (comma separated values, spreadsheet)'),
+                 space => &mt('Space separated'),
+                 tab   => &mt('Tabulator separated'),
+#                 xml   => &mt('HTML/XML'),
+                 );
+    my $Str = '<input type="file" name="upfile" size="50" />'.
+        '<br />Type: <select name="upfiletype">';
+    foreach my $type (sort(keys(%Types))) {
+        $Str .= '<option value="'.$type.'" >'.$Types{$type}."</option>\n";
+    }
+    $Str .= "</select>\n";
+    return $Str;
+}
+
+sub get_samples {
+    my ($records,$toget) = @_;
+    my @samples=({});
+    my $got=0;
+    foreach my $rec (@$records) {
+	my %temp = &record_sep($rec);
+	if (! grep(/\S/, values(%temp))) { next; }
+	if (%temp) {
+	    $samples[$got]=\%temp;
+	    $got++;
+	    if ($got == $toget) { last; }
+	}
+    }
+    return \@samples;
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * csv_print_samples($r,$records)
@@ -2518,19 +3910,19 @@ Apache Request ref, $records is an array
 
 =cut
 
+######################################################
+######################################################
 sub csv_print_samples {
     my ($r,$records) = @_;
-    my (%sone,%stwo,%sthree);
-    %sone=&record_sep($$records[0]);
-    if (defined($$records[1])) {%stwo=&record_sep($$records[1]);}
-    if (defined($$records[2])) {%sthree=&record_sep($$records[2]);}
+    my $samples = &get_samples($records,3);
 
-    $r->print('Samples<br /><table border="2"><tr>');
-    foreach (sort({$a <=> $b} keys(%sone))) { $r->print('<th>Column&nbsp;'.($_+1).'</th>'); }
+    $r->print(&mt('Samples').'<br /><table border="2"><tr>');
+    foreach (sort({$a <=> $b} keys(%{ $samples->[0] }))) { 
+        $r->print('<th>'.&mt('Column&nbsp;[_1]',($_+1)).'</th>'); }
     $r->print('</tr>');
-    foreach my $hash (\%sone,\%stwo,\%sthree) {
+    foreach my $hash (@$samples) {
 	$r->print('<tr>');
-	foreach (sort({$a <=> $b} keys(%sone))) {
+	foreach (sort({$a <=> $b} keys(%{ $samples->[0] }))) {
 	    $r->print('<td>');
 	    if (defined($$hash{$_})) { $r->print($$hash{$_}); }
 	    $r->print('</td>');
@@ -2540,32 +3932,42 @@ sub csv_print_samples {
     $r->print('</tr></table><br />'."\n");
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * csv_print_select_table($r,$records,$d)
 
 Prints a table to create associations between values and table columns.
+
 $r is an Apache Request ref,
 $records is an arrayref from &Apache::loncommon::upfile_record_sep,
-$d is an array of 2 element arrays (internal name, displayed name)
+$d is an array of 2 element arrays (internal name, displayed name,defaultcol)
 
 =cut
 
+######################################################
+######################################################
 sub csv_print_select_table {
     my ($r,$records,$d) = @_;
-    my $i=0;my %sone;
-    %sone=&record_sep($$records[0]);
-    $r->print('Associate columns with student attributes.'."\n".
-	     '<table border="2"><tr><th>Attribute</th><th>Column</th></tr>'."\n");
+    my $i=0;
+    my $samples = &get_samples($records,1);
+    $r->print(&mt('Associate columns with student attributes.')."\n".
+	     '<table border="2"><tr>'.
+              '<th>'.&mt('Attribute').'</th>'.
+              '<th>'.&mt('Column').'</th></tr>'."\n");
     foreach (@$d) {
-	my ($value,$display)=@{ $_ };
+	my ($value,$display,$defaultcol)=@{ $_ };
 	$r->print('<tr><td>'.$display.'</td>');
 
 	$r->print('<td><select name=f'.$i.
 		  ' onchange="javascript:flip(this.form,'.$i.');">');
 	$r->print('<option value="none"></option>');
-	foreach (sort({$a <=> $b} keys(%sone))) {
-	    $r->print('<option value="'.$_.'">Column '.($_+1).'</option>');
+	foreach (sort({$a <=> $b} keys(%{ $samples->[0] }))) {
+	    $r->print('<option value="'.$_.'"'.
+                      ($_ eq $defaultcol ? ' selected="selected" ' : '').
+                      '>Column '.($_+1).'</option>');
 	}
 	$r->print('</select></td></tr>'."\n");
 	$i++;
@@ -2574,6 +3976,9 @@ sub csv_print_select_table {
     return $i;
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * csv_samples_select_table($r,$records,$d)
@@ -2586,27 +3991,31 @@ $d is an array of 2 element arrays (inte
 
 =cut
 
+######################################################
+######################################################
 sub csv_samples_select_table {
     my ($r,$records,$d) = @_;
-    my %sone; my %stwo; my %sthree;
     my $i=0;
+    #
+    my $samples = &get_samples($records,3);
+    $r->print('<table border=2><tr><th>'.
+              &mt('Field').'</th><th>'.&mt('Samples').'</th></tr>');
 
-    $r->print('<table border=2><tr><th>Field</th><th>Samples</th></tr>');
-    %sone=&record_sep($$records[0]);
-    if (defined($$records[1])) {%stwo=&record_sep($$records[1]);}
-    if (defined($$records[2])) {%sthree=&record_sep($$records[2]);}
-
-    foreach (sort keys %sone) {
-	$r->print('<tr><td><select name=f'.$i.
+    foreach my $key (sort(keys(%{ $samples->[0] }))) {
+	$r->print('<tr><td><select name="f'.$i.'"'.
 		  ' onchange="javascript:flip(this.form,'.$i.');">');
-	foreach (@$d) {
-	    my ($value,$display)=@{ $_ };
-	    $r->print('<option value='.$value.'>'.$display.'</option>');
+	foreach my $option (@$d) {
+	    my ($value,$display,$defaultcol)=@{ $option };
+	    $r->print('<option value="'.$value.'"'.
+                      ($i eq $defaultcol ? ' selected="selected" ':'').'>'.
+                      $display.'</option>');
 	}
 	$r->print('</select></td><td>');
-	if (defined($sone{$_})) { $r->print($sone{$_}."</br>\n"); }
-	if (defined($stwo{$_})) { $r->print($stwo{$_}."</br>\n"); }
-	if (defined($sthree{$_})) { $r->print($sthree{$_}."</br>\n"); }
+	foreach my $line (0..2) {
+	    if (defined($samples->[$line]{$key})) { 
+		$r->print($samples->[$line]{$key}."<br />\n"); 
+	    }
+	}
 	$r->print('</td></tr>');
 	$i++;
     }
@@ -2614,6 +4023,9 @@ sub csv_samples_select_table {
     return($i);
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item clean_excel_name($name)
@@ -2622,6 +4034,8 @@ Returns a replacement for $name which do
 
 =cut
 
+######################################################
+######################################################
 sub clean_excel_name {
     my ($name) = @_;
     $name =~ s/[:\*\?\/\\]//g;
@@ -2650,13 +4064,47 @@ $uname, optional the username of the use
 
 sub check_if_partid_hidden {
     my ($id,$symb,$udom,$uname) = @_;
-    my $hiddenparts=&Apache::lonnet::EXT('resource.0.parameter_hiddenparts',
+    my $hiddenparts=&Apache::lonnet::EXT('resource.0.hiddenparts',
 					 $symb,$udom,$uname);
+    my $truth=1;
+    #if the string starts with !, then the list is the list to show not hide
+    if ($hiddenparts=~s/^\s*!//) { $truth=undef; }
     my @hiddenlist=split(/,/,$hiddenparts);
     foreach my $checkid (@hiddenlist) {
-	if ($checkid =~ /^\s*\Q$id\E\s*$/) { return 1; }
+	if ($checkid =~ /^\s*\Q$id\E\s*$/) { return $truth; }
     }
-    return undef;
+    return !$truth;
+}
+
+
+############################################################
+############################################################
+
+=pod
+
+=back 
+
+=head1 cgi-bin script and graphing routines
+
+=over 4
+
+=item get_cgi_id
+
+Inputs: none
+
+Returns an id which can be used to pass environment variables
+to various cgi-bin scripts.  These environment variables will
+be removed from the users environment after a given time by
+the routine &Apache::lonnet::transfer_profile_to_env.
+
+=cut
+
+############################################################
+############################################################
+my $uniq=0;
+sub get_cgi_id {
+    $uniq=($uniq+1)%100000;
+    return (time.'_'.$$.'_'.$uniq);
 }
 
 ############################################################
@@ -2664,50 +4112,143 @@ sub check_if_partid_hidden {
 
 =pod
 
-=item DrawGraph
+=item DrawBarGraph
+
+Facilitates the plotting of data in a (stacked) bar graph.
+Puts plot definition data into the users environment in order for 
+graph.png to plot it.  Returns an <img> tag for the plot.
+The bars on the plot are labeled '1','2',...,'n'.
+
+Inputs:
+
+=over 4
+
+=item $Title: string, the title of the plot
+
+=item $xlabel: string, text describing the X-axis of the plot
+
+=item $ylabel: string, text describing the Y-axis of the plot
+
+=item $Max: scalar, the maximum Y value to use in the plot
+If $Max is < any data point, the graph will not be rendered.
 
-Returns a link to cgi-bin/graph
+=item $colors: array ref holding the colors to be used for the data sets when
+they are plotted.  If undefined, default values will be used.
+
+=item $labels: array ref holding the labels to use on the x-axis for the bars.
+
+=item @Values: An array of array references.  Each array reference holds data
+to be plotted in a stacked bar chart.
+
+=item If the final element of @Values is a hash reference the key/value
+pairs will be added to the graph definition.
+
+=back
+
+Returns:
+
+An <img> tag which references graph.png and the appropriate identifying
+information for the plot.
 
 =cut
 
 ############################################################
 ############################################################
-sub DrawGraph {
-    my ($Title,$xlabel,$ylabel,$Max,$values1,$values2)=@_;
+sub DrawBarGraph {
+    my ($Title,$xlabel,$ylabel,$Max,$colors,$labels,@Values)=@_;
     #
-    my $identifier = time.'_'.int(rand(1000));
-    if (! defined($values1) || ref($values1) ne 'ARRAY') {
+    if (! defined($colors)) {
+        $colors = ['#33ff00', 
+                  '#0033cc', '#990000', '#aaaa66', '#663399', '#ff9933',
+                  '#66ccff', '#ff9999', '#cccc33', '#660000', '#33cc66',
+                  ]; 
+    }
+    my $extra_settings = {};
+    if (ref($Values[-1]) eq 'HASH') {
+        $extra_settings = pop(@Values);
+    }
+    #
+    my $identifier = &get_cgi_id();
+    my $id = 'cgi.'.$identifier;        
+    if (! @Values || ref($Values[0]) ne 'ARRAY') {
         return '';
     }
     #
-    $Title  = '' if (! defined($Title));
-    $xlabel = '' if (! defined($xlabel));
-    $ylabel = '' if (! defined($ylabel));
-    $Title  = &Apache::lonnet::escape($Title);
-    $xlabel = &Apache::lonnet::escape($xlabel);
-    $ylabel = &Apache::lonnet::escape($ylabel);
+    my @Labels;
+    if (defined($labels)) {
+        @Labels = @$labels;
+    } else {
+        for (my $i=0;$i<@{$Values[0]};$i++) {
+            push (@Labels,$i+1);
+        }
+    }
     #
-    my $data1 = join(',', @$values1);
-    my $data2;
-    if (defined($values2)) {
-        $data2 = join(',', @$values2);
+    my $NumBars = scalar(@{$Values[0]});
+    if ($NumBars < scalar(@Labels)) { $NumBars = scalar(@Labels); }
+    my %ValuesHash;
+    my $NumSets=1;
+    foreach my $array (@Values) {
+        next if (! ref($array));
+        $ValuesHash{$id.'.data.'.$NumSets++} = 
+            join(',',@$array);
+    }
+    #
+    my ($height,$width,$xskip,$bar_width) = (200,120,1,15);
+    if ($NumBars < 3) {
+        $width = 120+$NumBars*32;
+        $xskip = 1;
+        $bar_width = 30;
+    } elsif ($NumBars < 5) {
+        $width = 120+$NumBars*20;
+        $xskip = 1;
+        $bar_width = 20;
+    } elsif ($NumBars < 10) {
+        $width = 120+$NumBars*15;
+        $xskip = 1;
+        $bar_width = 15;
+    } elsif ($NumBars <= 25) {
+        $width = 120+$NumBars*11;
+        $xskip = 5;
+        $bar_width = 8;
+    } elsif ($NumBars <= 50) {
+        $width = 120+$NumBars*8;
+        $xskip = 5;
+        $bar_width = 4;
+    } else {
+        $width = 120+$NumBars*8;
+        $xskip = 5;
+        $bar_width = 4;
     }
     #
-    my $NumBars = scalar(@$values1);
     $Max = 1 if ($Max < 1);
     if ( int($Max) < $Max ) {
         $Max++;
         $Max = int($Max);
     }
+    $Title  = '' if (! defined($Title));
+    $xlabel = '' if (! defined($xlabel));
+    $ylabel = '' if (! defined($ylabel));
+    $ValuesHash{$id.'.title'}    = &Apache::lonnet::escape($Title);
+    $ValuesHash{$id.'.xlabel'}   = &Apache::lonnet::escape($xlabel);
+    $ValuesHash{$id.'.ylabel'}   = &Apache::lonnet::escape($ylabel);
+    $ValuesHash{$id.'.y_max_value'} = $Max;
+    $ValuesHash{$id.'.NumBars'}  = $NumBars;
+    $ValuesHash{$id.'.NumSets'}  = $NumSets;
+    $ValuesHash{$id.'.PlotType'} = 'bar';
+    $ValuesHash{$id.'.Colors'}   = join(',',@{$colors});
+    $ValuesHash{$id.'.height'}   = $height;
+    $ValuesHash{$id.'.width'}    = $width;
+    $ValuesHash{$id.'.xskip'}    = $xskip;
+    $ValuesHash{$id.'.bar_width'} = $bar_width;
+    $ValuesHash{$id.'.labels'} = join(',',@Labels);
+    #
+    # Deal with other parameters
+    while (my ($key,$value) = each(%$extra_settings)) {
+        $ValuesHash{$id.'.'.$key} = $value;
+    }
     #
-    &Apache::lonnet::appenv($identifier.'.title'   => $Title,
-                            $identifier.'.xlabel'  => $xlabel,
-                            $identifier.'.ylabel'  => $ylabel,
-                            $identifier.'.Max'     => $Max,
-                            $identifier.'.NumBars' => $NumBars,
-                            $identifier.'.data1'   => $data1,
-                            $identifier.'.data2'   => $data2);
-    return '<IMG src="/cgi-bin/graph.png?'.$identifier.'" border="1" />';
+    &Apache::lonnet::appenv(%ValuesHash);
+    return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />';
 }
 
 ############################################################
@@ -2715,6 +4256,410 @@ sub DrawGraph {
 
 =pod
 
+=item DrawXYGraph
+
+Facilitates the plotting of data in an XY graph.
+Puts plot definition data into the users environment in order for 
+graph.png to plot it.  Returns an <img> tag for the plot.
+
+Inputs:
+
+=over 4
+
+=item $Title: string, the title of the plot
+
+=item $xlabel: string, text describing the X-axis of the plot
+
+=item $ylabel: string, text describing the Y-axis of the plot
+
+=item $Max: scalar, the maximum Y value to use in the plot
+If $Max is < any data point, the graph will not be rendered.
+
+=item $colors: Array ref containing the hex color codes for the data to be 
+plotted in.  If undefined, default values will be used.
+
+=item $Xlabels: Array ref containing the labels to be used for the X-axis.
+
+=item $Ydata: Array ref containing Array refs.  
+Each of the contained arrays will be plotted as a separate curve.
+
+=item %Values: hash indicating or overriding any default values which are 
+passed to graph.png.  
+Possible values are: width, xskip, x_ticks, x_tick_offset, among others.
+
+=back
+
+Returns:
+
+An <img> tag which references graph.png and the appropriate identifying
+information for the plot.
+
+=cut
+
+############################################################
+############################################################
+sub DrawXYGraph {
+    my ($Title,$xlabel,$ylabel,$Max,$colors,$Xlabels,$Ydata,%Values)=@_;
+    #
+    # Create the identifier for the graph
+    my $identifier = &get_cgi_id();
+    my $id = 'cgi.'.$identifier;
+    #
+    $Title  = '' if (! defined($Title));
+    $xlabel = '' if (! defined($xlabel));
+    $ylabel = '' if (! defined($ylabel));
+    my %ValuesHash = 
+        (
+         $id.'.title'  => &Apache::lonnet::escape($Title),
+         $id.'.xlabel' => &Apache::lonnet::escape($xlabel),
+         $id.'.ylabel' => &Apache::lonnet::escape($ylabel),
+         $id.'.y_max_value'=> $Max,
+         $id.'.labels'     => join(',',@$Xlabels),
+         $id.'.PlotType'   => 'XY',
+         );
+    #
+    if (defined($colors) && ref($colors) eq 'ARRAY') {
+        $ValuesHash{$id.'.Colors'}   = join(',',@{$colors});
+    }
+    #
+    if (! ref($Ydata) || ref($Ydata) ne 'ARRAY') {
+        return '';
+    }
+    my $NumSets=1;
+    foreach my $array (@{$Ydata}){
+        next if (! ref($array));
+        $ValuesHash{$id.'.data.'.$NumSets++} = join(',',@$array);
+    }
+    $ValuesHash{$id.'.NumSets'} = $NumSets-1;
+    #
+    # Deal with other parameters
+    while (my ($key,$value) = each(%Values)) {
+        $ValuesHash{$id.'.'.$key} = $value;
+    }
+    #
+    &Apache::lonnet::appenv(%ValuesHash);
+    return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />';
+}
+
+############################################################
+############################################################
+
+=pod
+
+=item DrawXYYGraph
+
+Facilitates the plotting of data in an XY graph with two Y axes.
+Puts plot definition data into the users environment in order for 
+graph.png to plot it.  Returns an <img> tag for the plot.
+
+Inputs:
+
+=over 4
+
+=item $Title: string, the title of the plot
+
+=item $xlabel: string, text describing the X-axis of the plot
+
+=item $ylabel: string, text describing the Y-axis of the plot
+
+=item $colors: Array ref containing the hex color codes for the data to be 
+plotted in.  If undefined, default values will be used.
+
+=item $Xlabels: Array ref containing the labels to be used for the X-axis.
+
+=item $Ydata1: The first data set
+
+=item $Min1: The minimum value of the left Y-axis
+
+=item $Max1: The maximum value of the left Y-axis
+
+=item $Ydata2: The second data set
+
+=item $Min2: The minimum value of the right Y-axis
+
+=item $Max2: The maximum value of the left Y-axis
+
+=item %Values: hash indicating or overriding any default values which are 
+passed to graph.png.  
+Possible values are: width, xskip, x_ticks, x_tick_offset, among others.
+
+=back
+
+Returns:
+
+An <img> tag which references graph.png and the appropriate identifying
+information for the plot.
+
+=cut
+
+############################################################
+############################################################
+sub DrawXYYGraph {
+    my ($Title,$xlabel,$ylabel,$colors,$Xlabels,$Ydata1,$Min1,$Max1,
+                                        $Ydata2,$Min2,$Max2,%Values)=@_;
+    #
+    # Create the identifier for the graph
+    my $identifier = &get_cgi_id();
+    my $id = 'cgi.'.$identifier;
+    #
+    $Title  = '' if (! defined($Title));
+    $xlabel = '' if (! defined($xlabel));
+    $ylabel = '' if (! defined($ylabel));
+    my %ValuesHash = 
+        (
+         $id.'.title'  => &Apache::lonnet::escape($Title),
+         $id.'.xlabel' => &Apache::lonnet::escape($xlabel),
+         $id.'.ylabel' => &Apache::lonnet::escape($ylabel),
+         $id.'.labels' => join(',',@$Xlabels),
+         $id.'.PlotType' => 'XY',
+         $id.'.NumSets' => 2,
+         $id.'.two_axes' => 1,
+         $id.'.y1_max_value' => $Max1,
+         $id.'.y1_min_value' => $Min1,
+         $id.'.y2_max_value' => $Max2,
+         $id.'.y2_min_value' => $Min2,
+         );
+    #
+    if (defined($colors) && ref($colors) eq 'ARRAY') {
+        $ValuesHash{$id.'.Colors'}   = join(',',@{$colors});
+    }
+    #
+    if (! ref($Ydata1) || ref($Ydata1) ne 'ARRAY' ||
+        ! ref($Ydata2) || ref($Ydata2) ne 'ARRAY'){
+        return '';
+    }
+    my $NumSets=1;
+    foreach my $array ($Ydata1,$Ydata2){
+        next if (! ref($array));
+        $ValuesHash{$id.'.data.'.$NumSets++} = join(',',@$array);
+    }
+    #
+    # Deal with other parameters
+    while (my ($key,$value) = each(%Values)) {
+        $ValuesHash{$id.'.'.$key} = $value;
+    }
+    #
+    &Apache::lonnet::appenv(%ValuesHash);
+    return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />';
+}
+
+############################################################
+############################################################
+
+=pod
+
+=back 
+
+=head1 Statistics helper routines?  
+
+Bad place for them but what the hell.
+
+=over 4
+
+=item &chartlink
+
+Returns a link to the chart for a specific student.  
+
+Inputs:
+
+=over 4
+
+=item $linktext: The text of the link
+
+=item $sname: The students username
+
+=item $sdomain: The students domain
+
+=back
+
+=back
+
+=cut
+
+############################################################
+############################################################
+sub chartlink {
+    my ($linktext, $sname, $sdomain) = @_;
+    my $link = '<a href="/adm/statistics?reportSelected=student_assessment'.
+        '&amp;SelectedStudent='.&Apache::lonnet::escape($sname.':'.$sdomain).
+        '&amp;chartoutputmode='.HTML::Entities::encode('html, with all links').
+       '">'.$linktext.'</a>';
+}
+
+#######################################################
+#######################################################
+
+=pod
+
+=head1 Course Environment Routines
+
+=over 4
+
+=item &restore_course_settings 
+
+=item &store_course_settings
+
+Restores/Store indicated form parameters from the course environment.
+Will not overwrite existing values of the form parameters.
+
+Inputs: 
+a scalar describing the data (e.g. 'chart', 'problem_analysis')
+
+a hash ref describing the data to be stored.  For example:
+   
+%Save_Parameters = ('Status' => 'scalar',
+    'chartoutputmode' => 'scalar',
+    'chartoutputdata' => 'scalar',
+    'Section' => 'array',
+    'StudentData' => 'array',
+    'Maps' => 'array');
+
+Returns: both routines return nothing
+
+=cut
+
+#######################################################
+#######################################################
+sub store_course_settings {
+    # save to the environment
+    # appenv the same items, just to be safe
+    my $courseid = $env{'request.course.id'};
+    my $udom  = $env{'user.domain'};
+    my $uname = $env{'user.name'};
+    my ($prefix,$Settings) = @_;
+    my %SaveHash;
+    my %AppHash;
+    while (my ($setting,$type) = each(%$Settings)) {
+        my $basename = join('.','internal',$courseid,$prefix,$setting);
+        my $envname = 'environment.'.$basename;
+        if (exists($env{'form.'.$setting})) {
+            # Save this value away
+            if ($type eq 'scalar' &&
+                (! exists($env{$envname}) || 
+                 $env{$envname} ne $env{'form.'.$setting})) {
+                $SaveHash{$basename} = $env{'form.'.$setting};
+                $AppHash{$envname}   = $env{'form.'.$setting};
+            } elsif ($type eq 'array') {
+                my $stored_form;
+                if (ref($env{'form.'.$setting})) {
+                    $stored_form = join(',',
+                                        map {
+                                            &Apache::lonnet::escape($_);
+                                        } sort(@{$env{'form.'.$setting}}));
+                } else {
+                    $stored_form = 
+                        &Apache::lonnet::escape($env{'form.'.$setting});
+                }
+                # Determine if the array contents are the same.
+                if ($stored_form ne $env{$envname}) {
+                    $SaveHash{$basename} = $stored_form;
+                    $AppHash{$envname}   = $stored_form;
+                }
+            }
+        }
+    }
+    my $put_result = &Apache::lonnet::put('environment',\%SaveHash,
+                                          $udom,$uname);
+    if ($put_result !~ /^(ok|delayed)/) {
+        &Apache::lonnet::logthis('unable to save form parameters, '.
+                                 'got error:'.$put_result);
+    }
+    # Make sure these settings stick around in this session, too
+    &Apache::lonnet::appenv(%AppHash);
+    return;
+}
+
+sub restore_course_settings {
+    my $courseid = $env{'request.course.id'};
+    my ($prefix,$Settings) = @_;
+    while (my ($setting,$type) = each(%$Settings)) {
+        next if (exists($env{'form.'.$setting}));
+        my $envname = 'environment.internal.'.$courseid.'.'.$prefix.
+            '.'.$setting;
+        if (exists($env{$envname})) {
+            if ($type eq 'scalar') {
+                $env{'form.'.$setting} = $env{$envname};
+            } elsif ($type eq 'array') {
+                $env{'form.'.$setting} = [ 
+                                           map { 
+                                               &Apache::lonnet::unescape($_); 
+                                           } split(',',$env{$envname})
+                                           ];
+            }
+        }
+    }
+}
+
+############################################################
+############################################################
+
+sub propath {
+    my ($udom,$uname)=@_;
+    $udom=~s/\W//g;
+    $uname=~s/\W//g;
+    my $subdir=$uname.'__';
+    $subdir =~ s/(.)(.)(.).*/$1\/$2\/$3/;
+    my $proname="$Apache::lonnet::perlvar{'lonUsersDir'}/$udom/$subdir/$uname";
+    return $proname;
+} 
+
+sub icon {
+    my ($file)=@_;
+    my $curfext = (split(/\./,$file))[-1];
+    my $iconname=$Apache::lonnet::perlvar{'lonIconsURL'}.'/unknown.gif';
+    my $embstyle = &Apache::loncommon::fileembstyle($curfext);
+    if (!(!defined($embstyle) || $embstyle eq 'unk' || $embstyle eq 'hdn')) {
+	if (-e  $Apache::lonnet::perlvar{'lonDocRoot'}.'/'.
+	          $Apache::lonnet::perlvar{'lonIconsURL'}.'/'.
+	            $curfext.".gif") {
+	    $iconname=$Apache::lonnet::perlvar{'lonIconsURL'}.'/'.
+		$curfext.".gif";
+	}
+    }
+    return &lonhttpdurl($iconname);
+} 
+
+sub lonhttpdurl {
+    my ($url)=@_;
+    my $lonhttpd_port=$Apache::lonnet::perlvar{'lonhttpdPort'};
+    if (!defined($lonhttpd_port)) { $lonhttpd_port='8080'; }
+    return 'http://'.$ENV{'SERVER_NAME'}.':'.$lonhttpd_port.$url;
+}
+
+sub connection_aborted {
+    my ($r)=@_;
+    $r->print(" ");$r->rflush();
+    my $c = $r->connection;
+    return $c->aborted();
+}
+
+#    Escapes strings that may have embedded 's that will be put into
+#    strings as 'strings'.
+sub escape_single {
+    my ($input) = @_;
+    $input =~ s/\\/\\\\/g;	# Escape the \'s..(must be first)>
+    $input =~ s/\'/\\\'/g;	# Esacpe the 's....
+    return $input;
+}
+
+#  Same as escape_single, but escape's "'s  This 
+#  can be used for  "strings"
+sub escape_double {
+    my ($input) = @_;
+    $input =~ s/\\/\\\\/g;	# Escape the /'s..(must be first)>
+    $input =~ s/\"/\\\"/g;	# Esacpe the "s....
+    return $input;
+}
+ 
+#   Escapes the last element of a full URL.
+sub escape_url {
+    my ($url)   = @_;
+    my @urlslices = split(/\//, $url,-1);
+    my $lastitem = &Apache::lonnet::escape(pop(@urlslices));
+    return join('/',@urlslices).'/'.$lastitem;
+}
+=pod
+
 =back
 
 =cut