--- loncom/interface/loncommon.pm	2003/08/20 18:18:45	1.112
+++ loncom/interface/loncommon.pm	2005/01/14 21:15:09	1.246
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.112 2003/08/20 18:18:45 bowersj2 Exp $
+# $Id: loncommon.pm,v 1.246 2005/01/14 21:15:09 matthew 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
@@ -66,19 +59,21 @@ use Apache::lonnet();
 use GDBM_File;
 use POSIX qw(strftime mktime);
 use Apache::Constants qw(:common :http :methods);
-use Apache::lonmsg();
 use Apache::lonmenu();
-my $readit;
-
-=pod 
+use Apache::lonlocal;
+use HTML::Entities;
 
-=head1 Global Variables
+my $readit;
 
-=cut
+##
+## Global Variables
+##
 
 # ----------------------------------------------- Filetypes/Languages/Copyright
 my %language;
+my %supported_language;
 my %cprtag;
+my %scprtag;
 my %fe; my %fd;
 my %category_extensions;
 
@@ -87,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";
@@ -138,29 +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)=(split(/\t/,$_));
-		$language{$key}=$val.' - '.$enc;
-	    }
-	}
+        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
@@ -171,15 +154,16 @@ BEGIN {
     while ($filename=readdir(DIR)) {
 	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);
+        }
     }
 
     }
@@ -188,32 +172,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>");
@@ -239,8 +226,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
@@ -249,10 +234,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]
 
@@ -261,43 +246,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';
@@ -305,9 +306,27 @@ 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'}) && 
@@ -346,20 +365,21 @@ sub selectstudent_link {
 	   return '';
        }
        return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele.
-        '","'.$udomele.'");'."'>Select User</a>";
+        '","'.$udomele.'");'."'>".&mt('Select User')."</a>";
    }
    if ($ENV{'request.role'}=~/^(au|dc|su)/) {
        return "<a href='".'javascript:openstdbrowser("'.$form.'","'.$unameele.
-        '","'.$udomele.'",1);'."'>Select User</a>";
+        '","'.$udomele.'",1);'."'>".&mt('Select User')."</a>";
    }
    return '';
 }
 
 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) {
         var url = '/adm/pickcourse?';
         var filter;
         if (filter != null) {
@@ -367,8 +387,21 @@ 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;
+            }
+        }
         var title = 'Course_Browser';
         var options = 'scrollbars=1,resizable=1,menubar=0';
         options += ',width=700,height=600';
@@ -380,9 +413,9 @@ ENDSTDBRW
 }
 
 sub selectcourse_link {
-   my ($form,$unameele,$udomele)=@_;
+   my ($form,$unameele,$udomele,$desc,$extra_element)=@_;
     return "<a href='".'javascript:opencrsbrowser("'.$form.'","'.$unameele.
-        '","'.$udomele.'");'."'>Select Course</a>";
+        '","'.$udomele.'","'.$desc.'","'.$extra_element.'");'."'>".&mt('Select Course')."</a>";
 }
 
 =pod
@@ -465,7 +498,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 = '';
@@ -501,6 +534,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;
@@ -514,7 +548,7 @@ END
     foreach my $value (sort(keys(%$hashref))) {
         $result.="    <option value=\"$value\" ";
         $result.=" selected=\"true\" " if ($value eq $firstdefault);
-        $result.=">$hashref->{$value}->{'text'}</option>\n";
+        $result.=">".&mt($hashref->{$value}->{'text'})."</option>\n";
     }
     $result .= "</select>\n";
     my %select2 = %{$hashref->{$firstdefault}->{'select2'}};
@@ -524,7 +558,7 @@ END
     foreach my $value (sort(keys(%select2))) {
         $result.="    <option value=\"$value\" ";        
         $result.=" selected=\"true\" " if ($value eq $seconddefault);
-        $result.=">$select2{$value}</option>\n";
+        $result.=">".&mt($select2{$value})."</option>\n";
     }
     $result .= "</select>\n";
     #    return $debug;
@@ -571,6 +605,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'))";
@@ -589,8 +625,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;
@@ -617,24 +655,339 @@ sub helpLatexCheatsheet {
 	.'</td></tr></table>';
 }
 
