--- loncom/interface/loncommon.pm	2004/11/13 21:06:27	1.231
+++ loncom/interface/loncommon.pm	2006/06/26 22:31:56	1.401
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.231 2004/11/13 21:06:27 albertel Exp $
+# $Id: loncommon.pm,v 1.401 2006/06/26 22:31:56 albertel Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -55,13 +55,16 @@ 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::lonmenu();
 use Apache::lonlocal;
 use HTML::Entities;
+use Apache::lonhtmlcommon();
+use Apache::loncoursedata();
+use Apache::lontexconvert();
+use LONCAPA;
 
 my $readit;
 
@@ -74,7 +77,7 @@ my %language;
 my %supported_language;
 my %cprtag;
 my %scprtag;
-my %fe; my %fd;
+my %fe; my %fd; my %fm;
 my %category_extensions;
 
 # ---------------------------------------------- Designs
@@ -105,10 +108,10 @@ BEGIN {
         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/,$_));
+            while (my $line = <$fh>) {
+                next if ($line=~/^\#/);
+                chomp($line);
+                my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$line));
                 $language{$key}=$val.' - '.$enc;
                 if ($sup) {
                     $supported_language{$key}=$sup;
@@ -122,24 +125,24 @@ BEGIN {
         my $copyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}.
                                   '/copyright.tab';
         if ( open (my $fh,"<$copyrightfile") ) {
-            while (<$fh>) {
-                next if /^\#/;
-                chomp;
-                my ($key,$val)=(split(/\s+/,$_,2));
+            while (my $line = <$fh>) {
+                next if ($line=~/^\#/);
+                chomp($line);
+                my ($key,$val)=(split(/\s+/,$line,2));
                 $cprtag{$key}=$val;
             }
             close($fh);
         }
     }
-# ------------------------------------------------------------------ source copyrights
+# ----------------------------------------------------------- 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));
+            while (my $line = <$fh>) {
+                next if ($line =~ /^\#/);
+                chomp($line);
+                my ($key,$val)=(split(/\s+/,$line,2));
                 $scprtag{$key}=$val;
             }
             close($fh);
@@ -152,19 +155,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 $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);
-        }
-    }
+	{
+	    my $designfile = $designdir.'/'.$filename;
+	    if ( open (my $fh,"<$designfile") ) {
+		while (my $line = <$fh>) {
+		    next if ($line =~ /^\#/);
+		    chomp($line);
+		    my ($key,$val)=(split(/\=/,$line));
+		    if ($val) { $designhash{$domain.'.'.$key}=$val; }
+		}
+		close($fh);
+	    }
+	}
 
     }
     closedir(DIR);
