--- loncom/interface/loncommon.pm	2002/04/22 18:04:19	1.33
+++ loncom/interface/loncommon.pm	2002/07/12 14:36:16	1.46
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.33 2002/04/22 18:04:19 matthew Exp $
+# $Id: loncommon.pm,v 1.46 2002/07/12 14:36:16 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -33,40 +33,112 @@
 # 12/25,12/28 Gerd Kortemeyer
 # YEAR=2002
 # 1/4 Gerd Kortemeyer
+# 6/24,7/2 H. K. Ng
 
 # Makes a table out of the previous attempts
 # Inputs result_from_symbread, user, domain, course_id
 # Reads in non-network-related .tab files
 
+# POD header:
+
+=pod
+
+=head1 NAME
+
+Apache::loncommon - pile of common routines
+
+=head1 SYNOPSIS
+
+Referenced by other mod_perl Apache modules.
+
+Invocation:
+ &Apache::loncommon::SUBROUTINENAME(ARGUMENTS);
+
+=head1 INTRODUCTION
+
+Common collection of used subroutines.  This collection helps remove
+redundancy from other modules and increase efficiency of memory usage.
+
+Current things done:
+
+ Makes a table out of the previous homework attempts
+ Inputs result_from_symbread, user, domain, course_id
+ Reads in non-network-related .tab files
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+=head2 General Subroutines
+
+=over 4
+
+=cut 
+
+# End of POD header
 package Apache::loncommon;
 
 use strict;
 use Apache::lonnet();
+use GDBM_File;
 use POSIX qw(strftime);
 use Apache::Constants qw(:common);
 use Apache::lonmsg();
-
 my $readit;
 
+=pod 
+
+=item Global Variables
+
+=over 4
+
+=cut
 # ----------------------------------------------- Filetypes/Languages/Copyright
 my %language;
 my %cprtag;
 my %fe; my %fd;
-my %fc;
+my %category_extensions;
+
+# ---------------------------------------------- Thesaurus variables
 
-# -------------------------------------------------------------- Thesaurus data
-my @therelated;
-my @theword;
-my @thecount;
-my %theindex;
-my $thetotalcount;
-my $thefuzzy=2;
-my $thethreshold=0.1/$thefuzzy;
-my $theavecount;
+=pod
+
+=item %Keywords  
+
+A hash used by &keyword to determine if a word is considered a keyword.
+
+=item $thesaurus_db_file
+
+Scalar containing the full path to the thesaurus database.                 
+
+=cut
+
+my %Keywords;
+my $thesaurus_db_file;
+
+
+=pod
+
+=back
+
+=cut
 
 # ----------------------------------------------------------------------- BEGIN
-BEGIN {
 
+=pod
+
+=item BEGIN() 
+
+Initialize values from language.tab, copyright.tab, filetypes.tab,
+thesaurus.tab, and filecategories.tab.
+
+=cut
+
+# ----------------------------------------------------------------------- BEGIN
+
+BEGIN {
+    # Variable initialization
+    $thesaurus_db_file = $Apache::lonnet::perlvar{'lonTabDir'}."/thesaurus.db";
+    #
     unless ($readit) {
 # ------------------------------------------------------------------- languages
     {
@@ -102,8 +174,8 @@ BEGIN {
 	    while (<$fh>) {
 		next if /^\#/;
 		chomp;
-		my ($key,$val)=(split(/\s+/,$_,2));
-		push @{$fc{$key}},$val;
+		my ($extension,$category)=(split(/\s+/,$_,2));
+		push @{$category_extensions{lc($category)}},$extension;
 	    }
 	}
     }
@@ -123,32 +195,372 @@ BEGIN {
 	    }
 	}
     }
-# -------------------------------------------------------------- Thesaurus data
-    {
-	my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}.
-	       '/thesaurus.dat');
-	if ($fh) {
-            while (<$fh>) {
-               my ($tword,$tindex,$tcount,$trelated)=split(/\@/,$_);
-               $theindex{$tword}=$tindex;
-               $theword[$tindex]=$tword;
-               $thecount[$tindex]=$tcount;
-               $thetotalcount+=$tcount;
-               $therelated[$tindex]=$trelated;
-	   }
-        }
-        $theavecount=$thetotalcount/$#thecount;
-    }
     &Apache::lonnet::logthis(
-              "<font color=yellow>INFO: Read file types and thesaurus</font>");
+              "<font color=yellow>INFO: Read file types</font>");
     $readit=1;
-}
+    }  # end of unless($readit) 
     
 }
 # ============================================================= END BEGIN BLOCK
 ###############################################################