+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='#773311' cellspacing='1' cellpadding='1' border='0'><tr>".
+  "<td bgcolor='#886622'><a href=\"$link\"><font color='#FFFFFF' size='2'>$text</font></a>";
+    }
+    my $helpicon=&lonhttpdurl("/adm/lonIcons/helpgateway.gif");
+    $template .= <<"ENDTEMPLATE";
+ <script type="text/javascript">
+//<!-- BEGIN LON-CAPA Internal
+function helpMenu(target) {
+    var caller = this;
+    if (target == 'open') {
+        var newWindow = null;
+        try {
+            newWindow =  window.open("","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.write("<html><head><title>LON-CAPA Help Menu</title><meta http-equiv='pragma' content='no-cache'></head>")
+    caller.document.write("<frameset rows='105,*' border='0'><frame name='bannerframe'  src='$banner_link'><frame name='bodyframe' src='$details_link'></frameset>")
+    caller.document.write("</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;
+}
+
+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);
+
+    $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>";
+    }
+
+    # 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 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 * csv_translate($text) 
 
-Translate $text to allow it to be output as a 'comma seperated values' 
+Translate $text to allow it to be output as a 'comma separated values' 
 format.
 
-=back
-
 =cut
 
+###############################################################
+###############################################################
 sub csv_translate {
     my $text = shift;
     $text =~ s/\"/\"\"/g;
-    $text =~ s/\n//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 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->{'i'}    = $workbook->add_format(italic=>1);
+    $format->{'date'} = $workbook->add_format(num_format=>
+                                            'mm/dd/yyyy hh:mm:ss');
+    return $format;
+}
+
+###############################################################
+###############################################################
+
+=pod
+
+=item * change_content_javascript():
+
+This and the next function allow you to create small sections of an
+otherwise static HTML page that you can update on the fly with
+Javascript, even in Netscape 4.
+
+The Javascript fragment returned by this function (no E<lt>scriptE<gt> tag)
+must be written to the HTML page once. It will prove the Javascript
+function "change(name, content)". Calling the change function with the
+name of the section 
+you want to update, matching the name passed to C<changable_area>, and
+the new content you want to put in there, will put the content into
+that area.
+
+B<Note>: Netscape 4 only reserves enough space for the changable area
+to contain room for the original contents. You need to "make space"
+for whatever changes you wish to make, and be B<sure> to check your
+code in Netscape 4. This feature in Netscape 4 is B<not> powerful;
+it's adequate for updating a one-line status display, but little more.
+This script will set the space to 100% width, so you only need to
+worry about height in Netscape 4.
+
+Modern browsers are much less limiting, and if you can commit to the
+user not using Netscape 4, this feature may be used freely with
+pretty much any HTML.
+
+=cut
+
+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\./) {
+	return (<<NETSCAPE4);
+	function change(name, content) {
+	    doc = document.layers[name+"___escape"].layers[0].document;
+	    doc.open();
+	    doc.write(content);
+	    doc.close();
+	}
+NETSCAPE4
+    } else {
+	# Otherwise, we need to use semi-standards-compliant code
+	# (technically, "innerHTML" isn't standard but the equivalent
+	# is really scary, and every useful browser supports it
+	return (<<DOMBASED);
+	function change(name, content) {
+	    element = document.getElementById(name);
+	    element.innerHTML = content;
+	}
+DOMBASED
+    }
+}
+
+=pod
+
+=item * changable_area($name, $origContent):
+
+This provides a "changable area" that can be modified on the fly via
+the Javascript code provided in C<change_content_javascript>. $name is
+the name you will use to reference the area later; do not repeat the
+same name on a given HTML page more then once. $origContent is what
+the area will originally contain, which can be left blank.
+
+=cut
+
+sub changable_area {
+    my ($name, $origContent) = @_;
+
+    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 {
+	return "<span id='$name'>$origContent</span>";
+    }
+}
+
+=pod
+
+=back
+
+=cut
+
 ###############################################################
 ##        Home server <option> list generating code          ##
 ###############################################################
