--- loncom/interface/loncommon.pm	2002/03/21 20:24:21	1.27
+++ loncom/interface/loncommon.pm	2002/04/23 21:42:01	1.35
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.27 2002/03/21 20:24:21 albertel Exp $
+# $Id: loncommon.pm,v 1.35 2002/04/23 21:42:01 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -38,6 +38,40 @@
 # Inputs result_from_symbread, user, domain, course_id
 # Reads in non-network-related .tab files
 
+# POD header:
+
+=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 Subroutines
+
+=over 4
+
+=cut 
+
+# End of POD header
 package Apache::loncommon;
 
 use strict;
@@ -65,6 +99,14 @@ my $thethreshold=0.1/$thefuzzy;
 my $theavecount;
 
 # ----------------------------------------------------------------------- BEGIN
+=item BEGIN() 
+
+Initialize values from language.tab, copyright.tab, filetypes.tab,
+and filecategories.tab.
+
+=cut
+# ----------------------------------------------------------------------- BEGIN
+
 BEGIN {
 
     unless ($readit) {
@@ -143,9 +185,313 @@ BEGIN {
               "<font color=yellow>INFO: Read file types and thesaurus</font>");
     $readit=1;
 }
-
+    
 }
 # ============================================================= END BEGIN BLOCK
+###############################################################
+##        Home server <option> list generating code          ##
+###############################################################
+#-------------------------------------------
+
+=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;
+}
+
+#-------------------------------------------
+
+=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;
+}
+
+#-------------------------------------------
+
+=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;
+    foreach (keys(%Apache::lonnet::libserv)) {
+        if ($Apache::lonnet::hostdom{$_} eq $domain) {
+            $home_servers{$_} = $Apache::lonnet::hostname{$_};
+        }
+    }
+    return %home_servers;
+}
+
+#-------------------------------------------
+
+=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);
+    my $result = '';
+    foreach (sort keys(%servers)) {
+        $result.=
+            '<option value="'.$_.'">'.$_.' '.$servers{$_}."</option>\n";
+    }
+    return $result;
+}
+###############################################################
+##    End of home server <option> list generating code       ##
+###############################################################
+
+###############################################################
+##    Authentication changing form generation subroutines    ##
+###############################################################
+##
+## All of the authform_xxxxxxx subroutines take their inputs in a
+## hash, and have reasonable default values.
+##
+##    formname = the name given in the <form> tag.
+#-------------------------------------------
+
+=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',
+        kerb_def_dom => 'MSU.EDU',
+        @_,
+    );
+    $in{'formname'} = 'document.' . $in{'formname'};
+    my $result='';
+    $result.=<<"END";
+var current = new Object();
+current.radiovalue = 'nochange';
+current.argfield = null;
+
+function changed_radio(choice,currentform) {
+    var choicearg = choice + 'arg';
+    // If a radio button in changed, we need to change the argfield
+    if (current.radiovalue != choice) {
+        current.radiovalue = choice;
+        if (current.argfield != null) {
+            currentform.elements[current.argfield].value = '';
+        }
+        if (choice == 'nochange') {
+            current.argfield = null;
+        } else {
+            current.argfield = choicearg;
+            switch(choice) {
+                case 'krb': 
+                    currentform.elements[current.argfield].value = 
+                        "$in{'kerb_def_dom'}";
+                break;
+              default:
+                break;
+            }
+        }
+    }
+    return;
+}
+
+function changed_text(choice,currentform) {
+    var choicearg = choice + 'arg';
+    if (currentform.elements[choicearg].value !='') {
+        switch (choice) {
+            case 'krb': currentform.elements[choicearg].value =
+                currentform.elements[choicearg].value.toUpperCase();
+                break;
+            default:
+        }
+        // clear old field
+        if ((current.argfield != choicearg) && (current.argfield != null)) {
+            currentform.elements[current.argfield].value = '';
+        }
+        current.argfield = choicearg;
+    }
+    set_auth_radio_buttons(choice,currentform);
+    return;
+}
+
+function set_auth_radio_buttons(newvalue,currentform) {
+    var i=0;
+    while (i < currentform.login.length) {
+        if (currentform.login[i].value == newvalue) { break; }
+        i++;
+    }
+    if (i == currentform.login.length) {
+        return;
+    }
+    current.radiovalue = newvalue;
+    currentform.login[i].checked = true;
+    return;
+}
+END
+    return $result;
+}
+
+sub authform_authorwarning{
+    my $result='';
+    $result=<<"END";
+<i>As a general rule, only authors or co-authors should be filesystem
+authenticated (which allows access to the server filesystem).</i>
+END
+    return $result;
+}
+
+sub authform_nochange{  
+    my %in = (
+              formname => 'document.cu',
+              kerb_def_dom => 'MSU.EDU',
+              @_,
+          );
+    my $result='';
+    $result.=<<"END";
+<input type="radio" name="login" value="nochange" checked="checked"
+       onclick="javascript:changed_radio('nochange',$in{'formname'});">
+Do not change login data
+END
+    return $result;
+}
+
+sub authform_kerberos{  
+    my %in = (
+              formname => 'document.cu',
+              kerb_def_dom => 'MSU.EDU',
+              @_,
+              );
+    my $result='';
+    $result.=<<"END";
+<input type="radio" name="login" value="krb" 
+       onclick="javascript:changed_radio('krb',$in{'formname'});"
+       onchange="javascript:changed_radio('krb',$in{'formname'});">
+Kerberos authenticated with domain
+<input type="text" size="10" name="krbarg" value=""
+       onchange="javascript:changed_text('krb',$in{'formname'});">
+END
+    return $result;
+}
+
+sub authform_internal{  
+    my %args = (
+                formname => 'document.cu',
+                kerb_def_dom => 'MSU.EDU',
+                @_,
+                );
+    my $result='';
+    $result.=<<"END";
+<input type="radio" name="login" value="int"
+       onchange="javascript:changed_radio('int',$args{'formname'});"
+       onclick="javascript:changed_radio('int',$args{'formname'});">
+Internally authenticated (with initial password 
+<input type="text" size="10" name="intarg" value=""
+       onchange="javascript:changed_text('int',$args{'formname'});">
+END
+    return $result;
+}
+
+sub authform_local{  
+    my %in = (
+              formname => 'document.cu',
+              kerb_def_dom => 'MSU.EDU',
+              @_,
+              );
+    my $result='';
+    $result.=<<"END";
+<input type="radio" name="login" value="loc"
+       onchange="javascript:changed_radio('loc',$in{'formname'});"
+       onclick="javascript:changed_radio('loc',$in{'formname'});"> 
+Local Authentication with argument
+<input type="text" size="10" name="locarg" value=""
+       onchange="javascript:changed_text('loc',$in{'formname'});">
+END
+    return $result;
+}
+
+sub authform_filesystem{  
+    my %in = (
+              formname => 'document.cu',
+              kerb_def_dom => 'MSU.EDU',
+              @_,
+              );
+    my $result='';
+    $result.=<<"END";
+<input type="radio" name="login" value="fsys" 
+       onchange="javascript:changed_radio('fsys',$in{'formname'});"
+       onclick="javascript:changed_radio('fsys',$in{'formname'});"> 
+Filesystem authenticated (with initial password 
+<input type="text" size="10" name="fsysarg" value=""
+       onchange="javascript:changed_text('fsys',$in{'formname'});">
+END
+    return $result;
+}
+
+###############################################################
+##   End Authentication changing form generation functions   ##
+###############################################################
+
 
 
 # ---------------------------------------------------------- Is this a keyword?
@@ -273,7 +619,14 @@ sub get_previous_attempt {
       }
       $prevattempts='<table border=2></tr><th>History</th>';
       foreach (sort(keys %lasthash)) {
-        $prevattempts.='<th>'.$_.'</th>';
+	my ($ign,@parts) = split(/\./,$_);
+	if (@parts) {
+	  my $data=$parts[-1];
+	  pop(@parts);
+	  $prevattempts.='<th>Part '.join('.',@parts).'<br />'.$data.'</th>';
+	} else {
+	  $prevattempts.='<th>'.$ign.'</th>';
+	}
       }
       for ($version=1;$version<=$returnhash{'version'};$version++) {
         $prevattempts.='</tr><tr><th>Attempt '.$version.'</th>';
@@ -367,7 +720,7 @@ sub get_unprocessed_cgi {
       $value =~ tr/+/ /;
       $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
       &Apache::lonxml::debug("Seting :$name: to :$value:");
-      &add_to_env('form.'.$name,$value);
+      unless (defined($ENV{'form.'.$name})) { &add_to_env('form.'.$name,$value) };
     }
   }
 }
@@ -392,7 +745,7 @@ sub no_cache {
 
 sub add_to_env {
   my ($name,$value)=@_;
-  if ($ENV{$name}) {
+  if (defined($ENV{$name})) {
     if (ref($ENV{$name})) {
       #already have multiple values
       push(@{ $ENV{$name} },$value);
@@ -406,113 +759,271 @@ sub add_to_env {
     $ENV{$name}=$value;
   }
 }
-1;
-__END__;
 
+#---CSV Upload/Handling functions
 
-=head1 NAME
+# ========================================================= Store uploaded file
+# needs $ENV{'form.upfile'}
+# return $datatoken to be put into hidden field
+
+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;
 
-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:
+    my $datatoken=$ENV{'user.name'}.'_'.$ENV{'user.domain'}.
+	'_enroll_'.$ENV{'request.course.id'}.'_'.time.'_'.$$;
+    {
+	my $fh=Apache::File->new('>'.$r->dir_config('lonDaemons').
+				 '/tmp/'.$datatoken.'.tmp');
+	print $fh $ENV{'form.upfile'};
+    }
+    return $datatoken;
+}
 
- Makes a table out of the previous homework attempts
- Inputs result_from_symbread, user, domain, course_id
- Reads in non-network-related .tab files
+# ================================================= Load uploaded file from tmp
+# needs $ENV{'form.datatoken'}
+# sets $ENV{'form.upfile'} to the contents of the file
 
-This is part of the LearningOnline Network with CAPA project
-described at http://www.lon-capa.org.
+sub load_tmp_file {
+    my $r=shift;
+    my @studentdata=();
+    {
+	my $fh;
+	if ($fh=Apache::File->new($r->dir_config('lonDaemons').
+				  '/tmp/'.$ENV{'form.datatoken'}.'.tmp')) {
+	    @studentdata=<$fh>;
+	}
+    }
+    $ENV{'form.upfile'}=join('',@studentdata);
+}
 
-=head1 HANDLER SUBROUTINE
+# ========================================= Separate uploaded file into records
+# returns array of records
+# needs $ENV{'form.upfile'}
+# needs $ENV{'form.upfiletype'}
 
-There is no handler subroutine.
+sub upfile_record_sep {
+    if ($ENV{'form.upfiletype'} eq 'xml') {
+    } else {
+	return split(/\n/,$ENV{'form.upfile'});
+    }
+}
 
-=head1 OTHER SUBROUTINES
+# =============================================== Separate a record into fields
+# needs $ENV{'form.upfiletype'}
+# takes $record as arg
+sub record_sep {
+    my $record=shift;
+    my %components=();
+    if ($ENV{'form.upfiletype'} eq 'xml') {
+    } elsif ($ENV{'form.upfiletype'} eq 'space') {
+        my $i=0;
+        foreach (split(/\s+/,$record)) {
+            my $field=$_;
+            $field=~s/^(\"|\')//;
+            $field=~s/(\"|\')$//;
+            $components{$i}=$field;
+            $i++;
+        }
+    } elsif ($ENV{'form.upfiletype'} eq 'tab') {
+        my $i=0;
+        foreach (split(/\t+/,$record)) {
+            my $field=$_;
+            $field=~s/^(\"|\')//;
+            $field=~s/(\"|\')$//;
+            $components{$i}=$field;
+            $i++;
+        }
+    } else {
+        my @allfields=split(/\,/,$record);
+        my $i=0;
+        my $j;
+        for ($j=0;$j<=$#allfields;$j++) {
+            my $field=$allfields[$j];
+            if ($field=~/^\s*(\"|\')/) {
+		my $delimiter=$1;
+                while (($field!~/$delimiter$/) && ($j<$#allfields)) {
+		    $j++;
+		    $field.=','.$allfields[$j];
+		}
+                $field=~s/^\s*$delimiter//;
+                $field=~s/$delimiter\s*$//;
+            }
+            $components{$i}=$field;
+	    $i++;
+        }
+    }
+    return %components;
+}
 
-=over 4
+# =============================== HTML code to select file and specify its type
+sub upfile_select_html {
+    return (<<'ENDUPFORM');
+<input type="file" name="upfile" size="50">
+<br />Type: <select name="upfiletype">
+<option value="csv">CSV (comma separated values, spreadsheet)</option>
+<option value="space">Space separated</option>
+<option value="tab">Tabulator separated</option>
+<option value="xml">HTML/XML</option>
+</select>
+ENDUPFORM
+}
+
+# ===================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
+sub csv_print_samples {
+    my ($r,$records) = @_;
+    my (%sone,%stwo,%sthree);
+    %sone=&record_sep($$records[0]);
+    if (defined($$records[1])) {%stwo=&record_sep($$records[1]);}
+    if (defined($$records[2])) {%sthree=&record_sep($$records[2]);}
+
+    $r->print('Samples<br /><table border="2"><tr>');
+    foreach (sort({$a <=> $b} keys(%sone))) { $r->print('<th>Column&nbsp;'.($_+1).'</th>'); }
+    $r->print('</tr>');
+    foreach my $hash (\%sone,\%stwo,\%sthree) {
+	$r->print('<tr>');
+	foreach (sort({$a <=> $b} keys(%sone))) {
+	    $r->print('<td>');
+	    if (defined($$hash{$_})) { $r->print($$hash{$_}); }
+	    $r->print('</td>');
+	}
+	$r->print('</tr>');
+    }
+    $r->print('</tr></table><br />'."\n");
+}
 
-=item *
+# ======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)
+sub csv_print_select_table {
+    my ($r,$records,$d) = @_;
+    my $i=0;my %sone;
+    %sone=&record_sep($$records[0]);
+    $r->print('Associate columns with student attributes.'."\n".
+	     '<table border="2"><tr><th>Attribute</th><th>Column</th></tr>'."\n");
+    foreach (@$d) {
+	my ($value,$display)=@{ $_ };
+	$r->print('<tr><td>'.$display.'</td>');
+
+	$r->print('<td><select name=f'.$i.
+		  ' onchange="javascript:flip(this.form,'.$i.');">');
+	$r->print('<option value="none"></option>');
+	foreach (sort({$a <=> $b} keys(%sone))) {
+	    $r->print('<option value="'.$_.'">Column '.($_+1).'</option>');
+	}
+	$r->print('</select></td></tr>'."\n");
+	$i++;
+    }
+    $i--;
+    return $i;
+}
 
-BEGIN() : initialize values from language.tab, copyright.tab, filetypes.tab,
-and filecategories.tab.
+# ===================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)
+sub csv_samples_select_table {
+    my ($r,$records,$d) = @_;
+    my %sone; my %stwo; my %sthree;
+    my $i=0;
+
+    $r->print('<table border=2><tr><th>Field</th><th>Samples</th></tr>');
+    %sone=&record_sep($$records[0]);
+    if (defined($$records[1])) {%stwo=&record_sep($$records[1]);}
+    if (defined($$records[2])) {%sthree=&record_sep($$records[2]);}
+
+    foreach (sort keys %sone) {
+	$r->print('<tr><td><select name=f'.$i.
+		  ' onchange="javascript:flip(this.form,'.$i.');">');
+	foreach (@$d) {
+	    my ($value,$display)=@{ $_ };
+	    $r->print('<option value='.$value.'>'.$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"); }
+	$r->print('</td></tr>');
+	$i++;
+    }
+    $i--;
+    return($i);
+}
+1;
+__END__;
 
-=item *
+=item languageids() 
 
-languageids() : returns list of all language ids
+returns list of all language ids
 
-=item *
+=item languagedescription() 
 
-languagedescription() : returns description of a specified language id
+returns description of a specified language id
 
-=item *
+=item copyrightids() 
 
-copyrightids() : returns list of all copyrights
+returns list of all copyrights
 
-=item *
+=item copyrightdescription() 
 
-copyrightdescription() : returns description of a specified copyright id
+returns description of a specified copyright id
 
-=item *
+=item filecategories() 
 
-filecategories() : returns list of all file categories
+returns list of all file categories
 
-=item *
+=item filecategorytypes() 
 
-filecategorytypes() : returns list of file types belonging to a given file
+returns list of file types belonging to a given file
 category
 
-=item *
+=item fileembstyle() 
 
-fileembstyle() : returns embedding style for a specified file type
+returns embedding style for a specified file type
 
-=item *
+=item filedescription() 
 
-filedescription() : returns description for a specified file type
+returns description for a specified file type
 
-=item *
+=item filedescriptionex() 
 
-filedescriptionex() : returns description for a specified file type with
+returns description for a specified file type with
 extra formatting
 
-=item *
+=item get_previous_attempt() 
 
-get_previous_attempt() : return string with previous attempt on problem
+return string with previous attempt on problem
 
-=item *
+=item get_student_view() 
 
-get_student_view() : show a snapshot of what student was looking at
+show a snapshot of what student was looking at
 
-=item *
+=item get_student_answers() 
 
-get_student_answers() : show a snapshot of how student was answering problem
+show a snapshot of how student was answering problem
 
-=item *
+=item get_unprocessed_cgi() 
 
-get_unprocessed_cgi() : get unparsed CGI parameters
+get unparsed CGI parameters
 
-=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.