+##           HTML and Javascript Helper Functions            ##
+###############################################################
+
+=pod 
+
+=item browser_and_searcher_javascript 
+
+Returns scalar containing javascript to open a browser window
+or a searcher window.  Also creates 
+
+=over 4
+
+=item openbrowser(formname,elementname,only,omit) [javascript]
+
+inputs: formname, elementname, only, omit
+
+formname and elementname indicate the name of the html form and name of
+the element that the results of the browsing selection are to be placed in. 
+
+Specifying 'only' will restrict the browser to displaying only files
+with the given extension.  Can be a comma seperated list.
+
+Specifying 'omit' will restrict the browser to NOT displaying files
+with the given extension.  Can be a comma seperated list.
+
+=item opensearcher(formname, elementname) [javascript]
+
+Inputs: formname, elementname
+
+formname and elementname specify the name of the html form and the name
+of the element the selection from the search results will be placed in.
+
+=back
+
+=cut
+
+###############################################################
+sub browser_and_searcher_javascript {
+    return <<END;
+    var editbrowser;
+    function openbrowser(formname,elementname,only,omit) {
+        var url = '/res/?';
+        if (editbrowser == null) {
+            url += 'launch=1&';
+        }
+        url += 'catalogmode=interactive&';
+        url += 'mode=edit&';
+        url += 'form=' + formname + '&';
+        if (only != null) {
+            url += 'only=' + only + '&';
+        } 
+        if (omit != null) {
+            url += 'omit=' + omit + '&';
+        }
+        url += 'element=' + elementname + '';
+        var title = 'Browser';
+        var options = 'scrollbars=1,resizable=1,menubar=0';
+        options += ',width=700,height=600';
+        editbrowser = open(url,title,options,'1');
+        editbrowser.focus();
+    }
+    var editsearcher;
+    function opensearcher(formname,elementname) {
+        var url = '/adm/searchcat?';
+        if (editsearcher == null) {
+            url += 'launch=1&';
+        }
+        url += 'catalogmode=interactive&';
+        url += 'mode=edit&';
+        url += 'form=' + formname + '&';
+        url += 'element=' + elementname + '';
+        var title = 'Search';
+        var options = 'scrollbars=1,resizable=1,menubar=0';
+        options += ',width=700,height=600';
+        editsearcher = open(url,title,options,'1');
+        editsearcher.focus();
+    }
+END
+}
+
+
+
+###############################################################
+
+=pod
+
+=item linked_select_forms(...)
+
+linked_select_forms returns a string containing a <script></script> block
+and html for two <select> menus.  The select menus will be linked in that
+changing the value of the first menu will result in new values being placed
+in the second menu.  The values in the select menu will appear in alphabetical
+order.
+
+linked_select_forms takes the following ordered inputs:
+
+=over 4
+
+=item $formname, the name of the <form> tag
+
+=item $middletext, the text which appears between the <select> tags
+
+=item $firstdefault, the default value for the first menu
+
+=item $firstselectname, the name of the first <select> tag
+
+=item $secondselectname, the name of the second <select> tag
+
+=item $hashref, a reference to a hash containing the data for the menus.
+
+=back 
+
+Below is an example of such a hash.  Only the 'text', 'default', and 
+'select2' keys must appear as stated.  keys(%menu) are the possible 
+values for the first select menu.  The text that coincides with the 
+first menu value is given in $menu{$choice1}->{'text'}.  The values 
+and text for the second menu are given in the hash pointed to by 
+$menu{$choice1}->{'select2'}.  
+
+my %menu = ( A1 => { text =>"Choice A1" ,
+                      default => "B3",
+                      select2 => { 
+                          B1 => "Choice B1",
+                          B2 => "Choice B2",
+                          B3 => "Choice B3",
+                          B4 => "Choice B4"
+                          }
+                  },
+              A2 => { text =>"Choice A2" ,
+                      default => "C2",
+                      select2 => { 
+                          C1 => "Choice C1",
+                          C2 => "Choice C2",
+                          C3 => "Choice C3"
+                          }
+                  },
+              A3 => { text =>"Choice A3" ,
+                      default => "D6",
+                      select2 => { 
+                          D1 => "Choice D1",
+                          D2 => "Choice D2",
+                          D3 => "Choice D3",
+                          D4 => "Choice D4",
+                          D5 => "Choice D5",
+                          D6 => "Choice D6",
+                          D7 => "Choice D7"
+                          }
+                  }
+              );
+
+=cut
+
+# ------------------------------------------------
+
+sub linked_select_forms {
+    my ($formname,
+        $middletext,
+        $firstdefault,
+        $firstselectname,
+        $secondselectname, 
+        $hashref
+        ) = @_;
+    my $second = "document.$formname.$secondselectname";
+    my $first = "document.$formname.$firstselectname";
+    # output the javascript to do the changing
+    my $result = '';
+    $result.="<script>\n";
+    $result.="var select2data = new Object();\n";
+    $" = '","';
+    my $debug = '';
+    foreach my $s1 (sort(keys(%$hashref))) {
+        $result.="select2data.d_$s1 = new Object();\n";        
+        $result.="select2data.d_$s1.def = new String('".
+            $hashref->{$s1}->{'default'}."');\n";
+        $result.="select2data.d_$s1.values = new Array(";        
+        my @s2values = sort(keys( %{ $hashref->{$s1}->{'select2'} } ));
+        $result.="\"@s2values\");\n";
+        $result.="select2data.d_$s1.texts = new Array(";        
+        my @s2texts;
+        foreach my $value (@s2values) {
+            push @s2texts, $hashref->{$s1}->{'select2'}->{$value};
+        }
+        $result.="\"@s2texts\");\n";
+    }
+    $"=' ';
+    $result.= <<"END";
+
+function select1_changed() {
+    // Determine new choice
+    var newvalue = "d_" + $first.value;
+    // update select2
+    var values     = select2data[newvalue].values;
+    var texts      = select2data[newvalue].texts;
+    var select2def = select2data[newvalue].def;
+    var i;
+    // out with the old
+    for (i = 0; i < $second.options.length; i++) {
+        $second.options[i] = null;
+    }
+    // in with the nuclear
+    for (i=0;i<values.length; i++) {
+        $second.options[i] = new Option(values[i]);
+        $second.options[i].text = texts[i];
+        if (values[i] == select2def) {
+            $second.options[i].selected = true;
+        }
+    }
+}
+</script>
+END
+    # output the initial values for the selection lists
+    $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.=">$hashref->{$value}->{'text'}</option>\n";
+    }
+    $result .= "</select>\n";
+    my %select2 = %{$hashref->{$firstdefault}->{'select2'}};
+    $result .= $middletext;
+    $result .= "<select size=\"1\" name=\"$secondselectname\">\n";
+    my $seconddefault = $hashref->{$firstdefault}->{'default'};
+    foreach my $value (sort(keys(%select2))) {
+        $result.="    <option value=\"$value\" ";        
+        $result.=" selected=\"true\" " if ($value eq $seconddefault);
+        $result.=">$select2{$value}</option>\n";
+    }
+    $result .= "</select>\n";
+    #    return $debug;
+    return $result;
+}   #  end of sub linked_select_forms {
+
+###############################################################
+
+=pod
+
+=item help_open_topic($topic, $stayOnPage, $width, $height)
+
+Returns a string corresponding to an HTML link to the given help $topic, where $topic corresponds to the name of a .tex file in /home/httpd/html/adm/help/tex, with underscores replaced by spaces.
+
+$stayOnPage is a value that will be interpreted as a boolean. If true, the link will not open a new window. If false, the link will open a new window using Javascript. (Default is false.)
+
+$width and $height are optional numerical parameters that will override the width and height of the popped up window, which may be useful for certain help topics with big pictures included.
+
+=cut
+
+sub help_open_topic {
+    my ($topic, $stayOnPage, $width, $height) = @_;
+    $stayOnPage = 0 if (not defined $stayOnPage);
+    $width = 350 if (not defined $width);
+    $height = 400 if (not defined $height);
+    my $filename = $topic;
+    $filename =~ s/ /_/g;
+
+    my $template;
+
+    if (!$stayOnPage)
+    {
+        $template = <<"ENDTEMPLATE";
+<a href="javascript:void(open('/adm/help/${filename}.hlp', 'Help for $topic', 'menubar=0,s
+crollbars=1,width=$width,height=$height'))"><image
+  src="/adm/help/gif/smallHelp.gif"
+  border="0" alt="(Help: $topic)"></a>
+ENDTEMPLATE
+    }
+    else
+    {
+        $template = <<"ENDTEMPLATE";
+<a href="/adm/help/${filename}.hlp"><image
+  src="/adm/help/gif/smallHelp.gif"
+  border="0" alt="(Help: $topic)"></a>
+ENDTEMPLATE
+    }
+
+    return $template;
+
+}
+
+=pod
+
+=item csv_translate($text) 
+
+Translate $text to allow it to be output as a 'comma seperated values' 
+format.
+
+=cut
+
+sub csv_translate {
+    my $text = shift;
+    $text =~ s/\"/\"\"/g;
+    $text =~ s/\n//g;
+    return $text;
+}
+
+###############################################################
 ##        Home server <option> list generating code          ##
 ###############################################################