@@ -658,11 +1011,46 @@ 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);
+    }
+}
+
+sub multiple_select_form {
+    my ($name,$value,$size,%hash)=@_;
+    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'>";
+    foreach (sort(keys(%hash))) {
+        $output.='<option value="'.$_.'" ';
+        $output.='selected ' if ($selected{$_});
+        $output.='>'.$hash{$_}."</option>\n";
+    }
+    $output.="</select>\n";
+    return $output;
+}
+
 #-------------------------------------------
 
 =pod
@@ -679,15 +1067,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' : '').
-                ">".$hash{$_}."</option>\n";
+                ">".&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' : '').
+                ">".&gradeleveldescription($i)."</option>\n";
+    }
+    $selectform.="</select>";
+    return $selectform;
+}
 
 #-------------------------------------------
 
@@ -804,6 +1234,8 @@ Outputs:
 
 =back
 
+=back 
+
 =cut
 
 ###############################################################
@@ -842,12 +1274,6 @@ sub decode_user_agent {
             $clientunicode,$clientos,);
 }
 
-=pod
-
-=back
-
-=cut
-
 ###############################################################
 ##    Authentication changing form generation subroutines    ##
 ###############################################################
@@ -888,6 +1314,8 @@ See loncreateuser.pm for invocation and
 
 =back
 
+=back 
+
 =cut
 
 #-------------------------------------------
@@ -915,10 +1343,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';
@@ -978,10 +1426,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;
 }
 
@@ -991,12 +1439,10 @@ 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 = &mt('[_1] Do not change login data',
+                     '<input type="radio" name="login" value="nochange" '.
+                     'checked="checked" onclick="'.
+            "javascript:changed_radio('nochange',$in{'formname'});".'" />');
     return $result;
 }
 
@@ -1007,24 +1453,35 @@ 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',
+         '<input type="radio" name="login" value="krb" '.
+             'onclick="'.$jscall.'" onchange="'.$jscall.'"'.$krbcheck.' />',
+         '<input type="text" size="10" name="krbarg" '.
+             'value="'.$krbarg.'" '.
+             'onchange="'.$jscall.'" />',
+         '<input type="radio" name="krbver" value="4" '.$check4.' />',
+         '<input type="radio" name="krbver" value="5" '.$check5.' />');
     return $result;
 }
 
@@ -1034,15 +1491,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])',
+         '<input type="radio" name="login" value="int" '.$intcheck.
+             ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+         '<input type="text" size="10" name="intarg" '.$intarg.
+             ' onchange="'.$jscall.'" />');
     return $result;
 }
 
@@ -1052,15 +1519,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]',
+                    '<input type="radio" name="login" value="loc" '.$loccheck.
+                        ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+                    '<input type="text" size="10" name="locarg" '.$locarg.
+                        ' onchange="'.$jscall.'" />');
     return $result;
 }
 
@@ -1070,24 +1546,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])',
+         '<input type="radio" name="login" value="fsys" '.
+         'onchange="'.$jscall.'" onclick="'.$jscall.'" />',
+         '<input type="text" size="10" name="fsysarg" value="" '.
+                  'onchange="'.$jscall.'" />');
     return $result;
 }
 
-=pod
-
-=back
-
-=cut
-
 ###############################################################
 ##    Get Authentication Defaults for Domain                 ##
 ###############################################################
@@ -1242,7 +1710,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.
@@ -1290,23 +1758,29 @@ 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 ($uname,$udom,$first)=@_;
     my %names=&Apache::lonnet::get('environment',
                     ['firstname','middlename','lastname','generation'],
 					 $udom,$uname);
-    my $name=$names{'firstname'}.' '.$names{'middlename'}.' '.
-	$names{'lastname'}.' '.$names{'generation'};
+    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;
 }
 