@@ -175,10 +179,10 @@ BEGIN {
         my $categoryfile = $Apache::lonnet::perlvar{'lonTabDir'}.
                                   '/filecategories.tab';
         if ( open (my $fh,"<$categoryfile") ) {
-            while (<$fh>) {
-                next if /^\#/;
-                chomp;
-                my ($extension,$category)=(split(/\s+/,$_,2));
+	    while (my $line = <$fh>) {
+		next if ($line =~ /^\#/);
+		chomp($line);
+                my ($extension,$category)=(split(/\s+/,$line,2));
                 push @{$category_extensions{lc($category)}},$extension;
             }
             close($fh);
@@ -190,13 +194,14 @@ BEGIN {
         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);
+            while (my $line = <$fh>) {
+		next if ($line =~ /^\#/);
+		chomp($line);
+                my ($ending,$emb,$mime,$descr)=split(/\s+/,$line,4);
                 if ($descr ne '') {
                     $fe{$ending}=lc($emb);
                     $fd{$ending}=$descr;
+                    if ($mime ne 'unk') { $fm{$ending}=$mime; }
                 }
             }
             close($fh);
@@ -311,8 +316,8 @@ END
 }
 
 sub lastresurl {
-    if ($ENV{'environment.lastresurl'}) {
-	return $ENV{'environment.lastresurl'}
+    if ($env{'environment.lastresurl'}) {
+	return $env{'environment.lastresurl'}
     } else {
 	return '/res';
     }
@@ -329,9 +334,12 @@ sub storeresurl {
 
 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" >
@@ -360,14 +368,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>";
    }
@@ -375,12 +385,12 @@ sub selectstudent_link {
 }
 
 sub coursebrowser_javascript {
-    my ($domainfilter,$roleelement)=@_;
+    my ($domainfilter)=@_;
+    my $crs_or_grp_alert = &mt('Please select the type of LON-CAPA entity - Course or Group - for which you wish to add/modify a user role');
    return (<<ENDSTDBRW);
 <script type="text/javascript" language="Javascript" >
-    var extra_element = "$roleelement" 
     var stdeditbrowser;
-    function opencrsbrowser(formname,uname,udom,desc) {
+    function opencrsbrowser(formname,uname,udom,desc,extra_element,multflag,crstype) {
         var url = '/adm/pickcourse?';
         var filter;
         if (filter != null) {
@@ -397,8 +407,26 @@ sub coursebrowser_javascript {
         url += 'form=' + formname + '&cnumelement='+uname+
 	                            '&cdomelement='+udom+
                                     '&cnameelement='+desc;
-        if (extra_element != '') {
-            url += '&roleelement=$roleelement';
+        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;
+        }
+        if (crstype == 'Course/Group') {
+            if (formname == 'cu') {
+                crstype = document.cu.crstype.options[document.cu.crstype.selectedIndex].value; 
+                if (crstype == "") {
+                    alert("$crs_or_grp_alert");
+                    return;
+                }
+            }
+        }
+        if (crstype !=null && crstype != '') {
+            url += '&type='+crstype;
         }
         var title = 'Course_Browser';
         var options = 'scrollbars=1,resizable=1,menubar=0';
@@ -411,10 +439,35 @@ ENDSTDBRW
 }
 
 sub selectcourse_link {
-   my ($form,$unameele,$udomele,$desc)=@_;
+   my ($form,$unameele,$udomele,$desc,$extra_element,$multflag,$selecttype)=@_;
     return "<a href='".'javascript:opencrsbrowser("'.$form.'","'.$unameele.
-        '","'.$udomele.'","'.$desc.'");'."'>".&mt('Select Course')."</a>";
+        '","'.$udomele.'","'.$desc.'","'.$extra_element.'","'.$multflag.'","'.$selecttype.'");'."'>".&mt('Select [_1]',$selecttype)."</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
 
@@ -545,7 +598,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";
@@ -555,7 +608,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";
@@ -591,8 +644,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);
@@ -654,11 +707,11 @@ sub helpLatexCheatsheet {
 }
 
 sub help_open_menu {
-    my ($color,$topic,$component_help,$function,$faq,$bug,$stayOnPage,$width,$height,$text) = @_;
+    my ($topic,$component_help,$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' ) {
+    if ($env{'browser.interface'} eq 'textual' ||
+        $env{'environment.remote'} eq 'off' ) {
         $stayOnPage=1;
     }
     $width = 620 if (not defined $width);
@@ -668,42 +721,63 @@ sub help_open_menu {
     my $origurl = $ENV{'REQUEST_URI'};
     $origurl=~s|^/~|/priv/|;
     my $timestamp = time;
-    foreach (\$color,\$function,\$topic,\$component_help,\$faq,\$bug,\$origurl) {
-        $$_ = &Apache::lonnet::escape($$_);
+    foreach my $datum (\$topic,\$component_help,\$faq,\$bug,\$origurl) {
+        $$datum = &escape($$datum);
     }
-
     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 $banner_link = "/adm/helpmenu?page=banner&amp;topic=$topic&amp;component_help=$component_help&amp;faq=$faq&amp;bug=$bug&amp;origurl=$origurl&amp;stamp=$timestamp&amp;stayonpage=$stayOnPage";
+    my $details_link = "/adm/helpmenu?page=body&amp;topic=$topic&amp;component_help=$component_help&amp;faq=$faq&amp;bug=$bug&amp;origurl=$origurl&amp;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>";
+  "<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 $helpicon=&lonhttpdurl("/adm/lonIcons/helpgateway.gif");
+    my $start_page =
+        &Apache::loncommon::start_page('Help Menu', undef,
+				       {'frameset'    => 1,
+					'js_ready'    => 1,
+					'add_entries' => {
+					    'border' => '0',
+					    'rows'   => "105,*",},});
+    my $end_page =
+        &Apache::loncommon::end_page({'frameset' => 1,
+				      'js_ready' => 1,});
+
     $template .= <<"ENDTEMPLATE";
  <script type="text/javascript">
-//<!-- BEGIN LON-CAPA Internal
-function helpMenu(caller) {
-    if (caller == 'open') {
-        newWindow =  window.open("","helpmenu","HEIGHT=$height,WIDTH=$width,resize=yes,scrollbars=yes" )
-        caller = newWindow.document
-    } else {
-        caller = this.document
-    }
-    caller.write("<html><head><title>LON-CAPA Help Menu</title><meta http-equiv='pragma' content='no-cache'></head>")
-    caller.write("<frameset rows='105,*' border='0'><frame name='bannerframe'  src='$banner_link'><frame name='bodyframe' src='$details_link'></frameset>")
-    caller.write("</html>")
-    caller.close()
-    if (caller == newWindow.document) {
-        caller.focus()
+// <!-- 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('$start_page<frame name="bannerframe"  src="$banner_link" /><frame name="bodyframe" src="$details_link" /> $end_page')
+    caller.document.close()
+    caller.focus()
+}
+// ]]>
 // END LON-CAPA Internal -->
  </script>
  <a href="$link" title="$title"><img src="$helpicon" border="0" alt="(Help Menu)" /></a>
@@ -714,7 +788,7 @@ ENDTEMPLATE
 				       $width,$height).' '.$template;
 	} else {
 	    my $help_text;
-	    $help_text=&Apache::lonnet::unescape($topic);
+	    $help_text=&unescape($topic);
 	    $template='<table><tr><td>'.
 		&help_open_topic($component_help,$help_text,$stayOnPage,
 				 $width,$height).'</td><td>'.$template.
@@ -727,12 +801,12 @@ ENDTEMPLATE
 
 sub help_open_bug {
     my ($topic, $text, $stayOnPage, $width, $height) = @_;
-    unless ($ENV{'user.adv'}) { return ''; }
+    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' ) {
+    if ($env{'browser.interface'} eq 'textual' ||
+	$env{'environment.remote'} eq 'off' ) {
 	$stayOnPage=1;
     }
     $width = 600 if (not defined $width);
@@ -741,8 +815,8 @@ sub help_open_bug {
     $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;
+    my $url=$Apache::lonnet::perlvar{'BugzillaHost'}.'enter_bug.cgi?product=LON-CAPA&amp;bug_file_loc='.
+	&escape($ENV{'REQUEST_URI'}).'&amp;component='.$topic;
     if (!$stayOnPage)
     {
 	$link = "javascript:void(open('$url', 'Bugzilla', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
@@ -772,12 +846,12 @@ ENDTEMPLATE
 
 sub help_open_faq {
     my ($topic, $text, $stayOnPage, $width, $height) = @_;
-    unless ($ENV{'user.adv'}) { return ''; }
+    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' ) {
+    if ($env{'browser.interface'} eq 'textual' ||
+	$env{'environment.remote'} eq 'off' ) {
 	$stayOnPage=1;
     }
     $width = 350 if (not defined $width);
@@ -820,6 +894,98 @@ ENDTEMPLATE
 
 =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
+
+=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' 
@@ -836,7 +1002,6 @@ sub csv_translate {
     return $text;
 }
 
-
 ###############################################################
 ###############################################################
 
@@ -860,6 +1025,10 @@ Currently supported formats:
 
 =item h3
 
+=item h4
+
+=item i
+
 =item date
 
 =back
@@ -882,6 +1051,8 @@ sub define_excel_formats {
     $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;
@@ -892,84 +1063,83 @@ sub define_excel_formats {
 
 =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.
+=item * create_workbook
 
-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.
+Create an Excel worksheet.  If it fails, output message on the
+request object and return undefs.
 
-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.
+Inputs: Apache request object
 
-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.
+Returns (undef) on failure, 
+    Excel worksheet object, scalar with filename, and formats 
+    from &Apache::loncommon::define_excel_formats on success
 
 =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
+###############################################################
+###############################################################
+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 * changable_area($name, $origContent):
+=item * create_text_file
 
-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.
+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.
 
-=cut
+Inputs: Apache request object, and file suffix
 
-sub changable_area {
-    my ($name, $origContent) = @_;
+Returns (undef) on failure, 
+    Filehandle and filename on success.
 
-    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>";
+=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
+
+=pod 
 
 =back
 
@@ -997,8 +1167,8 @@ sub get_domains {
     # The code below was stolen from "The Perl Cookbook", p 102, 1st ed.
     my @domains;
     my %seen;
-    foreach (sort values(%Apache::lonnet::hostdom)) {
-	push (@domains,$_) unless $seen{$_}++;
+    foreach my $dom (sort(values(%Apache::lonnet::hostdom))) {
+	push(@domains,$dom) unless $seen{$dom}++;
     }
     return @domains;
 }
@@ -1012,27 +1182,49 @@ sub domain_select {
     } &get_domains;
     if ($multiple) {
 	$domains{''}=&mt('Any domain');
-	return &multiple_select_form($name,$value,4,%domains);
+	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)=@_;
+    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));
+        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";
+    my @order = ref($order) ? @$order
+                            : sort(keys(%$hash));
+    foreach my $key (@order) {
+        $output.='<option value="'.&HTML::Entities::encode($key,'"<>&').'" ';
+        $output.='selected="selected" ' if ($selected{$key});
+        $output.='>'.$hash->{$key}."</option>\n";
     }
     $output.="</select>\n";
     return $output;
@@ -1060,10 +1252,11 @@ sub select_form {
     } else {
 	@keys=sort(keys(%hash));
     }
-    foreach (@keys) {
-        $selectform.="<option value=\"$_\" ".
-            ($_ eq $def ? 'selected' : '').
-                ">".&mt($hash{$_})."</option>\n";
+    foreach my $key (@keys) {
+        $selectform.=
+	    '<option value="'.&HTML::Entities::encode($key,'"<>&').'" '.
+            ($key eq $def ? 'selected="selected" ' : '').
+                ">".&mt($hash{$key})."</option>\n";
     }
     $selectform.="</select>";
     return $selectform;
@@ -1099,7 +1292,7 @@ sub select_level_form {
     my $selectform = "<select name=\"$name\" size=\"1\">\n";
     for (my $i=0; $i<=18; $i++) {
         $selectform.="<option value=\"$i\" ".
-            ($i==$deflevel ? 'selected' : '').
+            ($i==$deflevel ? 'selected="selected" ' : '').
                 ">".&gradeleveldescription($i)."</option>\n";
     }
     $selectform.="</select>";
@@ -1127,10 +1320,10 @@ sub select_dom_form {
     my @domains = get_domains();
     if ($includeempty) { @domains=('',@domains); }
     my $selectdomain = "<select name=\"$name\" size=\"1\">\n";
-    foreach (@domains) {
-        $selectdomain.="<option value=\"$_\" ".
-            ($_ eq $defdom ? 'selected' : '').
-                ">$_</option>\n";
+    foreach my $dom (@domains) {
+        $selectdomain.="<option value=\"$dom\" ".
+            ($dom eq $defdom ? 'selected="selected" ' : '').
+                ">$dom</option>\n";
     }
     $selectdomain.="</select>";
     return $selectdomain;
@@ -1152,9 +1345,9 @@ given $domain.
 sub get_library_servers {
     my $domain = shift;
     my %library_servers;
-    foreach (keys(%Apache::lonnet::libserv)) {
-        if ($Apache::lonnet::hostdom{$_} eq $domain) {
-            $library_servers{$_} = $Apache::lonnet::hostname{$_};
+    foreach my $hostid (keys(%Apache::lonnet::libserv)) {
+        if ($Apache::lonnet::hostdom{$hostid} eq $domain) {
+            $library_servers{$hostid} = $Apache::lonnet::hostname{$hostid};
         }
     }
     return %library_servers;
@@ -1176,9 +1369,10 @@ sub home_server_option_list {
     my $domain = shift;
     my %servers = &get_library_servers($domain);
     my $result = '';
-    foreach (sort keys(%servers)) {
+    foreach my $hostid (sort(keys(%servers))) {
         $result.=
-            '<option value="'.$_.'">'.$_.' '.$servers{$_}."</option>\n";
+            '<option value="'.$hostid.'">'.
+	    $hostid.' '.$servers{$hostid}."</option>\n";
     }
     return $result;
 }
@@ -1228,9 +1422,11 @@ Outputs:
 ###############################################################
 ###############################################################
 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='';
@@ -1426,10 +1622,11 @@ sub authform_nochange{
               kerb_def_dom => 'MSU.EDU',
               @_,
           );
-    my $result = &mt('[_1] Do not change login data',
+    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'});".'" />');
+            "javascript:changed_radio('nochange',$in{'formname'});".'" />').
+	    '</label>';
     return $result;
 }
 
@@ -1461,14 +1658,15 @@ sub authform_kerberos{
     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" '.
+         '[_3] Version 4 [_4] Version 5 [_5]',
+         '<label><input type="radio" name="login" value="krb" '.
              'onclick="'.$jscall.'" onchange="'.$jscall.'"'.$krbcheck.' />',
-         '<input type="text" size="10" name="krbarg" '.
+         '</label><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.' />');
+         '<label><input type="radio" name="krbver" value="4" '.$check4.' />',
+         '</label><label><input type="radio" name="krbver" value="5" '.$check5.' />',
+	 '</label>');
     return $result;
 }
 
@@ -1493,9 +1691,9 @@ sub authform_internal{
     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.
+         '<label><input type="radio" name="login" value="int" '.$intcheck.
              ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
-         '<input type="text" size="10" name="intarg" '.$intarg.
+         '</label><input type="text" size="10" name="intarg" '.$intarg.
              ' onchange="'.$jscall.'" />');
     return $result;
 }
@@ -1520,9 +1718,9 @@ sub authform_local{
 
     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.
+                    '<label><input type="radio" name="login" value="loc" '.$loccheck.
                         ' onchange="'.$jscall.'" onclick="'.$jscall.'" />',
-                    '<input type="text" size="10" name="locarg" '.$locarg.
+                    '</label><input type="text" size="10" name="locarg" '.$locarg.
                         ' onchange="'.$jscall.'" />');
     return $result;
 }
@@ -1536,9 +1734,9 @@ sub authform_filesystem{
     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" '.
+         '<label><input type="radio" name="login" value="fsys" '.
          'onchange="'.$jscall.'" onclick="'.$jscall.'" />',
-         '<input type="text" size="10" name="fsysarg" value="" '.
+         '</label><input type="text" size="10" name="fsysarg" value="" '.
                   'onchange="'.$jscall.'" />');
     return $result;
 }
@@ -1664,8 +1862,8 @@ sub initialize_keywords {
     }
     untie %thesaurus_db;
     # Remove special values from %Keywords.
-    foreach ('total.count','average.count') {
-        delete($Keywords{$_}) if (exists($Keywords{$_}));
+    foreach my $value ('total.count','average.count') {
+        delete($Keywords{$value}) if (exists($Keywords{$value}));
     }
     return 1;
 }
@@ -1721,11 +1919,11 @@ sub get_related_words {
     } 
     my @Words=();
     if (exists($thesaurus_db{$keyword})) {
-        $_ = $thesaurus_db{$keyword};
-        (undef,@Words) = split/:/;  # The first element is the number of times
-                                    # the word appears.  We do not need it now.
+	# The first element is the number of times
+	# the word appears.  We do not need it now.
+	(undef,@Words) = (split(/:/,$thesaurus_db{$keyword}));
         for (my $i=0;$i<=$#Words;$i++) {
-            ($Words[$i],undef)= split/\,/,$Words[$i];
+            ($Words[$i],undef)= split(/\,/,$Words[$i]);
         }
     }
     untie %thesaurus_db;
@@ -1754,12 +1952,11 @@ if $first is set to 'lastname' then it r
 
 =cut
 
+
 ###############################################################
 sub plainname {
     my ($uname,$udom,$first)=@_;
-    my %names=&Apache::lonnet::get('environment',
-                    ['firstname','middlename','lastname','generation'],
-					 $udom,$uname);
+    my %names=&getnames($uname,$udom);
     my $name=&Apache::lonnet::format_name($names{'firstname'},
 					  $names{'middlename'},
 					  $names{'lastname'},
@@ -1767,7 +1964,7 @@ sub plainname {
     $name=~s/^\s+//;
     $name=~s/\s+$//;
     $name=~s/\s+/ /g;
-    if ($name !~ /\S/) { $name=$uname.'@'.$udom; }
+    if ($name !~ /\S/) { $name=$uname.':'.$udom; }
     return $name;
 }
 
@@ -1790,19 +1987,7 @@ if the user does not
 
 sub nickname {
     my ($uname,$udom)=@_;
-    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 %names=&getnames($uname,$udom);
     my $name=$names{'nickname'};
     if ($name) {
        $name='&quot;'.$name.'&quot;'; 
@@ -1815,6 +2000,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
 
@@ -1828,8 +2027,8 @@ Gets a users screenname and returns it a
 
 sub screenname {
     my ($uname,$udom)=@_;
-    if ($uname eq $ENV{'user.name'} &&
-	$udom eq $ENV{'user.domain'}) {return $ENV{'environment.screenname'};}
+    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'};
 }
@@ -1838,10 +2037,11 @@ sub screenname {
 # ------------------------------------------------------------- Message Wrapper
 
 sub messagewrapper {
-    my ($link,$username,$domain)=@_;
+    my ($link,$username,$domain,$subject,$text)=@_;
     return 
         '<a href="/adm/email?compose=individual&'.
-        'recname='.$username.'&recdom='.$domain.'" '.
+        'recname='.$username.'&recdom='.$domain.
+	'&subject='.&escape($subject).'&text='.&escape($text).'" '.
         'title="'.&mt('Send message').'">'.$link.'</a>';
 }
 # --------------------------------------------------------------- Notes Wrapper
@@ -1871,24 +2071,23 @@ sub syllabuswrapper {
 }
 
 sub track_student_link {
-    my ($linktext,$sname,$sdom,$target) = @_;
-    my $link ="/adm/trackstudent";
+    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";
+        $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
 
 =back
@@ -2002,7 +2201,8 @@ category
 =cut
 
 sub filecategorytypes {
-    return @{$category_extensions{lc($_[0])}};
+    my ($cat) = @_;
+    return @{$category_extensions{lc($cat)}};
 }
 
 =pod
@@ -2017,6 +2217,10 @@ sub fileembstyle {
     return $fe{lc(shift(@_))};
 }
 
+sub filemimetype {
+    return $fm{lc(shift(@_))};
+}
+
 
 sub filecategoryselect {
     my ($name,$value)=@_;
@@ -2073,13 +2277,13 @@ sub fileextensions {
 
 sub display_languages {
     my %languages=();
-    foreach (&preferred_languages()) {
-	$languages{$_}=1;
+    foreach my $lang (&preferred_languages()) {
+	$languages{$lang}=1;
     }
     &get_unprocessed_cgi($ENV{'QUERY_STRING'},['displaylanguage']);
-    if ($ENV{'form.displaylanguage'}) {
-	foreach (split(/\s*(\,|\;|\:)\s*/,$ENV{'form.displaylanguage'})) {
-	    $languages{$_}=1;
+    if ($env{'form.displaylanguage'}) {
+	foreach my $lang (split(/\s*(\,|\;|\:)\s*/,$env{'form.displaylanguage'})) {
+	    $languages{$lang}=1;
         }
     }
     return %languages;
@@ -2087,24 +2291,24 @@ sub display_languages {
 
 sub preferred_languages {
     my @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'});
+    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'}}) {
@@ -2114,11 +2318,11 @@ sub preferred_languages {
     }
 # turn "en-ca" into "en-ca,en"
     my @genlanguages;
-    foreach (@languages) {
-	unless ($_=~/\w/) { next; }
-	push (@genlanguages,$_);
-	if ($_=~/(\-|\_)/) {
-	    push (@genlanguages,(split(/(\-|\_)/,$_))[0]);
+    foreach my $lang (@languages) {
+	unless ($lang=~/\w/) { next; }
+	push (@genlanguages,$lang);
+	if ($lang=~/(\-|\_)/) {
+	    push(@genlanguages,(split(/(\-|\_)/,$lang))[0]);
 	}
     }
     return @genlanguages;
@@ -2174,14 +2378,14 @@ sub get_previous_attempt {
       my %lasthash=();
       my $version;
       for ($version=1;$version<=$returnhash{'version'};$version++) {
-        foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) {
-	  $lasthash{$_}=$returnhash{$version.':'.$_};
+        foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
+	  $lasthash{$key}=$returnhash{$version.':'.$key};
         }
       }
       $prevattempts='<table border="0" width="100%"><tr><td bgcolor="#777777">';
       $prevattempts.='<table border="0" width="100%"><tr bgcolor="#e6ffff"><td>History</td>';
-      foreach (sort(keys %lasthash)) {
-	my ($ign,@parts) = split(/\./,$_);
+      foreach my $key (sort(keys(%lasthash))) {
+	my ($ign,@parts) = split(/\./,$key);
 	if ($#parts > 0) {
 	  my $data=$parts[-1];
 	  pop(@parts);
@@ -2197,27 +2401,27 @@ sub get_previous_attempt {
       if ($getattempt eq '') {
 	for ($version=1;$version<=$returnhash{'version'};$version++) {
 	  $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Transaction '.$version.'</td>';
-	    foreach (sort(keys %lasthash)) {
+	    foreach my $key (sort(keys(%lasthash))) {
 	       my $value;
-	       if ($_ =~ /timestamp/) {
-		  $value=scalar(localtime($returnhash{$version.':'.$_}));
+	       if ($key =~ /timestamp/) {
+		  $value=scalar(localtime($returnhash{$version.':'.$key}));
 	       } else {
-		  $value=$returnhash{$version.':'.$_};
+		  $value=$returnhash{$version.':'.$key};
 	       }
-	       $prevattempts.='<td>'.&Apache::lonnet::unescape($value).'&nbsp;</td>';   
+	       $prevattempts.='<td>'.&unescape($value).'&nbsp;</td>';   
 	    }
 	 }
       }
       $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Current</td>';
-      foreach (sort(keys %lasthash)) {
+      foreach my $key (sort(keys(%lasthash))) {
 	my $value;
-	if ($_ =~ /timestamp/) {
-	  $value=scalar(localtime($lasthash{$_}));
+	if ($key =~ /timestamp/) {
+	  $value=scalar(localtime($lasthash{$key}));
 	} else {
-	  $value=$lasthash{$_};
+	  $value=$lasthash{$key};
 	}
-	$value=&Apache::lonnet::unescape($value);
-	if ($_ =~/$regexp$/ && (defined &$gradesub)) {$value = &$gradesub($value)}
+	$value=&unescape($value);
+	if ($key =~/$regexp$/ && (defined &$gradesub)) {$value = &$gradesub($value)}
 	$prevattempts.='<td>'.$value.'&nbsp;</td>';
       }
       $prevattempts.='</tr></table></td></tr></table>';
@@ -2249,14 +2453,14 @@ sub relative_to_absolute {
 	}
     }
     $thisdir=~s-/[^/]*$--;
-    foreach (@rlinks) {
-	unless (($_=~/^http:\/\//i) ||
-		($_=~/^\//) ||
-		($_=~/^javascript:/i) ||
-		($_=~/^mailto:/i) ||
-		($_=~/^\#/)) {
-	    my $newlocation=&Apache::lonnet::hreflocation($thisdir,$_);
-	    $output=~s/(\"|\'|\=\s*)$_(\"|\'|\s|\>)/$1$newlocation$2/;
+    foreach my $link (@rlinks) {
+	unless (($link=~/^http:\/\//i) ||
+		($link=~/^\//) ||
+		($link=~/^javascript:/i) ||
+		($link=~/^mailto:/i) ||
+		($link=~/^\#/)) {
+	    my $newlocation=&Apache::lonnet::hreflocation($thisdir,$link);
+	    $output=~s/(\"|\'|\=\s*)\Q$link\E(\"|\'|\s|\>)/$1$newlocation$2/;
 	}
     }
 # -------------------------------------------------- Deal with Applet codebases
@@ -2283,7 +2487,7 @@ sub get_student_view {
   if (defined($moreenv)) {
       %form=(%form,%{$moreenv});
   }
-  if ($target eq 'tex') {$form{'grade_target'} = 'tex';}
+  if (defined($target)) { $form{'grade_target'} = $target; }
   $feedurl=&Apache::lonnet::clutter($feedurl);
   my $userview=&Apache::lonnet::ssi_body($feedurl,%form);
   $userview=~s/\<body[^\>]*\>//gi;
@@ -2323,7 +2527,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
 
@@ -2331,15 +2535,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=&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=&escape($symb);
+    if ($target) { $target="target=\"$target\""; }
+    return '<a href="/adm/parmset?&command=set&'.
+	'symb='.$symb.'&uname='.$uname.
+	'&udom='.$udom.'" '.$target.'>'.$text.'</a>';
 }
 ##############################################
 
@@ -2365,6 +2618,11 @@ sub timehash {
              'dlsav'   => $ltime[8] );
 }
 
+sub utc_string {
+    my ($date)=@_;
+    return strftime("%Y%m%dT%H%M%SZ",gmtime($date));
+}
+
 sub maketime {
     my %th=@_;
     return POSIX::mktime(
@@ -2375,11 +2633,17 @@ sub maketime {
 #########################################
 
 sub findallcourses {
-    my %courses=();
+    my ($roles) = @_;
+    my %roles;
+    if (ref($roles)) { %roles = map { $_ => 1 } @{$roles}; }
+    my %courses;
     my $now=time;
-    foreach (keys %ENV) {
-	if ($_=~/^user\.role\.\w+\.\/(\w+)\/(\w+)/) {
-	    my ($starttime,$endtime)=$ENV{$_};
+    foreach my $key (keys(%env)) {
+	if ( $key=~m{^user\.role\.(\w+)\./(\w+)/(\w+)} ) {
+	    my ($role,$domain,$id) = ($1,$2,$3);
+	    next if ($role eq 'ca' || $role eq 'aa');
+	    next if (%roles && !exists($roles{$role}));
+	    my ($starttime,$endtime)=split(/\./,$env{$key});
             my $active=1;
             if ($starttime) {
 		if ($now<$starttime) { $active=0; }
@@ -2387,10 +2651,10 @@ sub findallcourses {
             if ($endtime) {
                 if ($now>$endtime) { $active=0; }
             }
-            if ($active) { $courses{$1.'_'.$2}=1; }
+            if ($active) { $courses{$domain.'_'.$id}=1; }
         }
     }
-    return keys %courses;
+    return keys(%courses);
 }
 
 ###############################################
@@ -2416,9 +2680,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;
@@ -2460,10 +2724,11 @@ Returns: value of designparamter $which
 
 =cut
 
+
 ##############################################
 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';
 	}
@@ -2474,11 +2739,11 @@ sub designparm {
 	    return '#CCCCCC';
 	}
     }
-    if ($ENV{'environment.color.'.$which}) {
-	return $ENV{'environment.color.'.$which};
+    if (exists($env{'environment.color.'.$which})) {
+	return $env{'environment.color.'.$which};
     }
     $domain=&determinedomain($domain);
-    if ($designhash{$domain.'.'.$which}) {
+    if (exists($designhash{$domain.'.'.$which})) {
 	return $designhash{$domain.'.'.$which};
     } else {
         return $designhash{'default.'.$which};
@@ -2517,6 +2782,21 @@ Inputs:
 =item * $forcereg, if page should register as content page (relevant for 
             text interface only)
 
+=item * $customtitle, alternate text to use instead of $title
+                      in the title box that appears, this text
+                      is not auto translated like the $title is
+
+=item * $notopbar, if true, keep the 'what is this' info but remove the
+                   navigational links
+
+=item * $bgcolor, used to override the bgcolor on a webpage to a specific value
+
+=item * $notitle, if true keep the nav controls, but remove the title bar
+
+=item * $no_inline_link, if true and in remote mode, don't show the 
+         'Switch To Inline Menu' link
+
+
 =back
 
 Returns: A uniform header for LON-CAPA web pages.  
@@ -2527,138 +2807,1043 @@ other decorations will be returned.
 =cut
 
 sub bodytag {
-    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,$customtitle)=@_;
+    my ($title,$function,$addentries,$bodyonly,$domain,$forcereg,$customtitle,
+	$notopbar,$bgcolor,$notitle,$no_inline_link)=@_;
+
     $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);
-    my $font=&designparm($function.'.font',$domain);
-    my $link=&designparm($function.'.link',$domain);
-    my $alink=&designparm($function.'.alink',$domain);
-    my $vlink=&designparm($function.'.vlink',$domain);
-    my $sidebg=&designparm($function.'.sidebg',$domain);
-# Accessibility font enhance
-    unless ($addentries) { $addentries=''; }
-    my $addstyle='';
-    if ($ENV{'browser.fontenhance'} eq 'on') {
-	$addstyle=' font-size: x-large;';
-    }
+    my $img =    &designparm($function.'.img',$domain);
+    my $font =   &designparm($function.'.font',$domain);
+    my $pgbg   = $bgcolor || &designparm($function.'.pgbg',$domain);
+
+    my %design = ( 'style'   => 'margin-top: 0px',
+		   'bgcolor' => $pgbg,
+		   'text'    => $font,
+                   'alink'   => &designparm($function.'.alink',$domain),
+		   'vlink'   => &designparm($function.'.vlink',$domain),
+		   'link'    => &designparm($function.'.link',$domain),);
+    @$addentries{keys(%design)} = @design{keys(%design)};
+
  # role and realm
-    my ($role,$realm)
-       =&Apache::lonnet::plaintext((split(/\./,$ENV{'request.role'}))[0]);
+    my ($role,$realm) = split(/\./,$env{'request.role'},2);
+    if ($role  eq 'ca') {
+        my ($rdom,$rname) = ($realm =~ m-^/(\w+)/(\w+)$-);
+        $realm = &plainname($rname,$rdom).':'.$rdom;
+    } 
 # realm
-    if ($ENV{'request.course.id'}) {
-	$realm=
-         $ENV{'course.'.$ENV{'request.course.id'}.'.description'};
+    if ($env{'request.course.id'}) {
+        if ($env{'request.role'} !~ /^cr/) {
+            $role = &Apache::lonnet::plaintext($role,&course_type());
+        }
+	$realm = $env{'course.'.$env{'request.course.id'}.'.description'};
+    } else {
+        $role = &Apache::lonnet::plaintext($role);
     }
-    unless ($realm) { $realm='&nbsp;'; }
+    if (!$realm) { $realm='&nbsp;'; }
 # Set messages
     my $messages=&domainlogo($domain);
 # Port for miniserver
     my $lonhttpdPort=$Apache::lonnet::perlvar{'lonhttpdPort'};
     if (!defined($lonhttpdPort)) { $lonhttpdPort='8080'; }
+
+    my $extra_body_attr = &make_attr_string($forcereg,$addentries);
+
 # 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"
-style="margin-top: 0px;$addstyle" $addentries>
-END
-    my $upperleft='<img src="http://'.$ENV{'HTTP_HOST'}.':'.
-                   $lonhttpdPort.$img.'" alt="'.$function.'" />';
-    if ($bodyonly) {
+    my $bodytag = "<body $extra_body_attr>".
+	&Apache::lontexconvert::init_math_support();
+
+    if ($bodyonly 
+	|| ($env{'request.state'} eq 'construct' 
+	    && $env{'environment.remote'} ne 'off' )) {
         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') {
-# No Remote
-	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;
+	$bodytag.=&Apache::lonmenu::menubuttons($forcereg,$forcereg);
+	if (!$notitle) {
+	    $bodytag.='<h1>LON-CAPA: '.$title.'</h1>';
+	}
+	return $bodytag;
+    }
+
+
+    
+    my $roleinfo=(<<ENDROLE);
+<td class="LC_title_bar_who">
+<div class="LC_title_bar_name">
+    $env{'environment.firstname'}
+    $env{'environment.middlename'}
+    $env{'environment.lastname'}
+    $env{'environment.generation'}
+    &nbsp;
+</div>
+<div class="LC_title_bar_role">
+$role&nbsp;
+</div>
+<div class="LC_title_bar_realm">
+$realm&nbsp;
+</div>
 </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 $titleinfo = '<span class="LC_title_bar_title">'.$title.'</span>';
+    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 =~ s/\s+$//;
+        $dc_info = '('.$dc_info.')';
+    }
+
+    if ($env{'environment.remote'} eq 'off') {
+        # No Remote
+	if ($env{'request.state'} eq 'construct') {
+	    $forcereg=1;
+	}
+
+	if (!$customtitle && $env{'request.state'} eq 'construct') {
+	    # this is for resources; directories have customtitle, and crumbs
+            # and select recent are created in lonpubdir.pm  
 	    my ($uname,$thisdisfn)=
-		($ENV{'request.filename'} =~ m|^/home/([^/]+)/public_html/(.*)|);
+		($env{'request.filename'} =~ m|^/home/([^/]+)/public_html/(.*)|);
 	    my $formaction='/priv/'.$uname.'/'.$thisdisfn;
 	    $formaction=~s/\/+/\//g;
-	    $titleinfo = '<form name="dirs" method="post" action="'.$formaction
-		.'" target="_top">'
-		.&Apache::lonhtmlcommon::crumbs($uname.'/'.$thisdisfn,'_top','/priv','',-1,1)."<br />"
+
+	    my $parentpath = '';
+	    my $lastitem = '';
+	    if ($thisdisfn =~ m-(.+/)([^/]*)$-) {
+		$parentpath = $1;
+		$lastitem = $2;
+	    } else {
+		$lastitem = $thisdisfn;
+	    }
+	    $titleinfo = 
+		&Apache::loncommon::help_open_menu('','',3,'Authoring').
+		'<b>Construction Space</b>:&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();
+        }
 
-	    &Apache::lonhtmlcommon::store_recent('construct',$formaction,$formaction);
-	    if ($thisdisfn!~m|/$|) {  $forcereg=1; }
+        my $titletable;
+	if (!$notitle) {
+	    $titletable =
+		'<table id="LC_title_bar">'.
+                         "<tr><td> $titleinfo $dc_info</td>".$roleinfo.
+			 '</tr></table>';
 	}
-
-	&Apache::lonnet::logthis("hrrm");
-        return $bodytag.&Apache::lonmenu::menubuttons($forcereg,'web',
-                                                      $forcereg).
-      '<table bgcolor="'.$pgbg.'" width="100%" border="0" cellspacing="3" cellpadding="3"><tr><td rowspan="3" bgcolor="'.$tabbg.'">'.$titleinfo.'</td>'.$roleinfo.'</tr></table>';
+	if ($notopbar) {
+	    $bodytag .= $titletable;
+	} else {
+	    if ($env{'request.state'} eq 'construct') {
+                $bodytag .= &Apache::lonmenu::menubuttons($forcereg,$forcereg,
+							  $titletable);
+            } else {
+                $bodytag .= &Apache::lonmenu::menubuttons($forcereg,$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;
+
+    my $upperleft='<img src="http://'.$ENV{'HTTP_HOST'}.':'.
+        $lonhttpdPort.$img.'" alt="'.$function.'" />';
+
+    # Explicit link to get inline menu
+    my $menu= ($no_inline_link?''
+	       :'<br /><a href="/adm/remote?action=collapse">'.&mt('Switch to Inline Menu Mode').'</a>');
+    #
+    if ($notitle) {
+	return $bodytag;
     }
     return(<<ENDBODY);
 $bodytag
-<table width="100%" cellspacing="0" border="0" cellpadding="0">
-<tr><td bgcolor="$sidebg">
-$upperleft</td>
-<td bgcolor="$sidebg" align="right">$messages&nbsp;</td>
+<table id="LC_title_bar" class="LC_with_remote">
+<tr><td class="LC_title_bar_role_logo">$upperleft</td>
+    <td class="LC_title_bar_domain_logo">$messages&nbsp;</td>
 </tr>
-<tr>
-<td rowspan="3" bgcolor="$tabbg">
-$titleinfo
-<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><td>$titleinfo $dc_info $menu</td>
+$roleinfo
 </tr>
-<tr><td bgcolor="$tabbg" align="right">
-<font size="2" face="Arial, Helvetica, sans-serif">$role</font>&nbsp;
-</td></tr>
-<tr>
-<td bgcolor="$tabbg" align="right"><font size="2" face="Arial, Helvetica, sans-serif">$realm</font>&nbsp;</td></tr>
-</table><br />
+</table>
 ENDBODY
 }
 
+sub make_attr_string {
+    my ($register,$attr_ref) = @_;
+
+    if ($attr_ref && !ref($attr_ref)) {
+	die("addentries Must be a hash ref ".
+	    join(':',caller(1))." ".
+	    join(':',caller(0))." ");
+    }
+
+    if ($register) {
+	my ($on_load,$on_unload);
+	foreach my $key (keys(%{$attr_ref})) {
+	    if      (lc($key) eq 'onload') {
+		$on_load.=$attr_ref->{$key}.';';
+		delete($attr_ref->{$key});
+
+	    } elsif (lc($key) eq 'onunload') {
+		$on_unload.=$attr_ref->{$key}.';';
+		delete($attr_ref->{$key});
+	    }
+	}
+	$attr_ref->{'onload'}  =
+	    &Apache::lonmenu::loadevents().  $on_load;
+	$attr_ref->{'onunload'}=
+	    &Apache::lonmenu::unloadevents().$on_unload;
+    }
+
+# Accessibility font enhance
+    if ($env{'browser.fontenhance'} eq 'on') {
+	my $style;
+	foreach my $key (keys(%{$attr_ref})) {
+	    if (lc($key) eq 'style') {
+		$style.=$attr_ref->{$key}.';';
+		delete($attr_ref->{$key});
+	    }
+	}
+	$attr_ref->{'style'}=$style.'; font-size: x-large;';
+    }
+
+    if ($env{'browser.blackwhite'} eq 'on') {
+	delete($attr_ref->{'font'});
+	delete($attr_ref->{'link'});
+	delete($attr_ref->{'alink'});
+	delete($attr_ref->{'vlink'});
+	delete($attr_ref->{'bgcolor'});
+	delete($attr_ref->{'background'});
+    }
+
+    my $attr_string;
+    foreach my $attr (keys(%$attr_ref)) {
+	$attr_string .= " $attr=\"".$attr_ref->{$attr}.'" ';
+    }
+    return $attr_string;
+}
+
+
 ###############################################
+###############################################
+
+=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;
+    if ( exists( $env{'internal.head.redirect'} ) ) {
+	$endbodytag=
+	    "<br /><a href=\"$env{'internal.head.redirect'}\">".
+	    &mt('Continue').'</a>'.
+	    $endbodytag;
+    }
+    return $endbodytag;
+}
 
 =pod
 
+=over 4
+
+=item * &standard_css()
+
+Returns a style sheet
+
+Inputs: (all optional)
+            domain         -> force to color decorate a page for a specific
+                               domain
+            function       -> force usage of a specific rolish color scheme
+            bgcolor        -> override the default page bgcolor
+
+=back
+
+=cut
+
+sub standard_css {
+    my ($function,$domain,$bgcolor) = @_;
+    $function  = &get_users_function() if (!$function);
+    my $img    = &designparm($function.'.img',   $domain);
+    my $tabbg  = &designparm($function.'.tabbg', $domain);
+    my $font   = &designparm($function.'.font',  $domain);
+    my $sidebg = &designparm($function.'.sidebg',$domain);
+    my $pgbg_or_bgcolor =
+	         $bgcolor ||
+	         &designparm($function.'.pgbg',  $domain);
+    my $pgbg   = &designparm($function.'.pgbg',  $domain);
+    my $alink  = &designparm($function.'.alink', $domain);
+    my $vlink  = &designparm($function.'.vlink', $domain);
+    my $link   = &designparm($function.'.link',  $domain);
+
+    my $sans                 = 'Arial,Helvetica,sans-serif';
+    my $mono                 = 'monospace';
+    my $data_table_head      = $tabbg;
+    my $data_table_light     = '#EEEEEE';
+    my $data_table_dark      = '#DDD';
+    my $data_table_highlight = '#FFFF00';
+    my $mail_new             = '#FFBB77';
+    my $mail_new_hover       = '#DD9955';
+    my $mail_read            = '#BBBB77';
+    my $mail_read_hover      = '#999944';
+    my $mail_replied         = '#AAAA88';
+    my $mail_replied_hover   = '#888855';
+    my $mail_other           = '#99BBBB';
+    my $mail_other_hover     = '#669999';
+    my $table_header         = '#DDDDDD';
+
+    my $border = ($env{'browser.type'} eq 'explorer') ? '0px 2px 0px 2px'
+	                                              : '0px 3px 0px 4px';
+    return <<END;
+h1, h2, h3, th { font-family: $sans }
+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; }
+.LC_filename {font-family: $mono;}
+.LC_error {
+  color: red;
+  font-size: larger;
+}
+.LC_warning {
+  color: red;
+}
+.LC_success {
+  color: green;
+}
+
+table#LC_top_nav, table#LC_menubuttons {
+  width: 100%;
+  background: $pgbg;
+  border: 2px;
+  border-collapse: seperate;
+}
+
+table#LC_title_bar, table#LC_breadcrumbs, table#LC_nav_location,
+table#LC_title_bar.LC_with_remote {
+  width: 100%;
+  border-color: $pgbg;
+  border-style: solid;
+  border-width: $border;
+
+  background: $pgbg;
+  font-family: $sans;
+  border-collapse: collapse;
+}
+
+table#LC_title_bar td {
+  padding: 3px;
+  background: $tabbg;
+}
+table#LC_title_bar td.LC_title_bar_who {
+  background: $tabbg;
+  color: $font;
+  font: medium $sans;
+  text-align: right;
+}
+span.LC_title_bar_title {
+  font: bold xx-large $sans;
+}
+table#LC_title_bar td.LC_title_bar_domain_logo {
+  background: $sidebg;
+  text-align: right;
+  padding: 0px;
+}
+table#LC_title_bar td.LC_title_bar_role_logo {
+  background: $sidebg;
+  padding: 0px;
+}
+
+table#LC_menubuttons_mainmenu {
+  background: $pgbg;
+  border: 0px;
+  border-spacing: 1px;
+  padding: 0px 1px;
+  margin: 0px;
+  border-collapse: separate;
+}
+table#LC_menubuttons img, table#LC_menubuttons_mainmenu img {
+  border: 0px;
+}
+table#LC_top_nav td {
+  background: $tabbg;
+  border: 0px;
+}
+table#LC_top_nav td a, div#LC_top_nav a {
+  color: $font;
+  font-family: $sans;
+}
+table#LC_top_nav td.LC_top_nav_logo {
+  background: $tabbg;
+  text-align: right;
+}
+table#LC_breadcrumbs td {
+  background: $tabbg;
+  color: $font;
+  font-family: $sans;
+  font-size: smaller;
+}
+table#LC_breadcrumbs td.LC_breadcrumb_component {
+  background: $tabbg;
+  color: $font;
+  font-family: $sans;
+  font-size: larger;
+  text-align: right;
+}
+td.LC_table_cell_checkbox {
+  text-align: center;
+}
+
+.LC_menubuttons_inline_text {
+  color: $font;
+  font-family: $sans;
+  font-size: smaller;
+}
+
+td.LC_menubuttons_text {
+  color: $font;
+  font-family: $sans;
+}
+td.LC_menubuttons_img {
+  background: $tabbg;
+}
+.LC_current_location {
+  font-family: $sans;
+  background: $tabbg;
+}
+.LC_new_mail {
+  font-family: $sans;
+  font-weight: bold;
+}
+
+table.LC_data_table, table.LC_mail_list {
+  border: 1px solid #000000;
+  border-collapse: seperate;
+}
+table.LC_data_table tr th, table.LC_calendar tr th, table.LC_mail_list tr th {
+  font-weight: bold;
+  background-color: $data_table_head;
+}
+table.LC_data_table tr td {
+  background-color: $data_table_light;
+}
+table.LC_data_table tr.LC_even_row td {
+  background-color: $data_table_dark;
+}
+table.LC_data_table tr.LC_empty td {
+  background-color: #FFFFFF;
+}
+
+table.LC_calendar {
+  border: 1px solid #000000;
+  border-collapse: collapse;
+}
+table.LC_calendar_pickdate {
+  font-size: xx-small;
+}
+table.LC_calendar tr td {
+  border: 1px solid #000000;
+  vertical-align: top;
+}
+table.LC_calendar tr td.LC_calendar_day_empty {
+  background-color: $data_table_dark;
+}
+table.LC_calendar tr td.LC_calendar_day_current {
+  background-color: $data_table_highlight;
+}
+
+table.LC_mail_list tr.LC_mail_new {
+  background-color: $mail_new;
+}
+table.LC_mail_list tr.LC_mail_new:hover {
+  background-color: $mail_new_hover;
+}
+table.LC_mail_list tr.LC_mail_read {
+  background-color: $mail_read;
+}
+table.LC_mail_list tr.LC_mail_read:hover {
+  background-color: $mail_read_hover;
+}
+table.LC_mail_list tr.LC_mail_replied {
+  background-color: $mail_replied;
+}
+table.LC_mail_list tr.LC_mail_replied:hover {
+  background-color: $mail_replied_hover;
+}
+table.LC_mail_list tr.LC_mail_other {
+  background-color: $mail_other;
+}
+table.LC_mail_list tr.LC_mail_other:hover {
+  background-color: $mail_other_hover;
+}
+
+table#LC_portfolio_actions {
+  width: auto;
+  background: $pgbg;
+  border: 0px;
+  border-spacing: 2px 2px;
+  padding: 0px;
+  margin: 0px;
+  border-collapse: separate;
+}
+table#LC_portfolio_actions td.LC_label {
+  background: $tabbg;
+  text-align: right;
+}
+table#LC_portfolio_actions td.LC_value {
+  background: $tabbg;
+}
+
+table#LC_cstr_controls {
+  width: 100%;
+  border-collapse: collapse;
+}
+table#LC_cstr_controls tr td {
+  border: 4px solid $pgbg;
+  padding: 4px;
+  text-align: center;
+  background: $tabbg;
+}
+table#LC_cstr_controls tr th {
+  border: 4px solid $pgbg;
+  background: $table_header;
+  text-align: center;
+  font-family: $sans;
+  font-size: smaller;
+}
+
+table#LC_browser {
+ 
+}
+table#LC_browser tr th {
+  background: $table_header;
+}
+table#LC_browser tr td {
+  padding: 2px;
+}
+table#LC_browser tr.LC_browser_file,
+table#LC_browser tr.LC_browser_file_published {
+  background: #CCFF88;
+}
+table#LC_browser tr.LC_browser_file_locked,
+table#LC_browser tr.LC_browser_file_unpublished {
+  background: #FFAA99;
+}
+table#LC_browser tr.LC_browser_file_obsolete {
+  background: #AAAAAA;
+}
+table#LC_browser tr.LC_browser_file_modified {
+  background: #FFFF77;
+}
+table#LC_browser tr.LC_browser_folder {
+  background: #CCCCFF;
+}
+span.LC_current_location {
+  font-size: x-large;
+  background: $pgbg;
+}
+
+span.LC_parm_menu_item {
+  font-size: larger;
+  font-family: $sans;
+}
+span.LC_parm_scope_all {
+  color: red;
+}
+span.LC_parm_scope_folder {
+  color: green;
+}
+span.LC_parm_scope_resource {
+  color: orange;
+}
+span.LC_parm_part {
+  color: blue;
+}
+span.LC_parm_folder, span.LC_parm_symb {
+  font-size: x-small;
+  font-family: $mono;
+  color: #AAAAAA;
+}
+
+td.LC_parm_overview_level_menu, td.LC_parm_overview_map_menu,
+td.LC_parm_overview_parm_selectors, td.LC_parm_overview_parm_restrictions {
+  border: 1px solid black;
+  border-collapse: collapse;
+}
+table.LC_parm_overview_restrictions td {
+  border-width: 1px 4px 1px 4px;
+  border-style: solid;
+  border-color: $pgbg;
+  text-align: center;
+}
+table.LC_parm_overview_restrictions th {
+  background: $tabbg;
+  border-width: 1px 4px 1px 4px;
+  border-style: solid;
+  border-color: $pgbg;
+}
+table#LC_helpmenu {
+  border: 0px;
+  height: 55px;
+  border-spacing: 0px;
+}
+
+table#LC_helpmenu fieldset legend {
+  font-size: larger;
+  font-weight: bold;
+}
+table#LC_helpmenu_links {
+  width: 100%;
+  border: 1px solid black;
+  background: $pgbg;
+  padding: 0px;
+  border-spacing: 1px;
+}
+table#LC_helpmenu_links tr td {
+  padding: 1px;
+  background: $tabbg;
+  text-align: center;
+  font-weight: bold;
+}
+
+table#LC_helpmenu_links a:link, table#LC_helpmenu_links a:visited,
+table#LC_helpmenu_links a:active {
+  text-decoration: none;
+  color: $font;
+}
+table#LC_helpmenu_links a:hover {
+  text-decoration: underline;
+  color: $vlink;
+}
+
+END
+}
+
+=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>
+        $args - optional arguments
+            force_register - if is true call registerurl so the remote is 
+                             informed
+            redirect       -> array ref of seconds before redirect occurs
+                                    url to redirect to
+                           (side effect of setting 
+                               $env{'internal.head.redirect'} to the url 
+                               redirected too)
+            domain         -> force to color decorate a page for a specific
+                               domain
+            function       -> force usage of a specific rolish color scheme
+            bgcolor        -> override the default page bgcolor
+
+=back
+
+=cut
+
+sub headtag {
+    my ($title,$head_extra,$args) = @_;
+    
+    my $function = $args->{'function'} || &get_users_function();
+    my $domain   = $args->{'domain'}   || &determinedomain();
+    my $bgcolor  = $args->{'bgcolor'}  || &designparm($function.'.pgbg',$domain);
+    my $url = join(':',$env{'user.name'},$env{'user.domain'},time(),
+		   #$env{'environment.color.timestamp'},
+		   $function,$domain,$bgcolor);
+
+    $url = '/adm/css/'.&escape($url).'.css';
+
+    my $result =
+	'<head>'.
+	'<link rel="stylesheet" type="text/css" href="'.$url.'" />'.
+	&font_settings().
+	&Apache::lonhtmlcommon::htmlareaheaders();
+
+    if ($args->{'force_register'}) {
+	$result .= &Apache::lonmenu::registerurl(1);
+    }
+
+    if (ref($args->{'redirect'})) {
+	my ($time,$url) = @{$args->{'redirect'}};
+	$url = &Apache::lonenc::check_encrypt($url);
+	$env{'internal.head.redirect'} = $url;
+	$result.=<<ADDMETA
+<meta http-equiv="pragma" content="no-cache" />
+<meta http-equiv="Refresh" content="$time; url=$url" />
+ADDMETA
+    }
+    if (!defined($title)) {
+	$title = 'The LearningOnline Network with CAPA';
+    }
+    
+    $result .= '<title> LON-CAPA '.&mt($title).'</title>'.$head_extra;
+    return $result;
+}
+
+=pod
+
+=over 4
+
+=item * &font_settings()
+
+Returns neccessary <meta> to set the proper encoding
+
+Inputs: none
+
+=back
+
+=cut
+
+sub font_settings {
+    my $headerstring='';
+    if (($env{'browser.os'} eq 'mac') && (!$env{'browser.mathml'})) { 
+	$headerstring.=
+	    '<meta Content-Type="text/html; charset=x-mac-roman" />';
+    } elsif (!$env{'browser.mathml'} && $env{'browser.unicode'}) {
+	$headerstring.=
+	    '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
+    }
+    return $headerstring;
+}
+
+=pod
+
+=over 4
+
+=item * &xml_begin()
+
+Returns the needed doctype and <html>
+
+Inputs: none
+
+=back
+
+=cut
+
+sub xml_begin {
+    my $output='';
+
+    &Apache::lonhtmlcommon::init_htmlareafields();
+
+    if ($env{'browser.mathml'}) {
+	$output='<?xml version="1.0"?>'
+            #.'<?xml-stylesheet type="text/css" href="/adm/MathML/mathml.css"?>'."\n"
+#            .'<!DOCTYPE html SYSTEM "/adm/MathML/mathml.dtd" '
+            
+#	    .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [<!ENTITY mathns "http://www.w3.org/1998/Math/MathML">] >'
+	    .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">'
+            .'<html xmlns:math="http://www.w3.org/1998/Math/MathML" ' 
+	    .'xmlns="http://www.w3.org/1999/xhtml">';
+    } else {
+	$output='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>';
+    }
+    return $output;
+}
+
+=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,$args) = @_;
+    return &headtag($title,$head_extra,$args).&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       -> see &headtag()
+                  bgcolor        -> override the default page bg color
+                  js_ready       -> return a string ready for being used in 
+                                    a javascript writeln
+                  html_encode    -> return a string ready for being used in 
+                                    a html attribute
+                  force_register -> if is true will turn on the &bodytag()
+                                    $forcereg arg
+                  body_title     -> alternate text to use instead of $title
+                                    in the title box that appears, this text
+                                    is not auto translated like the $title is
+                  frameset       -> if true will start with a <frameset>
+                                    rather than <body>
+                  no_title       -> if true the title bar won't be shown
+                  skip_phases    -> hash ref of 
+                                    head -> skip the <html><head> generation
+                                    body -> skip all <body> generation
+
+                  no_inline_link -> if true and in remote mode, don't show the 
+                                    'Switch To Inline Menu' link
+
+=back
+
+=cut
+
+sub start_page {
+    my ($title,$head_extra,$args) = @_;
+    #&Apache::lonnet::logthis("start_page ".join(':',caller(0)));
+    my %head_args;
+    foreach my $arg ('redirect','force_register','domain','function',
+		     'bgcolor') {
+	if (defined($args->{$arg})) {
+	    $head_args{$arg} = $args->{$arg};
+	}
+    }
+
+    $env{'internal.start_page'}++;
+    my $result;
+    if (! exists($args->{'skip_phases'}{'head'}) ) {
+	$result.=
+	    &xml_begin().
+	    &headtag($title,$head_extra,\%head_args).&endheadtag();
+    }
+    
+    if (! exists($args->{'skip_phases'}{'body'}) ) {
+	if ($args->{'frameset'}) {
+	    my $attr_string = &make_attr_string($args->{'force_register'},
+						$args->{'add_entries'});
+	    $result .= "\n<frameset $attr_string>\n";
+	} else {
+	    $result .=
+		&bodytag($title, 
+			 $args->{'function'},       $args->{'add_entries'},
+			 $args->{'only_body'},      $args->{'domain'},
+			 $args->{'force_register'}, $args->{'body_title'},
+			 $args->{'no_nav_bar'},     $args->{'bgcolor'},
+			 $args->{'no_title'},       $args->{'no_inline_link'});
+	}
+    }
+
+    if ($args->{'js_ready'}) {
+	$result = &js_ready($result);
+    }
+    if ($args->{'html_encode'}) {
+	$result = &html_encode($result);
+    }
+    return $result;
+}
+
+
+=pod
+
+=over 4
+
+=item * &head()
+
+Returns a complete </body></html> section for LON-CAPA web pages.
+
+Inputs:         $args - additional optional args supported are:
+                 js_ready     -> return a string ready for being used in 
+                                 a javascript writeln
+                 html_encode  -> return a string ready for being used in 
+                                 a html attribute
+                 frameset     -> if true will start with a <frameset>
+                                 rather than <body>
+=back
+
+=cut
+
+sub end_page {
+    my ($args) = @_;
+    $env{'internal.end_page'}++;
+    my $result;
+    if ($args->{'discussion'}) {
+	my ($target,$parser);
+	if (ref($args->{'discussion'})) {
+	    ($target,$parser) =($args->{'discussion'}{'target'},
+				$args->{'discussion'}{'parser'});
+	}
+	$result .= &Apache::lonxml::xmlend($target,$parser);
+    }
+
+    if ($args->{'frameset'}) {
+	$result .= '</frameset>';
+    } else {
+	$result .= &endbodytag();
+    }
+    $result .= "\n</html>";
+
+    if ($args->{'js_ready'}) {
+	$result = &js_ready($result);
+    }
+
+    if ($args->{'html_encode'}) {
+	$result = &html_encode($result);
+    }
+
+    return $result;
+}
+
+sub html_encode {
+    my ($result) = @_;
+
+    $result = &HTML::Entities::encode($result,'<>&"');
+    
+    return $result;
+}
+sub js_ready {
+    my ($result) = @_;
+
+    $result =~ s/[\n\r]/ /xmsg;
+    $result =~ s/\\/\\\\/xmsg;
+    $result =~ s/'/\\'/xmsg;
+    $result =~ s{</}{<\\/}xmsg;
+    
+    return $result;
+}
+
+sub validate_page {
+    if (  exists($env{'internal.start_page'})
+	  &&     $env{'internal.start_page'} > 1) {
+	&Apache::lonnet::logthis('start_page called multiple times '.
+				 $env{'internal.start_page'}.' '.
+				 $ENV{'request.filename'});
+    }
+    if (  exists($env{'internal.end_page'})
+	  &&     $env{'internal.end_page'} > 1) {
+	&Apache::lonnet::logthis('end_page called multiple times '.
+				 $env{'internal.end_page'}.' '.
+				 $env{'request.filename'});
+    }
+    if (     exists($env{'internal.start_page'})
+	&& ! exists($env{'internal.end_page'})) {
+	&Apache::lonnet::logthis('start_page called without end_page '.
+				 $env{'request.filename'});
+    }
+    if (   ! exists($env{'internal.start_page'})
+	&&   exists($env{'internal.end_page'})) {
+	&Apache::lonnet::logthis('end_page called without start_page'.
+				 $env{'request.filename'});
+    }
+}
+
+sub simple_error_page {
+    my ($r,$title,$msg) = @_;
+    my $page =
+	&Apache::loncommon::start_page($title).
+	&mt($msg).
+	&Apache::loncommon::end_page();
+    if (ref($r)) {
+	$r->print($page);
+	return;
+    }
+    return $page;
+}
+
+{
+    my $row_count;
+    sub start_data_table {
+	undef($row_count);
+	return '<table class="LC_data_table">'."\n";
+    }
+
+    sub end_data_table {
+	undef($row_count);
+	return '</table>'."\n";;
+    }
+
+    sub start_data_table_row {
+	$row_count++;
+	return  '<tr '.(($row_count % 2)?'':'class="LC_even_row"').'>'."\n";;
+    }
+
+    sub end_data_table_row {
+	return '</tr>'."\n";;
+    }
+
+    sub start_data_table_header_row {
+	return  '<tr class="LC_header_row">'."\n";;
+    }
+
+    sub end_data_table_header_row {
+	return '</tr>'."\n";;
+    }
+}
+
+###############################################
+
+=pod
+
+=over 4
+
 =item get_users_function
 
 Used by &bodytag to determine the current users primary role.
@@ -2669,13 +3854,13 @@ Returns either 'student','coordinator','
 ###############################################
 sub get_users_function {
     my $function = 'student';
-    if ($ENV{'request.role'}=~/^(cc|in|ta|ep)/) {
+    if ($env{'request.role'}=~/^(cc|in|ta|ep)/) {
         $function='coordinator';
     }
-    if ($ENV{'request.role'}=~/^(su|dc|ad|li)/) {
+    if ($env{'request.role'}=~/^(su|dc|ad|li)/) {
         $function='admin';
     }
-    if (($ENV{'request.role'}=~/^(au|ca)/) ||
+    if (($env{'request.role'}=~/^(au|ca)/) ||
         ($ENV{'REQUEST_URI'}=~/^(\/priv|\~)/)) {
         $function='author';
     }
@@ -2684,101 +3869,315 @@ sub get_users_function {
 
 ###############################################
 
-sub get_posted_cgi {
-    my $r=shift;
+=pod
 
-    my $buffer;
-    
-    $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;
-	foreach $pair (@pairs) {
-	    my ($name,$value) = split(/=/,$pair);
-	    $value =~ tr/+/ /;
-	    $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
-	    $name  =~ tr/+/ /;
-	    $name  =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
-	    &add_to_env("form.$name",$value);
-	}
-    } else {
-	my $contentsep=$1;
-	my @lines = split (/\n/,$buffer);
-	my $name='';
-	my $value='';
-	my $fname='';
-	my $fmime='';
-	my $i;
-	for ($i=0;$i<=$#lines;$i++) {
-	    if ($lines[$i]=~/^$contentsep/) {
-		if ($name) {
-		    chomp($value);
-		    if ($fname) {
-			$ENV{"form.$name.filename"}=$fname;
-			$ENV{"form.$name.mimetype"}=$fmime;
-		    } else {
-			$value=~s/\s+$//s;
-		    }
-		    &add_to_env("form.$name",$value);
+=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 ID.
+
+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 array containing roles for which sections should 
+be gathered (optional). If the third argument is undefined,
+sections are gathered for any role.
+ 
+Returns section hash (keys are section IDs, values are
+number of users in each section), subject to the
+optional roles filter.
+
+=cut
+
+###############################################
+sub get_sections {
+    my ($cdom,$cnum,$possible_roles) = @_;
+    if (!defined($cdom) || !defined($cnum)) {
+        my $cid =  $env{'request.course.id'};
+
+	return if (!defined($cid));
+
+        $cdom = $env{'course.'.$cid.'.domain'};
+        $cnum = $env{'course.'.$cid.'.num'};
+    }
+
+    my %sectioncount;
+
+    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*$/) {
+		$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; }
+	$sectioncount{$section}++;
+    }
+    return %sectioncount;
+}
+
+###############################################
+
+=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 ($i<$#lines) {
-		    $i++;
-		    $lines[$i]=~
-		/Content\-Disposition\:\s*form\-data\;\s*name\=\"([^\"]+)\"/i;
-		    $name=$1;
-		    $value='';
-		    if ($lines[$i]=~/filename\=\"([^\"]+)\"/i) {
-			$fname=$1;
-			if 
-                            ($lines[$i+1]=~/Content\-Type\:\s*([\w\-\/]+)/i) {
-				$fmime=$1;
-				$i++;
-			    } else {
-				$fmime='';
-			    }
-		    } else {
-			$fname='';
-			$fmime='';
+            } 
+            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;
 		    }
-		    $i++;
-		}
-	    } else {
-		$value.=$lines[$i]."\n";
-	    }
+                }
+                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_secgrprole_info {
+    my ($cdom,$cnum,$needroles,$type)  = @_;
+    my %sections_count = &get_sections($cdom,$cnum);
+    my @sections =  (sort {$a <=> $b} keys(%sections_count));
+    my %curr_groups = &Apache::longroup::coursegroups($cdom,$cnum);
+    my @groups = sort(keys(%curr_groups));
+    my $allroles = [];
+    my $rolehash;
+    my $accesshash = {
+                     active => 'Currently has access',
+                     future => 'Will have future access',
+                     previous => 'Previously had access',
+                  };
+    if ($needroles) {
+        $rolehash = {'all' => 'all'};
+        my %user_roles = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum);
+	if (&Apache::lonnet::error(%user_roles)) {
+	    undef(%user_roles);
 	}
+        foreach my $item (keys(%user_roles)) {
+            my ($role)=split(/\:/,$item,2);
+            if ($role eq 'cr') { next; }
+            if ($role =~ /^cr/) {
+                $$rolehash{$role} = (split('/',$role))[3];
+            } else {
+                $$rolehash{$role} = &Apache::lonnet::plaintext($role,$type);
+            }
+        }
+        foreach my $key (sort(keys(%{$rolehash}))) {
+            push(@{$allroles},$key);
+        }
+        push (@{$allroles},'st');
+        $$rolehash{'st'} = &Apache::lonnet::plaintext('st',$type);
     }
-    $ENV{'request.method'}=$ENV{'REQUEST_METHOD'};
-    $r->method_number(M_GET);
-    $r->method('GET');
-    $r->headers_in->unset('Content-length');
+    return (\@sections,\@groups,$allroles,$rolehash,$accesshash);
 }
 
 =pod
 
 =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
 
 sub get_unprocessed_cgi {
   my ($query,$possible_names)= @_;
   # $Apache::lonxml::debug=1;
-  foreach (split(/&/,$query)) {
-    my ($name, $value) = split(/=/,$_);
-    $name = &Apache::lonnet::unescape($name);
+  foreach my $pair (split(/&/,$query)) {
+    my ($name, $value) = split(/=/,$pair);
+    $name = &unescape($name);
     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) };
     }
   }
 }
@@ -2792,7 +4191,7 @@ returns cache-controlling header code
 =cut
 
 sub cacheheader {
-    unless ($ENV{'request.method'} eq 'GET') { return ''; }
+    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" />
@@ -2811,7 +4210,7 @@ specifies header code to not have cache
 sub no_cache {
     my ($r) = @_;
     if ($ENV{'REQUEST_METHOD'} ne 'GET' &&
-	$ENV{'request.method'} ne 'GET') { return ''; }
+	$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);
@@ -2820,6 +4219,11 @@ sub no_cache {
 
 sub content_type {
     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;
     }
@@ -2835,7 +4239,7 @@ sub content_type {
 
 =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.
 
@@ -2843,18 +4247,18 @@ 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;
   }
 }
 
@@ -2862,7 +4266,7 @@ sub add_to_env {
 
 =item * get_env_multiple($name) 
 
-gets $name from the %ENV hash, it seemlessly handles the cases where multiple
+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
@@ -2872,12 +4276,12 @@ returns an array of values
 sub get_env_multiple {
     my ($name) = @_;
     my @values;
-    if (defined($ENV{$name})) {
+    if (defined($env{$name})) {
         # exists is it an array
-        if (ref($ENV{$name})) {
-            @values=@{ $ENV{$name} };
+        if (ref($env{$name})) {
+            @values=@{ $env{$name} };
         } else {
-            $values[0]=$ENV{$name};
+            $values[0]=$env{$name};
         }
     }
     return(@values);
@@ -2895,25 +4299,25 @@ sub get_env_multiple {
 =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 $datafile = $r->dir_config('lonDaemons').
                            '/tmp/'.$datatoken.'.tmp';
         if ( open(my $fh,">$datafile") ) {
-            print $fh $ENV{'form.upfile'};
+            print $fh $env{'form.upfile'};
             close($fh);
         }
     }
@@ -2925,8 +4329,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
 
@@ -2935,13 +4339,13 @@ sub load_tmp_file {
     my @studentdata=();
     {
         my $studentfile = $r->dir_config('lonDaemons').
-                              '/tmp/'.$ENV{'form.datatoken'}.'.tmp';
+                              '/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
@@ -2950,14 +4354,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;
     }
 }
 
@@ -2965,30 +4374,33 @@ 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=$_;
+        foreach my $field (split(/\s+/,$record)) {
             $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)) {
-            my $field=$_;
+        foreach my $field (split(/\t/,$record)) {
             $field=~s/^(\"|\')//;
             $field=~s/(\"|\')$//;
-            $components{$i}=$field;
+            $components{&takeleft($i)}=$field;
             $i++;
         }
     } else {
@@ -3006,7 +4418,7 @@ sub record_sep {
                 $field=~s/^\s*$delimiter//;
                 $field=~s/$delimiter\s*$//;
             }
-            $components{$i}=$field;
+            $components{&takeleft($i)}=$field;
 	    $i++;
         }
     }
@@ -3043,6 +4455,22 @@ sub upfile_select_html {
     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;
+}
+
 ######################################################
 ######################################################
 
@@ -3060,20 +4488,17 @@ Apache Request ref, $records is an array
 ######################################################
 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(&mt('Samples').'<br /><table border="2"><tr>');
-    foreach (sort({$a <=> $b} keys(%sone))) { 
-        $r->print('<th>'.&mt('Column&nbsp;[_1]',($_+1)).'</th>'); }
+    foreach my $sample (sort({$a <=> $b} keys(%{ $samples->[0] }))) { 
+        $r->print('<th>'.&mt('Column&nbsp;[_1]',($sample+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 my $sample (sort({$a <=> $b} keys(%{ $samples->[0] }))) {
 	    $r->print('<td>');
-	    if (defined($$hash{$_})) { $r->print($$hash{$_}); }
+	    if (defined($$hash{$sample})) { $r->print($$hash{$sample}); }
 	    $r->print('</td>');
 	}
 	$r->print('</tr>');
@@ -3100,23 +4525,23 @@ $d is an array of 2 element arrays (inte
 ######################################################
 sub csv_print_select_table {
     my ($r,$records,$d) = @_;
-    my $i=0;my %sone;
-    %sone=&record_sep($$records[0]);
+    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,$defaultcol)=@{ $_ };
+    foreach my $array_ref (@$d) {
+	my ($value,$display,$defaultcol)=@{ $array_ref };
 	$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="'.$_.'"'.
-                      ($_ eq $defaultcol ? ' selected ' : '').
-                      '>Column '.($_+1).'</option>');
+	foreach my $sample (sort({$a <=> $b} keys(%{ $samples->[0] }))) {
+	    $r->print('<option value="'.$sample.'"'.
+                      ($sample eq $defaultcol ? ' selected="selected" ' : '').
+                      '>Column '.($sample+1).'</option>');
 	}
 	$r->print('</select></td></tr>'."\n");
 	$i++;
@@ -3144,28 +4569,27 @@ $d is an array of 2 element arrays (inte
 ######################################################
 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>');
-    %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) {
+
+    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,$defaultcol)=@{ $_ };
+	foreach my $option (@$d) {
+	    my ($value,$display,$defaultcol)=@{ $option };
 	    $r->print('<option value="'.$value.'"'.
-                      ($i eq $defaultcol ? ' selected ':'').'>'.
+                      ($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++;
     }
@@ -3254,7 +4678,7 @@ the routine &Apache::lonnet::transfer_pr
 my $uniq=0;
 sub get_cgi_id {
     $uniq=($uniq+1)%100000;
-    return (time.'_'.$uniq);
+    return (time.'_'.$$.'_'.$uniq);
 }
 
 ############################################################
@@ -3290,6 +4714,9 @@ they are plotted.  If undefined, default
 =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:
@@ -3375,9 +4802,9 @@ sub DrawBarGraph {
     $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.'.title'}    = &escape($Title);
+    $ValuesHash{$id.'.xlabel'}   = &escape($xlabel);
+    $ValuesHash{$id.'.ylabel'}   = &escape($ylabel);
     $ValuesHash{$id.'.y_max_value'} = $Max;
     $ValuesHash{$id.'.NumBars'}  = $NumBars;
     $ValuesHash{$id.'.NumSets'}  = $NumSets;
@@ -3457,9 +4884,9 @@ sub DrawXYGraph {
     $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.'.title'  => &escape($Title),
+         $id.'.xlabel' => &escape($xlabel),
+         $id.'.ylabel' => &escape($ylabel),
          $id.'.y_max_value'=> $Max,
          $id.'.labels'     => join(',',@$Xlabels),
          $id.'.PlotType'   => 'XY',
@@ -3554,9 +4981,9 @@ sub DrawXYYGraph {
     $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.'.title'  => &escape($Title),
+         $id.'.xlabel' => &escape($xlabel),
+         $id.'.ylabel' => &escape($ylabel),
          $id.'.labels' => join(',',@$Xlabels),
          $id.'.PlotType' => 'XY',
          $id.'.NumSets' => 2,
@@ -3628,7 +5055,7 @@ Inputs:
 sub chartlink {
     my ($linktext, $sname, $sdomain) = @_;
     my $link = '<a href="/adm/statistics?reportSelected=student_assessment'.
-        '&amp;SelectedStudent='.&Apache::lonnet::escape($sname.':'.$sdomain).
+        '&amp;SelectedStudent='.&escape($sname.':'.$sdomain).
         '&amp;chartoutputmode='.HTML::Entities::encode('html, with all links').
        '">'.$linktext.'</a>';
 }
@@ -3658,6 +5085,7 @@ a hash ref describing the data to be sto
     'chartoutputmode' => 'scalar',
     'chartoutputdata' => 'scalar',
     'Section' => 'array',
+    'Group' => 'array',
     'StudentData' => 'array',
     'Maps' => 'array');
 
@@ -3670,34 +5098,35 @@ Returns: both routines return nothing
 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 $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 = 'internal.'.$prefix.'.'.$setting;
-        my $envname = 'course.'.$courseid.'.'.$basename;
-        if (exists($ENV{'form.'.$setting})) {
+        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};
+                (! 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})) {
+                if (ref($env{'form.'.$setting})) {
                     $stored_form = join(',',
                                         map {
-                                            &Apache::lonnet::escape($_);
-                                        } sort(@{$ENV{'form.'.$setting}}));
+                                            &escape($_);
+                                        } sort(@{$env{'form.'.$setting}}));
                 } else {
                     $stored_form = 
-                        &Apache::lonnet::escape($ENV{'form.'.$setting});
+                        &escape($env{'form.'.$setting});
                 }
                 # Determine if the array contents are the same.
-                if ($stored_form ne $ENV{$envname}) {
+                if ($stored_form ne $env{$envname}) {
                     $SaveHash{$basename} = $stored_form;
                     $AppHash{$envname}   = $stored_form;
                 }
@@ -3705,8 +5134,7 @@ sub store_course_settings {
         }
     }
     my $put_result = &Apache::lonnet::put('environment',\%SaveHash,
-                                          $coursedom,
-                                          $ENV{'course.'.$courseid.'.num'});
+                                          $udom,$uname);
     if ($put_result !~ /^(ok|delayed)/) {
         &Apache::lonnet::logthis('unable to save form parameters, '.
                                  'got error:'.$put_result);
@@ -3717,20 +5145,20 @@ sub store_course_settings {
 }
 
 sub restore_course_settings {
-    my $courseid = $ENV{'request.course.id'};
+    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.
+        next if (exists($env{'form.'.$setting}));
+        my $envname = 'environment.internal.'.$courseid.'.'.$prefix.
             '.'.$setting;
-        if (exists($ENV{$envname})) {
+        if (exists($env{$envname})) {
             if ($type eq 'scalar') {
-                $ENV{'form.'.$setting} = $ENV{$envname};
+                $env{'form.'.$setting} = $env{$envname};
             } elsif ($type eq 'array') {
-                $ENV{'form.'.$setting} = [ 
+                $env{'form.'.$setting} = [ 
                                            map { 
-                                               &Apache::lonnet::unescape($_); 
-                                           } split(',',$ENV{$envname})
+                                               &unescape($_); 
+                                           } split(',',$env{$envname})
                                            ];
             }
         }
@@ -3740,15 +5168,17 @@ sub restore_course_settings {
 ############################################################
 ############################################################
 
-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 course_type {
+    my ($cid) = @_;
+    if (!defined($cid)) {
+        $cid = $env{'request.course.id'};
+    }
+    if (defined($env{'course.'.$cid.'type'})) {
+        return $env{'course.'.$cid.'type'};
+    } else {
+        return 'Course';
+    }
+}
 
 sub icon {
     my ($file)=@_;
@@ -3763,7 +5193,7 @@ sub icon {
 		$curfext.".gif";
 	}
     }
-    return $iconname;
+    return &lonhttpdurl($iconname);
 } 
 
 sub lonhttpdurl {
@@ -3801,8 +5231,8 @@ sub escape_double {
 #   Escapes the last element of a full URL.
 sub escape_url {
     my ($url)   = @_;
-    my @urlslices = split(/\//, $url);
-    my $lastitem = &Apache::lonnet::escape(pop(@urlslices));
+    my @urlslices = split(/\//, $url,-1);
+    my $lastitem = &escape(pop(@urlslices));
     return join('/',@urlslices).'/'.$lastitem;
 }
 =pod