+#-------------------------------------------
+
+=pod
+
+=item get_domains()
+
+Returns an array containing each of the domains listed in the hosts.tab
+file.
+
+=cut
+
+#-------------------------------------------
+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{$_}++;
+    }
+    return @domains;
+}
+
+#-------------------------------------------
+
+=pod
+
+=item select_dom_form($defdom,$name)
+
+Returns a string containing a <select name='$name' size='1'> form to 
+allow a user to select the domain to preform an operation in.  
+See loncreateuser.pm for an example invocation and use.
+
+=cut
+
+#-------------------------------------------
+sub select_dom_form {
+    my ($defdom,$name) = @_;
+    my @domains = get_domains();
+    my $selectdomain = "<select name=\"$name\" size=\"1\">\n";
+    foreach (@domains) {
+        $selectdomain.="<option value=\"$_\" ".
+            ($_ eq $defdom ? 'selected' : '').
+                ">$_</option>\n";
+    }
+    $selectdomain.="</select>";
+    return $selectdomain;
+}
+
+#-------------------------------------------
+
+=pod
+
+=item get_home_servers($domain)
+
+Returns a hash which contains keys like '103l3' and values like 
+'kirk.lite.msu.edu'.  All of the keys will be for machines in the
+given $domain.
+
+=cut
+
+#-------------------------------------------
 sub get_home_servers {
     my $domain = shift;
     my %home_servers;
@@ -160,6 +572,18 @@ sub get_home_servers {
     return %home_servers;
 }
 
+#-------------------------------------------
+
+=pod
+
+=item home_server_option_list($domain)
+
+returns a string which contains an <option> list to be used in a 
+<select> form input.  See loncreateuser.pm for an example.
+
+=cut
+
+#-------------------------------------------
 sub home_server_option_list {
     my $domain = shift;
     my %servers = &get_home_servers($domain);
@@ -182,6 +606,37 @@ sub home_server_option_list {
 ## hash, and have reasonable default values.
 ##
 ##    formname = the name given in the <form> tag.
+#-------------------------------------------
+
+=pod
+
+=item authform_xxxxxx
+
+The authform_xxxxxx subroutines provide javascript and html forms which 
+handle some of the conveniences required for authentication forms.  
+This is not an optimal method, but it works.  
+
+See loncreateuser.pm for invocation and use examples.
+
+=over 4
+
+=item authform_header
+
+=item authform_authorwarning
+
+=item authform_nochange
+
+=item authform_kerberos
+
+=item authform_internal
+
+=item authform_filesystem
+
+=back
+
+=cut
+
+#-------------------------------------------
 sub authform_header{  
     my %in = (
         formname => 'cu',
@@ -356,66 +811,166 @@ END
 ##   End Authentication changing form generation functions   ##
 ###############################################################
 
+###############################################################
+##                Thesaurus Functions                        ##
+###############################################################
 
+=pod
 
-# ---------------------------------------------------------- Is this a keyword?
+=item initialize_keywords
 
-sub keyword {
-    my $newword=shift;
-    $newword=~s/\W//g;
-    $newword=~tr/A-Z/a-z/;
-    my $tindex=$theindex{$newword};
-    if ($tindex) {
-        if ($thecount[$tindex]>$theavecount) {
-           return 1;
-        }
+Initializes the package variable %Keywords if it is empty.  Uses the
+package variable $thesaurus_db_file.
+
+=cut
+
+###################################################
+
+sub initialize_keywords {
+    return 1 if (scalar keys(%Keywords));
+    # If we are here, %Keywords is empty, so fill it up
+    #   Make sure the file we need exists...
+    if (! -e $thesaurus_db_file) {
+        &Apache::lonnet::logthis("Attempt to access $thesaurus_db_file".
+                                 " failed because it does not exist");
+        return 0;
+    }
+    #   Set up the hash as a database
+    my %thesaurus_db;
+    if (! tie(%thesaurus_db,'GDBM_File',
+              $thesaurus_db_file,&GDBM_READER,0640)){
+        &Apache::lonnet::logthis("Could not tie \%thesaurus_db to ".
+                                 $thesaurus_db_file);
+        return 0;
+    } 
+    #  Get the average number of appearances of a word.
+    my $avecount = $thesaurus_db{'average.count'};
+    #  Put keywords (those that appear > average) into %Keywords
+    while (my ($word,$data)=each (%thesaurus_db)) {
+        my ($count,undef) = split /:/,$data;
+        $Keywords{$word}++ if ($count > $avecount);
+    }
+    untie %thesaurus_db;
+    # Remove special values from %Keywords.
+    foreach ('total.count','average.count') {
+        delete($Keywords{$_}) if (exists($Keywords{$_}));
     }
-    return 0;
+    return 1;
+}
+
+###################################################
+
+=pod
+
+=item keyword($word)
+
+Returns true if $word is a keyword.  A keyword is a word that appears more 
+than the average number of times in the thesaurus database.  Calls 
+&initialize_keywords
+
+=cut
+
+###################################################
+
+sub keyword {
+    return if (!&initialize_keywords());
+    my $word=lc(shift());
+    $word=~s/\W//g;
+    return exists($Keywords{$word});
 }
+
+###################################################
+#         Old code, to be removed soon            #
+###################################################
 # -------------------------------------------------------- Return related words
+#sub related {
+#    my $newword=shift;
+#    $newword=~s/\W//g;
+#    $newword=~tr/A-Z/a-z/;
+#    my $tindex=$theindex{$newword};
+#    if ($tindex) {
+#        my %found=();
+#        foreach (split(/\,/,$therelated[$tindex])) {
+## - Related word found
+#            my ($ridx,$rcount)=split(/\:/,$_);
+## - Direct relation index
+#            my $directrel=$rcount/$thecount[$tindex];
+#            if ($directrel>$thethreshold) {
+#               foreach (split(/\,/,$therelated[$ridx])) {
+#                  my ($rridx,$rrcount)=split(/\:/,$_);
+#                  if ($rridx==$tindex) {
+## - Determine reverse relation index
+#                     my $revrel=$rrcount/$thecount[$ridx];
+## - Calculate full index
+#                     $found{$ridx}=$directrel*$revrel;
+#                     if ($found{$ridx}>$thethreshold) {
+#                        foreach (split(/\,/,$therelated[$ridx])) {
+#                            my ($rrridx,$rrrcount)=split(/\:/,$_);
+#                            unless ($found{$rrridx}) {
+#                               my $revrevrel=$rrrcount/$thecount[$ridx];
+#                               if (
+#                          $directrel*$revrel*$revrevrel>$thethreshold
+#                               ) {
+#                                  $found{$rrridx}=
+#                                       $directrel*$revrel*$revrevrel;
+#                               }
+#                            }
+#                        }
+#                     }
+#                  }
+#               }
+#            }
+#        }
+#    }
+#    return ();
+#}
 
-sub related {
-    my $newword=shift;
-    $newword=~s/\W//g;
-    $newword=~tr/A-Z/a-z/;
-    my $tindex=$theindex{$newword};
-    if ($tindex) {
-        my %found=();
-        foreach (split(/\,/,$therelated[$tindex])) {
-# - Related word found
-            my ($ridx,$rcount)=split(/\:/,$_);
-# - Direct relation index
-            my $directrel=$rcount/$thecount[$tindex];
-            if ($directrel>$thethreshold) {
-               foreach (split(/\,/,$therelated[$ridx])) {
-                  my ($rridx,$rrcount)=split(/\:/,$_);
-                  if ($rridx==$tindex) {
-# - Determine reverse relation index
-                     my $revrel=$rrcount/$thecount[$ridx];
-# - Calculate full index
-                     $found{$ridx}=$directrel*$revrel;
-                     if ($found{$ridx}>$thethreshold) {
-                        foreach (split(/\,/,$therelated[$ridx])) {
-                            my ($rrridx,$rrrcount)=split(/\:/,$_);
-                            unless ($found{$rrridx}) {
-                               my $revrevrel=$rrrcount/$thecount[$ridx];
-                               if (
-                          $directrel*$revrel*$revrevrel>$thethreshold
-                               ) {
-                                  $found{$rrridx}=
-                                       $directrel*$revrel*$revrevrel;
-                               }
-                            }
-                        }
-                     }
-                  }
-               }
-            }
+###############################################################
+
+=pod 
+
+=item get_related_words
+
+Look up a word in the thesaurus.  Takes a scalar arguement and returns
+an array of words.  If the keyword is not in the thesaurus, an empty array
+will be returned.  The order of the words returned is determined by the
+database which holds them.
+
+Uses global $thesaurus_db_file.
+
+=cut
+
+###############################################################
+
+sub get_related_words {
+    my $keyword = shift;
+    my %thesaurus_db;
+    if (! -e $thesaurus_db_file) {
+        &Apache::lonnet::logthis("Attempt to access $thesaurus_db_file ".
+                                 "failed because the file does not exist");
+        return ();
+    }
+    if (! tie(%thesaurus_db,'GDBM_File',
+              $thesaurus_db_file,&GDBM_READER,0640)){
+        return ();
+    } 
+    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.
+        for (my $i=0;$i<=$#Words;$i++) {
+            ($Words[$i],undef)= split/\,/,$Words[$i];
         }
     }
-    return ();
+    untie %thesaurus_db;
+    return @Words;
 }
 
+###############################################################
+##              End Thesaurus Functions                      ##
+###############################################################
+
 # ---------------------------------------------------------------- Language IDs
 sub languageids {
     return sort(keys(%language));
@@ -438,12 +993,12 @@ sub copyrightdescription {
 
 # ------------------------------------------------------------- File Categories
 sub filecategories {
-    return sort(keys(%fc));
+    return sort(keys(%category_extensions));
 }
 
 # -------------------------------------- File Types within a specified category
 sub filecategorytypes {
-    return @{$fc{lc(shift(@_))}};
+    return @{$category_extensions{lc($_[0])}};
 }
 
 # ------------------------------------------------------------------ File Types
@@ -467,9 +1022,22 @@ sub filedescriptionex {
     return '.'.$ex.' '.$fd{lc($ex)};
 }
 
+# ---- Retrieve attempts by students
+# input
+# $symb             - problem including path
+# $username,$domain - that of the student
+# $course           - course name
+# $getattempt       - leave blank if want all attempts, else put something.
+# $regexp           - regular expression. If string matches regexp send to
+# $gradesub         - routine that process the string if it matches regexp
+# 
+# output
+# formatted as a table all the attempts, if any.
+#
 sub get_previous_attempt {
-  my ($symb,$username,$domain,$course)=@_;
+  my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_;
   my $prevattempts='';
+  no strict 'refs';
   if ($symb) {
     my (%returnhash)=
       &Apache::lonnet::restore($symb,$course,$domain,$username);
@@ -481,30 +1049,37 @@ sub get_previous_attempt {
 	  $lasthash{$_}=$returnhash{$version.':'.$_};
         }
       }
-      $prevattempts='<table border=2></tr><th>History</th>';
+      $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(/\./,$_);
-	if (@parts) {
+	if ($#parts > 0) {
 	  my $data=$parts[-1];
 	  pop(@parts);
-	  $prevattempts.='<th>Part '.join('.',@parts).'<br />'.$data.'</th>';
+	  $prevattempts.='<td>Part '.join('.',@parts).'<br />'.$data.'&nbsp;</td>';
 	} else {
-	  $prevattempts.='<th>'.$ign.'</th>';
-	}
-      }
-      for ($version=1;$version<=$returnhash{'version'};$version++) {
-        $prevattempts.='</tr><tr><th>Attempt '.$version.'</th>';
-        foreach (sort(keys %lasthash)) {
-	  my $value;
-	  if ($_ =~ /timestamp/) {
-	    $value=scalar(localtime($returnhash{$version.':'.$_}));
+	  if ($#parts == 0) {
+	    $prevattempts.='<th>'.$parts[0].'</th>';
 	  } else {
-	    $value=$returnhash{$version.':'.$_};
+	    $prevattempts.='<th>'.$ign.'</th>';
 	  }
-	  $prevattempts.='<td>'.$value.'</td>';   
-        }
+	}
+      }
+      if ($getattempt eq '') {
+	for ($version=1;$version<=$returnhash{'version'};$version++) {
+	  $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Transaction '.$version.'</td>';
+	    foreach (sort(keys %lasthash)) {
+	       my $value;
+	       if ($_ =~ /timestamp/) {
+		  $value=scalar(localtime($returnhash{$version.':'.$_}));
+	       } else {
+		  $value=$returnhash{$version.':'.$_};
+	       }
+	       $prevattempts.='<td>'.$value.'&nbsp;</td>';   
+	    }
+	 }
       }
-      $prevattempts.='</tr><tr><th>Current</th>';
+      $prevattempts.='</tr><tr bgcolor="#ffffe6"><td>Current</td>';
       foreach (sort(keys %lasthash)) {
 	my $value;
 	if ($_ =~ /timestamp/) {
@@ -512,9 +1087,10 @@ sub get_previous_attempt {
 	} else {
 	  $value=$lasthash{$_};
 	}
-	$prevattempts.='<td>'.$value.'</td>';
+	if ($_ =~/$regexp$/) {$value = &$gradesub($value)}
+	$prevattempts.='<td>'.$value.'&nbsp;</td>';
       }
-      $prevattempts.='</tr></table>';
+      $prevattempts.='</tr></table></td></tr></table>';
     } else {
       $prevattempts='Nothing submitted - no attempts.';
     }
@@ -564,16 +1140,13 @@ sub get_student_answers {
   foreach my $element (@elements) {
     $ENV{'form.grade_'.$element}=$old{$element};
   }
-  $userview=~s/\<body[^\>]*\>//gi;
-  $userview=~s/\<\/body\>//gi;
-  $userview=~s/\<html\>//gi;
-  $userview=~s/\<\/html\>//gi;
-  $userview=~s/\<head\>//gi;
-  $userview=~s/\<\/head\>//gi;
-  $userview=~s/action\s*\=/would_be_action\=/gi;
   return $userview;
 }
 
+###############################################
+
+###############################################
+
 sub get_unprocessed_cgi {
   my ($query,$possible_names)= @_;
   # $Apache::lonxml::debug=1;
@@ -624,11 +1197,21 @@ sub add_to_env {
   }
 }
 
-#---CSV Upload/Handling functions
+=pod
+
+=back 
 
-# ========================================================= Store uploaded file
-# needs $ENV{'form.upfile'}
-# return $datatoken to be put into hidden field
+=head2 CSV Upload/Handling functions
+
+=over 4
+
+=item  upfile_store($r)
+
+Store uploaded file, $r should be the HTTP Request object,
+needs $ENV{'form.upfile'}
+returns $datatoken to be put into hidden field
+
+=cut
 
 sub upfile_store {
     my $r=shift;
@@ -647,9 +1230,13 @@ sub upfile_store {
     return $datatoken;
 }
 
-# ================================================= Load uploaded file from tmp
-# needs $ENV{'form.datatoken'}
-# sets $ENV{'form.upfile'} to the contents of the file
+=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
+
+=cut
 
 sub load_tmp_file {
     my $r=shift;
@@ -664,10 +1251,13 @@ sub load_tmp_file {
     $ENV{'form.upfile'}=join('',@studentdata);
 }
 
-# ========================================= Separate uploaded file into records
-# returns array of records
-# needs $ENV{'form.upfile'}
-# needs $ENV{'form.upfiletype'}
+=item upfile_record_sep()
+
+Separate uploaded file into records
+returns array of records,
+needs $ENV{'form.upfile'} and $ENV{'form.upfiletype'}
+
+=cut
 
 sub upfile_record_sep {
     if ($ENV{'form.upfiletype'} eq 'xml') {
@@ -676,9 +1266,12 @@ sub upfile_record_sep {
     }
 }
 
-# =============================================== Separate a record into fields
-# needs $ENV{'form.upfiletype'}
-# takes $record as arg
+=item record_sep($record)
+
+Separate a record into fields $record should be an item from the upfile_record_sep(), needs $ENV{'form.upfiletype'}
+
+=cut
+
 sub record_sep {
     my $record=shift;
     my %components=();
@@ -723,7 +1316,12 @@ sub record_sep {
     return %components;
 }
 
-# =============================== HTML code to select file and specify its type
+=item upfile_select_html()
+
+return HTML code to select file and specify its type
+
+=cut
+
 sub upfile_select_html {
     return (<<'ENDUPFORM');
 <input type="file" name="upfile" size="50">
@@ -736,9 +1334,14 @@ sub upfile_select_html {
 ENDUPFORM
 }
 
-# ===================Prints a table of sample values from each column uploaded
-# $r is an Apache Request ref
-# $records is an arrayref from &Apache::loncommon::upfile_record_sep
+=item csv_print_samples($r,$records)
+
+Prints a table of sample values from each column uploaded $r is an
+Apache Request ref, $records is an arrayref from
+&Apache::loncommon::upfile_record_sep
+
+=cut
+
 sub csv_print_samples {
     my ($r,$records) = @_;
     my (%sone,%stwo,%sthree);
@@ -761,10 +1364,15 @@ sub csv_print_samples {
     $r->print('</tr></table><br />'."\n");
 }
 
-# ======Prints a table to create associations between values and table columns
-# $r is an Apache Request ref
-# $records is an arrayref from &Apache::loncommon::upfile_record_sep
-# $d is an array of 2 element arrays (internal name, displayed name)
+=item csv_print_select_table($r,$records,$d)
+
+Prints a table to create associations between values and table columns.
+$r is an Apache Request ref,
+$records is an arrayref from &Apache::loncommon::upfile_record_sep,
+$d is an array of 2 element arrays (internal name, displayed name)
+
+=cut
+
 sub csv_print_select_table {
     my ($r,$records,$d) = @_;
     my $i=0;my %sone;
@@ -788,11 +1396,16 @@ sub csv_print_select_table {
     return $i;
 }
 
-# ===================Prints a table of sample values from the upload and
-#                      can make associate samples to internal names
-# $r is an Apache Request ref
-# $records is an arrayref from &Apache::loncommon::upfile_record_sep
-# $d is an array of 2 element arrays (internal name, displayed name)
+=item csv_samples_select_table($r,$records,$d)
+
+Prints a table of sample values from the upload and can make associate samples to internal names.
+
+$r is an Apache Request ref,
+$records is an arrayref from &Apache::loncommon::upfile_record_sep,
+$d is an array of 2 element arrays (internal name, displayed name)
+
+=cut
+
 sub csv_samples_select_table {
     my ($r,$records,$d) = @_;
     my %sone; my %stwo; my %sthree;
@@ -823,110 +1436,98 @@ sub csv_samples_select_table {
 1;
 __END__;
 
+=pod
 
-=head1 NAME
-
-Apache::loncommon - pile of common routines
-
-=head1 SYNOPSIS
-
-Referenced by other mod_perl Apache modules.
-
-Invocation:
- &Apache::loncommon::SUBROUTINENAME(ARGUMENTS);
-
-=head1 INTRODUCTION
-
-Common collection of used subroutines.  This collection helps remove
-redundancy from other modules and increase efficiency of memory usage.
+=back
 
-Current things done:
+=head2 Access .tab File Data
 
- Makes a table out of the previous homework attempts
- Inputs result_from_symbread, user, domain, course_id
- Reads in non-network-related .tab files
+=over 4
 
-This is part of the LearningOnline Network with CAPA project
-described at http://www.lon-capa.org.
+=item languageids() 
 
-=head1 HANDLER SUBROUTINE
+returns list of all language ids
 
-There is no handler subroutine.
+=item languagedescription() 
 
-=head1 OTHER SUBROUTINES
+returns description of a specified language id
 
-=over 4
+=item copyrightids() 
 
-=item *
+returns list of all copyrights
 
-BEGIN() : initialize values from language.tab, copyright.tab, filetypes.tab,
-and filecategories.tab.
+=item copyrightdescription() 
 
-=item *
+returns description of a specified copyright id
 
-languageids() : returns list of all language ids
+=item filecategories() 
 
-=item *
+returns list of all file categories
 
-languagedescription() : returns description of a specified language id
+=item filecategorytypes() 
 
-=item *
+returns list of file types belonging to a given file
+category
 
-copyrightids() : returns list of all copyrights
+=item fileembstyle() 
 
-=item *
+returns embedding style for a specified file type
 
-copyrightdescription() : returns description of a specified copyright id
+=item filedescription() 
 
-=item *
+returns description for a specified file type
 
-filecategories() : returns list of all file categories
+=item filedescriptionex() 
 
-=item *
+returns description for a specified file type with
+extra formatting
 
-filecategorytypes() : returns list of file types belonging to a given file
-category
+=back
 
-=item *
+=head2 Alternate Problem Views
 
-fileembstyle() : returns embedding style for a specified file type
+=over 4
 
-=item *
+=item get_previous_attempt() 
 
-filedescription() : returns description for a specified file type
+return string with previous attempt on problem
 
-=item *
+=item get_student_view() 
 
-filedescriptionex() : returns description for a specified file type with
-extra formatting
+show a snapshot of what student was looking at
 
-=item *
+=item get_student_answers() 
 
-get_previous_attempt() : return string with previous attempt on problem
+show a snapshot of how student was answering problem
 
-=item *
+=back
 
-get_student_view() : show a snapshot of what student was looking at
+=head2 HTTP Helper
 
-=item *
+=over 4
 
-get_student_answers() : show a snapshot of how student was answering problem
+=item get_unprocessed_cgi($query,$possible_names)
 
-=item *
+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.
 
-get_unprocessed_cgi() : get unparsed CGI parameters
+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.
 
-=item *
+=item cacheheader() 
 
-cacheheader() : returns cache-controlling header code
+returns cache-controlling header code
 
-=item *
+=item nocache() 
 
-nocache() : specifies header code to not have cache
+specifies header code to not have cache
 
-=item *
+=item add_to_env($name,$value) 
 
-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.