@@ -1329,8 +1803,19 @@ 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;
+    if ($uname eq $ENV{'user.name'} &&
+	$udom eq $ENV{'user.domain'}) {
+	%names=('nickname'   => $ENV{'environment.nickname'}  ,
+		'firstname'  => $ENV{'environment.firstname'} ,
+		'middlename' => $ENV{'environment.middlename'},
+		'lastname'   => $ENV{'environment.lastname'}  ,
+		'generation' => $ENV{'environment.generation'});
+    } else {
+	%names=&Apache::lonnet::get('environment',
+				    ['nickname','firstname','middlename',
+				     'lastname','generation'],$udom,$uname);
+    }
     my $name=$names{'nickname'};
     if ($name) {
        $name='&quot;'.$name.'&quot;'; 
@@ -1356,17 +1841,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
 
@@ -1378,8 +1867,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
@@ -1390,9 +1880,28 @@ 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) = @_;
+    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 = '';
+    }
+    return qq{<a href="$link" title="$title" $target>$linktext</a>};
+}
+
+
+
 =pod
 
 =back
@@ -1420,7 +1929,20 @@ returns description of a specified langu
 =cut
 
 sub languagedescription {
-    return $language{shift(@_)};
+    my $code=shift;
+    return  ($supported_language{$code}?'* ':'').
+            $language{$code}.
+	    ($supported_language{$code}?' ('.&mt('interface available').')':'');
+}
+
+sub plainlanguagedescription {
+    my $code=shift;
+    return $language{$code};
+}
+
+sub supportedlanguagecode {
+    my $code=shift;
+    return $supported_language{$code};
 }
 
 =pod
@@ -1444,7 +1966,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
@@ -1484,6 +2030,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() 
@@ -1493,7 +2047,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
@@ -1507,7 +2063,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
@@ -1528,16 +2086,8 @@ sub fileextensions {
 
 sub display_languages {
     my %languages=();
-    if ($ENV{'environment.languages'}) {
-	foreach (split(/\s*(\,|\;|\:)\s*/,$ENV{'environment.languages'})) {
-	    $languages{$_}=1;
-        }
-    }
-    if ($ENV{'course.'.$ENV{'request.course.id'}.'.languages'}) {
-	foreach (split(/\s*(\,|\;|\:)\s*/,
-	$ENV{'course.'.$ENV{'request.course.id'}.'.languages'})) {
-	    $languages{$_}=1;
-        }
+    foreach (&preferred_languages()) {
+	$languages{$_}=1;
     }
     &get_unprocessed_cgi($ENV{'QUERY_STRING'},['displaylanguage']);
     if ($ENV{'form.displaylanguage'}) {
@@ -1548,6 +2098,45 @@ sub display_languages {
     return %languages;
 }
 
+sub preferred_languages {
+    my @languages=();
+    if ($ENV{'course.'.$ENV{'request.course.id'}.'.languages'}) {
+	@languages=(@languages,split(/\s*(\,|\;|\:)\s*/,
+	         $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'}}) {
+	@languages=(@languages,
+		$Apache::lonnet::domain_lang_def{$ENV{'user.domain'}});
+    }
+    if ($Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}}) {
+	@languages=(@languages,
+		$Apache::lonnet::domain_lang_def{$ENV{'request.role.domain'}});
+    }
+    if ($Apache::lonnet::domain_lang_def{
+	                          $Apache::lonnet::perlvar{'lonDefDomain'}}) {
+	@languages=(@languages,
+		$Apache::lonnet::domain_lang_def{
+                                  $Apache::lonnet::perlvar{'lonDefDomain'}});
+    }
+# turn "en-ca" into "en-ca,en"
+    my @genlanguages;
+    foreach (@languages) {
+	unless ($_=~/\w/) { next; }
+	push (@genlanguages,$_);
+	if ($_=~/(\-|\_)/) {
+	    push (@genlanguages,(split(/(\-|\_)/,$_))[0]);
+	}
+    }
+    return @genlanguages;
+}
+
 ###############################################################
 ##               Student Answer Attempts                     ##
 ###############################################################
@@ -1628,7 +2217,7 @@ sub get_previous_attempt {
 	       } else {
 		  $value=$returnhash{$version.':'.$_};
 	       }
-	       $prevattempts.='<td>'.$value.'&nbsp;</td>';   
+	       $prevattempts.='<td>'.&Apache::lonnet::unescape($value).'&nbsp;</td>';   
 	    }
 	 }
       }
@@ -1640,6 +2229,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>';
       }
@@ -1696,22 +2286,19 @@ show a snapshot of what student was look
 =cut
 
 sub get_student_view {
-  my ($symb,$username,$domain,$courseid,$target) = @_;
-  my ($map,$id,$feedurl) = split(/___/,$symb);
-  my (%old,%moreenv);
+  my ($symb,$username,$domain,$courseid,$target,$moreenv) = @_;
+  my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb);
+  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;
@@ -1733,25 +2320,93 @@ show a snapshot of how student was answe
 
 sub get_student_answers {
   my ($symb,$username,$domain,$courseid,%form) = @_;
-  my ($map,$id,$feedurl) = split(/___/,$symb);
-  my (%old,%moreenv);
+  my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb);
+  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;
 }
 
 =pod
 
+=item * &submlink()
+
+Inputs: $text $uname $udom $symb $target
+
+Returns: A link to grades.pm such as to see the SUBM view of a student
+
+=cut
+
+###############################################
+sub submlink {
+    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(); }
+    $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 andn 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(); }
+    $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>';
+}
+##############################################
+
+=pod
+
 =back
 
 =cut
@@ -1776,22 +2431,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));
 }
 
 #########################################
@@ -1862,10 +2502,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 {
@@ -1951,20 +2589,9 @@ other decorations will be returned.
 =cut
 
 sub bodytag {
-    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg)=@_;
-    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';
-        }
-    }
+    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,$customtitle)=@_;
+    $title=&mt($title);
+    $function = &get_users_function() if (!$function);
     my $img=&designparm($function.'.img',$domain);
     my $pgbg=&designparm($function.'.pgbg',$domain);
     my $tabbg=&designparm($function.'.tabbg',$domain);
@@ -1975,8 +2602,9 @@ sub bodytag {
     my $sidebg=&designparm($function.'.sidebg',$domain);
 # Accessibility font enhance
     unless ($addentries) { $addentries=''; }
+    my $addstyle='';
     if ($ENV{'browser.fontenhance'} eq 'on') {
-	$addentries.=' style="font-size: x-large"';
+	$addstyle=' font-size: x-large;';
     }
  # role and realm
     my ($role,$realm)
@@ -1994,29 +2622,102 @@ 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 } 
+</style>
 <body bgcolor="$pgbg" text="$font" alink="$alink" vlink="$vlink" link="$link"
-$addentries>
+style="margin-top: 0px;$addstyle" $addentries>
 END
     my $upperleft='<img src="http://'.$ENV{'HTTP_HOST'}.':'.
-                   $lonhttpdPort.$img.'" />';
+                   $lonhttpdPort.$img.'" alt="'.$function.'" />';
     if ($bodyonly) {
         return $bodytag;
     } 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') {
 # 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 rowspan="3" bgcolor="'.$tabbg.'">'.
+                         $titleinfo.'</td>'.$roleinfo.'</tr></table>';
+        if ($ENV{'request.state'} eq 'construct') {
+            $bodytag .= &Apache::lonmenu::menubuttons($forcereg,'web',$forcereg,$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.')';
+    }
+    #
     return(<<ENDBODY);
 $bodytag
 <table width="100%" cellspacing="0" border="0" cellpadding="0">
@@ -2026,9 +2727,9 @@ $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">
+$titleinfo $dc_info
+<td bgcolor="$tabbg" align="right">
+<font size="2" face="Arial, Helvetica, sans-serif">
     $ENV{'environment.firstname'}
     $ENV{'environment.middlename'}
     $ENV{'environment.lastname'}
@@ -2037,16 +2738,98 @@ $upperleft</td>
 </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
+
+=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 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 $cid = $cdom.'_'.$cnum;
+    my $numsections = 0;
+
+    if (!defined($possible_roles) || (grep/^st$/,@$possible_roles)) {
+	my ($classlist) = &Apache::loncoursedata::get_classlist($cid,$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;
+}
+
+
 sub get_posted_cgi {
     my $r=shift;
 
@@ -2155,12 +2938,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
@@ -2172,12 +2955,26 @@ 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;
+    }
+    if ($charset) { $type.='; charset='.$charset; }
+    if ($r) {
+	$r->content_type($type);
+    } else {
+	print("Content-type: $type\n\n");
+    }
 }
 
 =pod
@@ -2209,6 +3006,32 @@ sub add_to_env {
 
 =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
@@ -2233,9 +3056,12 @@ sub upfile_store {
     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;
 }
@@ -2254,11 +3080,12 @@ 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);
 }
@@ -2303,7 +3130,7 @@ sub record_sep {
         }
     } elsif ($ENV{'form.upfiletype'} eq 'tab') {
         my $i=0;
-        foreach (split(/\t+/,$record)) {
+        foreach (split(/\t/,$record)) {
             my $field=$_;
             $field=~s/^(\"|\')//;
             $field=~s/(\"|\')$//;
@@ -2332,26 +3159,39 @@ sub record_sep {
     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;
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * csv_print_samples($r,$records)
@@ -2362,15 +3202,18 @@ 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]);}
-
-    $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(%sone))) { 
+        $r->print('<th>'.&mt('Column&nbsp;[_1]',($_+1)).'</th>'); }
     $r->print('</tr>');
     foreach my $hash (\%sone,\%stwo,\%sthree) {
 	$r->print('<tr>');
@@ -2384,32 +3227,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");
+    $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>');
+	    $r->print('<option value="'.$_.'"'.
+                      ($_ eq $defaultcol ? ' selected ' : '').
+                      '>Column '.($_+1).'</option>');
 	}
 	$r->print('</select></td></tr>'."\n");
 	$i++;
@@ -2418,6 +3271,9 @@ sub csv_print_select_table {
     return $i;
 }
 
+######################################################
+######################################################
+
 =pod
 
 =item * csv_samples_select_table($r,$records,$d)
@@ -2430,22 +3286,27 @@ $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;
-
-    $r->print('<table border=2><tr><th>Field</th><th>Samples</th></tr>');
+    #
+    $r->print('<table border=2><tr><th>'.
+              &mt('Field').'</th><th>'.&mt('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.
+	$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>');
+	    my ($value,$display,$defaultcol)=@{ $_ };
+	    $r->print('<option value="'.$value.'"'.
+                      ($i eq $defaultcol ? ' selected ':'').'>'.
+                      $display.'</option>');
 	}
 	$r->print('</select></td><td>');
 	if (defined($sone{$_})) { $r->print($sone{$_}."</br>\n"); }
@@ -2458,6 +3319,28 @@ sub csv_samples_select_table {
     return($i);
 }
 
+######################################################
+######################################################
+
+=pod
+
+=item clean_excel_name($name)
+
+Returns a replacement for $name which does not contain any illegal characters.
+
+=cut
+
+######################################################
+######################################################
+sub clean_excel_name {
+    my ($name) = @_;
+    $name =~ s/[:\*\?\/\\]//g;
+    if (length($name) > 31) {
+        $name = substr($name,0,31);
+    }
+    return $name;
+}
+
 =pod
 
 =item * check_if_partid_hidden($id,$symb,$udom,$uname)
@@ -2477,15 +3360,600 @@ $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 !$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);
+}
+
+############################################################
+############################################################
+
+=pod
+
+=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.
+
+=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 DrawBarGraph {
+    my ($Title,$xlabel,$ylabel,$Max,$colors,$labels,@Values)=@_;
+    #
+    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 '';
+    }
+    #
+    my @Labels;
+    if (defined($labels)) {
+        @Labels = @$labels;
+    } else {
+        for (my $i=0;$i<@{$Values[0]};$i++) {
+            push (@Labels,$i+1);
+        }
+    }
+    #
+    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;
     }
-    return undef;
+    #
+    $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(%ValuesHash);
+    return '<img src="/cgi-bin/graph.png?'.$identifier.'" border="1" />';
 }
 
+############################################################
+############################################################
+
+=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 $coursedom = $ENV{'course.'.$courseid.'.domain'};
+    my ($prefix,$Settings) = @_;
+    my %SaveHash;
+    my %AppHash;
+    while (my ($setting,$type) = each(%$Settings)) {
+        my $basename = 'internal.'.$prefix.'.'.$setting;
+        my $envname = 'course.'.$courseid.'.'.$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,
+                                          $coursedom,
+                                          $ENV{'course.'.$courseid.'.num'});
+    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 = 'course.'.$courseid.'.internal.'.$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 $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