--- loncom/interface/lonsearchcat.pm	2002/09/16 20:09:45	1.157
+++ loncom/interface/lonsearchcat.pm	2003/06/19 13:07:49	1.183
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Search Catalog
 #
-# $Id: lonsearchcat.pm,v 1.157 2002/09/16 20:09:45 www Exp $
+# $Id: lonsearchcat.pm,v 1.183 2003/06/19 13:07:49 matthew Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -74,12 +74,11 @@ package Apache::lonsearchcat;
 
 # ------------------------------------------------- modules used by this module
 use strict;
-use Apache::Constants qw(:common);
+use Apache::Constants qw(:common :http);
 use Apache::lonnet();
 use Apache::File();
 use CGI qw(:standard);
 use Text::Query;
-use DBI;
 use GDBM_File;
 use Apache::loncommon();
 use Apache::lonmysql();
@@ -143,6 +142,14 @@ my %persistent_db;
 my $hidden_fields;
 my $bodytag;
 
+#
+# For course search
+#
+my %alreadyseen;
+my $hashtied;
+my %hash;
+my $totalfound;
+ 
 ######################################################################
 ######################################################################
 
@@ -191,7 +198,8 @@ sub handler {
     ##
     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
              ['catalogmode','launch','acts','mode','form','element','pause',
-              'phase','persistent_db_id','table','start','show']);
+              'phase','persistent_db_id','table','start','show',
+              'cleargroupsort']);
     ##
     ## The following is a trick - we wait a few seconds if asked to so
     ##     the daemon running the search can get ahead of the daemon
@@ -199,15 +207,14 @@ sub handler {
     ##     this once, so the pause indicator is deleted
     ##
     if (exists($ENV{'form.pause'})) {
-        sleep(3);
+        sleep(1);
         delete($ENV{'form.pause'});
     }
     ##
     ## Initialize global variables
     ##
     my $domain  = $r->dir_config('lonDefDomain');
-    $diropendb= "/home/httpd/perl/tmp/".&Apache::lonnet::escape($domain).
-            "\_".&Apache::lonnet::escape($ENV{'user.name'})."_searchcat.db";
+    $diropendb= "/home/httpd/perl/tmp/$ENV{'user.domain'}_$ENV{'user.name'}_searchcat.db";
     #
     # set the name of the persistent database
     #          $ENV{'form.persistent_db_id'} can only have digits in it.
@@ -242,11 +249,13 @@ END
     ## Clear out old values from groupsearch database
     ##
     untie %groupsearch_db if (tied(%groupsearch_db));
-    if (($ENV{'form.launch'} eq '1') && 
-        ($ENV{'form.catalogmode'} eq 'groupsearch')) {
+    if (($ENV{'form.cleargroupsort'} eq '1') || 
+        (($ENV{'form.launch'} eq '1') && 
+         ($ENV{'form.catalogmode'} eq 'groupsearch'))) {
 	if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) {
 	    &start_fresh_session();
 	    untie %groupsearch_db;
+            delete($ENV{'form.cleargroupsort'});
 	} else {
             # This is a stupid error to give to the user.  
             # It really tells them nothing.
@@ -336,6 +345,8 @@ END
             &run_search($r,$query,$customquery,$customshow,
                         $libraries,$pretty_string);
         }
+    } elsif ($ENV{'form.phase'} eq 'course_search') {
+        &course_search($r);
     } elsif(($ENV{'form.phase'} eq 'basic_search') ||
             ($ENV{'form.phase'} eq 'adv_search')) {
         $ENV{'form.searchmode'} = 'basic';
@@ -371,7 +382,8 @@ END
             (undef,undef,undef,undef);
         my $pretty_string;
         if ($ENV{'form.phase'} eq 'basic_search') {
-            ($query,$pretty_string) = &parse_basic_search($r,$closebutton);
+            ($query,$pretty_string,$libraries) = 
+                &parse_basic_search($r,$closebutton);
         } else {                      # Advanced search
             ($query,$customquery,$customshow,$libraries,$pretty_string) 
                 = &parse_advanced_search($r,$closebutton);
@@ -394,6 +406,111 @@ END
 ######################################################################
 ######################################################################
 
+sub course_search {
+    my $r=shift;
+    my $bodytag=&Apache::loncommon::bodytag('Course Search');
+    my $pretty_search_string = '<b>'.$ENV{'form.courseexp'}.'</b>';
+    my $search_string = $ENV{'form.courseexp'};
+    my @New_Words;
+    if ($ENV{'form.crsrelated'}) {
+        ($search_string,@New_Words) = &related_version($ENV{'form.courseexp'});
+        if (@New_Words) {
+            $pretty_search_string .= " with related words: <b>@New_Words</b>.";
+        } else {
+            $pretty_search_string .= " with no related words.";
+        }
+    }
+    my $fulltext=$ENV{'form.crsfulltext'};
+    my @allwords=($search_string,@New_Words);
+    $totalfound=0;
+    $r->print('<html><head><title>LON-CAPA Course Search</title></head>'.
+	      $bodytag.$pretty_search_string);
+    $r->rflush();
+# ======================================================= Go through the course
+   $hashtied=0;
+   undef %alreadyseen;
+   %alreadyseen=();
+    my $c=$r->connection;
+   &tiehash();
+   foreach (keys %hash) {
+       if ($c->aborted()) { last; }
+       if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) {
+           &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext,@allwords);
+       }
+   }
+   &untiehash();
+    unless ($totalfound) {
+	$r->print('<p>No resources found.</p>');
+    }
+# =================================================== Done going through course
+    $r->print('</body></html>');
+}
+
+# ---------------------------------------------------------------- tie the hash
+
+sub tiehash {
+    $hashtied=0;
+    if ($ENV{'request.course.fn'}) {
+        if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db",
+            &GDBM_READER(),0640)) {
+                $hashtied=1;
+        }
+    }    
+}
+
+sub untiehash {
+    if ($hashtied) { untie %hash; }
+    $hashtied=0;
+}
+
+# =============================== This pulls up a resource and its dependencies
+
+sub checkonthis {
+    my ($r,$url,$level,$title,$fulltext,@allwords)=@_;
+    $alreadyseen{$url}=1;
+    $r->rflush();
+    my $result=&Apache::lonnet::metadata($url,'title').' '.
+               &Apache::lonnet::metadata($url,'subject').' '.
+               &Apache::lonnet::metadata($url,'abstract').' '.
+               &Apache::lonnet::metadata($url,'keywords');
+    if (($url) && ($fulltext)) {
+	$result.=&Apache::lonnet::ssi_body($url);
+    }
+    $result=~s/\s+/ /gs;
+    my $applies=0;
+    foreach (@allwords) {
+        if ($_=~/\w/) {
+	   if ($result=~/$_/si) {
+	      $applies++;
+           }
+       }
+    }
+# Does this resource apply?
+    if ($applies) {
+       $r->print('<br />');
+       for (my $i=0;$i<=$level*5;$i++) {
+           $r->print('&nbsp;');
+       }
+       $r->print('<a href="'.$url.'" target="cat">'.
+		 ($title?$title:$url).'</a><br />');
+       $totalfound++;
+    } elsif ($fulltext) {
+       $r->print(' .');
+    }
+    $r->rflush();
+# Check also the dependencies of this one
+    my $dependencies=
+                &Apache::lonnet::metadata($url,'dependencies');
+    foreach (split(/\,/,$dependencies)) {
+       if (($_=~/^\/res\//) && (!$alreadyseen{$_})) {
+          &checkonthis($r,$_,$level+1,'',$fulltext,@allwords);
+       }
+    }
+}
+
+######################################################################
+######################################################################
+
 =pod 
 
 =item &print_basic_search_form() 
@@ -407,7 +524,7 @@ Returns a scalar which holds html for th
 
 sub print_basic_search_form{
     my ($r,$closebutton) = @_;
-    my $bodytag=&Apache::loncommon::bodytag('Catalog Search');
+    my $bodytag=&Apache::loncommon::bodytag('Search');
     my $scrout=<<"ENDDOCUMENT";
 <html>
 <head>
@@ -421,6 +538,10 @@ sub print_basic_search_form{
 </script>
 </head>
 $bodytag
+ENDDOCUMENT
+if (&Apache::lonnet::allowed('bre',$ENV{'request.role.domain'})) {
+    $scrout.=(<<ENDDOCUMENT);
+<h1>Catalog Search</h1>
 <form method="post" action="/adm/searchcat">
 <input type="hidden" name="phase" value="basic_search" />
 $hidden_fields
@@ -434,10 +555,15 @@ then press SEARCH below.
 ENDDOCUMENT
     $scrout.='&nbsp;'.&simpletextfield('basicexp',$ENV{'form.basicexp'},40).
         '&nbsp;';
-    my $checkbox = &simplecheckbox('related',$ENV{'form.related'});
+    my $relatedcheckbox = &simplecheckbox('related',$ENV{'form.related'});
+    my $domain = $r->dir_config('lonDefDomain');
+    my $domaincheckbox = &simplecheckbox('domains',$domain,'checked');
     $scrout.=<<END;
-</td><td><a href="/adm/searchcat?phase=disp_adv">Advanced Search</a></td></tr>
-<tr><td>$checkbox use related words</td><td></td></tr>
+</td><td><a
+href="/adm/searchcat?phase=disp_adv&catalogmode=$ENV{'form.catalogmode'}&launch=$ENV{'form.launch'}"
+>Advanced Search</a></td></tr>
+<tr><td>$relatedcheckbox use related words</td>
+    <td>$domaincheckbox only search domain <b>$domain</b></td></tr>
 </table>
 </p>
 <p>
@@ -456,6 +582,38 @@ END
 per page.
 </p>
 </form>
+ENDDOCUMENT
+    }
+    if ($ENV{'request.course.id'}) {
+        $scrout.=(<<ENDCOURSESEARCH);
+<hr />
+<h1>Course Search</h1>    
+<form method="post" action="/adm/searchcat">
+<input type="hidden" name="phase" value="course_search" />
+$hidden_fields
+<p>
+Enter terms or phrases, then press SEARCH below.
+</p>
+<p>
+<table>
+<tr><td>
+ENDCOURSESEARCH
+        $scrout.='&nbsp;'.
+            &simpletextfield('courseexp',$ENV{'form.courseexp'},40);
+        my $crscheckbox = 
+            &simplecheckbox('crsfulltext',$ENV{'form.crsfulltext'});
+        my $relcheckbox = 
+            &simplecheckbox('crsrelated',$ENV{'form.crsrelated'});
+        $scrout.=(<<ENDENDCOURSE);
+</td></tr>
+<tr><td>$relcheckbox use related words</td><td></td></tr>
+<tr><td>$crscheckbox fulltext search (time consuming)</td><td></td></tr>
+</table><p>
+&nbsp;<input type="submit" name="coursesubmit" value='SEARCH' />
+</p>
+ENDENDCOURSE
+    }
+    $scrout.=(<<ENDDOCUMENT);
 </body>
 </html>
 ENDDOCUMENT
@@ -539,13 +697,13 @@ ENDHEADER
                                              $ENV{'form.abstract'});
     # Hack - an empty table row.
     $scrout.="<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>\n";
-    $scrout.=&searchphrasefield('file<br />extension','mime',
-                        $ENV{'form.mime'});
+    $scrout.=&searchphrasefield('file<br />extension','extension',
+                        $ENV{'form.extension'});
     $scrout.="<tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>\n";
     $scrout.=&searchphrasefield('publisher<br />owner','owner',
 				$ENV{'form.owner'});
     $scrout.="</table>\n";
-    $ENV{'form.category'}='any' unless length($ENV{'form.category'});
+    $ENV{'form.category'}='any' if (! defined($ENV{'form.category'}));
     $scrout.=&selectbox('File Category','category',
 			$ENV{'form.category'},
 			'any','Any category',
@@ -566,9 +724,9 @@ ENDHEADER
     my @domains =&Apache::loncommon::get_domains();
     # adjust the size of the select box
     my $size = 4;
-    my $size = (scalar @domains < ($size - 1) ? scalar @domains + 1 : $size);
+    $size = (scalar @domains < ($size - 1) ? scalar @domains + 1 : $size);
     $scrout.="\n".'<font color="#800000" face="helvetica"><b>'.
-        'DOMAINS</b></font><br />'.
+        'DOMAINS TO BE SEARCHED</b></font><br />'.
             '<select name="domains" size="'.$size.'" multiple>'."\n".
                 '<option name="any" value="any" '.
                     ($domain_hash{'any'}? 'selected ' :'').
@@ -581,7 +739,7 @@ ENDHEADER
     #----------------------------------------------------------------
     $scrout.=&selectbox('Limit by language','language',
 			$ENV{'form.language'},'any','Any Language',
-			\&{Apache::loncommon::languagedescription},
+			\&Apache::loncommon::languagedescription,
 			(&Apache::loncommon::languageids),
 			);
 # ------------------------------------------------ Compute date selection boxes
@@ -628,7 +786,7 @@ LASTREVISIONDATEEND
     $scrout.=&selectbox('Limit by copyright/distribution','copyright',
 			 $ENV{'form.copyright'},
 			 'any','Any copyright/distribution',
-			 \&{Apache::loncommon::copyrightdescription},
+			 \&Apache::loncommon::copyrightdescription,
 			 (&Apache::loncommon::copyrightids),
 			 );
 # ------------------------------------------- Compute customized metadata field
@@ -689,11 +847,18 @@ sub get_persistent_form_data {
                            &GDBM_READER(),0640));
     #
     # These make sure we do not get array references printed out as 'values'.
-    my %arrays_allowed = ('form.category'=>1,'form.domains'=>1);
+    my %arrays_allowed = ('form.domains'=>1);
     #
     # Loop through the keys, looking for 'form.'
     foreach my $name (keys(%persistent_db)) {
         next if ($name !~ /^form./);
+        # Kludgification begins!
+        if ($name eq 'form.domains' && 
+            $ENV{'form.searchmode'} eq 'basic' &&
+            $ENV{'form.phase'} ne 'disp_basic') {
+            next;
+        }
+        # End kludge (hopefully)
         next if (exists($ENV{$name}));
         my @values = map { 
             &Apache::lonnet::unescape($_);
@@ -708,6 +873,7 @@ sub get_persistent_form_data {
     untie (%persistent_db);
     return 1;
 }
+
 ######################################################################
 ######################################################################
 
@@ -866,11 +1032,11 @@ If $value eq 'on' the box is checked.
 ###############################################
 ###############################################
 
-sub simplecheckbox {
-    my ($name,$value)=@_;
-    my $checked='';
-    $checked="checked" if $value eq 'on';
-    return '<input type="checkbox" name="'.$name.'" '. $checked . ' />';
+sub simplecheckbox{
+    my ($name,$value,$checked)=@_;
+    $checked="checked" if ($value eq 'on');
+    return '<input type="checkbox" name="'.$name.'" value="'.$value.'" '.
+        $checked.' />';
 }
 
 ###############################################
@@ -1131,7 +1297,7 @@ sub parse_advanced_search {
 		   'lastrevisiondatestart_month','lastrevisiondatestart_day',
 		   'lastrevisiondatestart_year','lastrevisiondateend_month',
 		   'lastrevisiondateend_day','lastrevisiondateend_year',
-		   'notes','abstract','mime','language','owner',
+		   'notes','abstract','extension','language','owner',
 		   'custommetadata','customshow','category') {
 	$ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
     }
@@ -1142,16 +1308,13 @@ sub parse_advanced_search {
 	$ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
     }
     # Preprocess the category form element.
-    $ENV{'form.category'} = 'any' if (ref($ENV{'form.category'}));
-    if ($ENV{'form.category'} ne 'any') {
-        my @extensions = &Apache::loncommon::filecategorytypes
-            ($ENV{'form.category'});
-        $ENV{'form.mime'} = join ' OR ',@extensions;
-    }
+    $ENV{'form.category'} = 'any' if (! defined($ENV{'form.category'}) ||
+                                      ref($ENV{'form.category'}));
+    #
     # Check to see if enough information was filled in
     for my $field ('title','author','subject','keywords','url','version',
-		   'notes','abstract','mime','language','owner',
-		   'custommetadata') {
+		   'notes','abstract','category','extension','language',
+                   'owner','custommetadata') {
 	if (&filled($ENV{"form.$field"})) {
 	    $fillflag++;
 	}
@@ -1166,7 +1329,7 @@ sub parse_advanced_search {
     my $font = '<font color="#800000" face="helvetica">';
     # Evaluate logical expression AND/OR/NOT phrase fields.
     foreach my $field ('title','author','subject','notes','abstract','url',
-		       'keywords','version','owner','mime') {
+		       'keywords','version','owner') {
 	if ($ENV{'form.'.$field}) {
             my $searchphrase = $ENV{'form.'.$field};
             $pretty_search_string .= $font."$field</font> contains <b>".
@@ -1185,10 +1348,30 @@ sub parse_advanced_search {
 	    push @queries,&build_SQL_query($field,$searchphrase);
         }
     }
-    # I dislike the hack below.
-    if ($ENV{'form.category'}) {
-        $ENV{'form.mime'}='';
+    #
+    # Make the 'mime' from 'form.category' and 'form.extension'
+    #
+    my $searchphrase;
+    if (exists($ENV{'form.category'})    && 
+        $ENV{'form.category'} !~ /^\s*$/ &&
+        $ENV{'form.category'} ne 'any')     {
+        my @extensions = &Apache::loncommon::filecategorytypes
+                                                   ($ENV{'form.category'});
+        if (scalar(@extensions) > 0) {
+            $searchphrase = join(' OR ',@extensions);
+        }
+    }
+    if (exists($ENV{'form.extension'}) && $ENV{'form.extension'} !~ /^\s*$/) {
+        $searchphrase .= ' OR ' if (defined($searchphrase));
+        my @extensions = split(/,/,$ENV{'form.extension'});
+        $searchphrase .= join(' OR ',@extensions);
+    }
+    if (defined($searchphrase)) {
+        push @queries,&build_SQL_query('mime',$searchphrase);
+        $pretty_search_string .=$font.'mime</font> contains <b>'.
+            $searchphrase.'</b><br />';
     }
+    #####
     # Evaluate option lists
     if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') {
 	push @queries,"(language like \"$ENV{'form.language'}\")";
@@ -1247,11 +1430,33 @@ sub parse_advanced_search {
     ## ---------------------------------------------------------------
     ## Deal with restrictions to given domains
     ## 
+    my ($libraries_to_query,$pretty_domains_string) = 
+        &parse_domain_restrictions();
+    $pretty_search_string .= $pretty_domains_string."<br />\n";
+    #
+    if (@queries) {
+	$query=join(" AND ",@queries);
+	$query="select * from metadata where $query";
+    } elsif ($customquery) {
+        $query = '';
+    }
+    return ($query,$customquery,$customshow,$libraries_to_query,
+            $pretty_search_string);
+}
+
+sub parse_domain_restrictions {
     my $libraries_to_query = undef;
     # $ENV{'form.domains'} can be either a scalar or an array reference.
     # We need an array.
-    my @allowed_domains = (ref($ENV{'form.domains'}) ? @{$ENV{'form.domains'}} 
-                           :  ($ENV{'form.domains'}) );
+    if (! exists($ENV{'form.domains'})) {
+        return (undef,'');
+    }
+    my @allowed_domains;
+    if (ref($ENV{'form.domains'})) {
+        @allowed_domains =  @{$ENV{'form.domains'}};
+    } else {
+        @allowed_domains = ($ENV{'form.domains'});
+    }
     my %domain_hash = ();
     my $pretty_domains_string;
     foreach (@allowed_domains) {
@@ -1274,16 +1479,7 @@ sub parse_advanced_search {
             }
         }
     }
-    $pretty_search_string .= $pretty_domains_string."<br />\n";
-    #
-    if (@queries) {
-	$query=join(" AND ",@queries);
-	$query="select * from metadata where $query";
-    } elsif ($customquery) {
-        $query = '';
-    }
-    return ($query,$customquery,$customshow,$libraries_to_query,
-            $pretty_search_string);
+    return ($libraries_to_query,$pretty_domains_string);
 }
 
 ######################################################################
@@ -1311,7 +1507,8 @@ sub parse_basic_search {
 	$ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
 	$ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
     }
-
+    my ($libraries_to_query,$pretty_domains_string) = 
+        &parse_domain_restrictions();
     # Check to see if enough is filled in
     unless (&filled($ENV{'form.basicexp'})) {
 	&output_blank_field_error($r,$closebutton,'phase=disp_basic');
@@ -1330,13 +1527,19 @@ sub parse_basic_search {
     }
     # Build SQL query string based on form page
     my $query='';
-    my $concatarg=join(',"    ",',
+    my $concatarg=join(',',
 		       ('title', 'author', 'subject', 'notes', 'abstract',
                         'keywords'));
     $concatarg='title' if $ENV{'form.titleonly'};
-    $query=&build_SQL_query('concat('.$concatarg.')',$search_string);
+    $query=&build_SQL_query('concat_ws(" ",'.$concatarg.')',$search_string);
+    if (defined($pretty_domains_string) && $pretty_domains_string ne '') {
+        $pretty_search_string .= ' '.$pretty_domains_string;
+    }
     $pretty_search_string .= "<br />\n";
-    return 'select * from metadata where '.$query,$pretty_search_string;
+    my $final_query = 'SELECT * FROM metadata WHERE '.$query;
+#    &Apache::lonnet::logthis($final_query);
+    return ($final_query,$pretty_search_string,
+            $libraries_to_query);
 }
 
 
@@ -1449,30 +1652,26 @@ sub recursive_SQL_query_build {
     my @matches=($pattern=~/(\[[^\]|\[]*\])/g);
     return $pattern unless @matches;
     foreach my $match (@matches) {
-	$match=~/\[ (\w+)\s(.*) \]/;
-	my ($key,$value)=($1,$2);
-	my $replacement='';
-	if ($key eq 'literal') {
-	    $replacement="($dkey like \"\%$value\%\")";
-	}
-	elsif ($key eq 'not') {
-	    $value=~s/like/not like/;
-#	    $replacement="($dkey not like $value)";
-	    $replacement="$value";
-	}
-	elsif ($key eq 'and') {
-	    $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
-	    $replacement="($1 AND $2)";
-	}
-	elsif ($key eq 'or') {
-	    $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
-	    $replacement="($1 OR $2)";
+        $match=~/\[ (\w+)\s(.*) \]/;
+        my ($key,$value)=($1,$2);
+        my $replacement='';
+        if ($key eq 'literal') {
+            $replacement="($dkey LIKE \"\%$value\%\")";
+        } elsif (lc($key) eq 'not') {
+            $value=~s/LIKE/NOT LIKE/;
+#          $replacement="($dkey not like $value)";
+            $replacement="$value";
+        } elsif ($key eq 'and') {
+            $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
+            $replacement="($1 AND $2)";
+	} elsif ($key eq 'or') {
+            $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
+            $replacement="($1 OR $2)";
 	}
 	substr($pattern,
-	       index($pattern,$match),
-	       length($match),
-	       $replacement
-	       );
+               index($pattern,$match),
+               length($match),
+               $replacement);
     }
     &recursive_SQL_query_build($dkey,$pattern);
 }
@@ -1729,37 +1928,31 @@ the following format:
 
 =cut
 
-##
-## Restrictions:
-##    columns of type 'text' and 'blob' cannot have defaults.
-##    columns of type 'enum' cannot be used for FULLTEXT.
-##
-my @DataOrder = qw/id title author subject url keywords version notes
-    abstract mime lang owner copyright creationdate lastrevisiondate hostname/;
-
-my %Datatypes = 
-    ( id        =>{ type         => 'MEDIUMINT',
-                    restrictions => 'UNSIGNED NOT NULL',
-                    primary_key  => 'yes',
-                    auto_inc     => 'yes'
-                    },
-      title     =>{ type=>'TEXT'},
-      author    =>{ type=>'TEXT'},
-      subject   =>{ type=>'TEXT'},
-      url       =>{ type=>'TEXT',
-                    restrictions => 'NOT NULL' },
-      keywords  =>{ type=>'TEXT'},
-      version   =>{ type=>'TEXT'},
-      notes     =>{ type=>'TEXT'},
-      abstract  =>{ type=>'TEXT'},
-      mime      =>{ type=>'TEXT'},
-      lang      =>{ type=>'TEXT'},
-      owner     =>{ type=>'TEXT'},
-      copyright =>{ type=>'TEXT'},
-      hostname  =>{ type=>'TEXT'},
+#####################################################################
+#####################################################################
+
+my @Datatypes = 
+    ( { name => 'id', 
+        type => 'MEDIUMINT',
+        restrictions => 'UNSIGNED NOT NULL',
+        primary_key  => 'yes',
+        auto_inc     => 'yes' },
+      { name => 'title',     type=>'TEXT'},
+      { name => 'author',    type=>'TEXT'},
+      { name => 'subject',   type=>'TEXT'},
+      { name => 'url',       type=>'TEXT', restrictions => 'NOT NULL' },
+      { name => 'keywords',  type=>'TEXT'},
+      { name => 'version',   type=>'TEXT'},
+      { name => 'notes',     type=>'TEXT'},
+      { name => 'abstract',  type=>'TEXT'},
+      { name => 'mime',      type=>'TEXT'},
+      { name => 'lang',      type=>'TEXT'},
+      { name => 'owner',     type=>'TEXT'},
+      { name => 'copyright', type=>'TEXT'},
+      { name => 'hostname',  type=>'TEXT'},
       #--------------------------------------------------
-      creationdate     =>{ type=>'DATETIME'},
-      lastrevisiondate =>{ type=>'DATETIME'},
+      { name => 'creationdate',     type=>'DATETIME'},
+      { name => 'lastrevisiondate', type=>'DATETIME'},
       #--------------------------------------------------
       );
 
@@ -1787,9 +1980,8 @@ Returns: the identifier of the table on
 ######################################################################
 sub create_results_table {
     my $table = &Apache::lonmysql::create_table
-        ( { columns => \%Datatypes,
-            column_order => \@DataOrder,
-            fullindex => \@Fullindicies,
+        ( { columns => \@Datatypes,
+            FULLTEXT => [{'columns' => \@Fullindicies},],
         } );
     if (defined($table)) {
         $ENV{'form.table'} = $table;
@@ -1871,6 +2063,7 @@ sub revise_button {
     $revise_phase = 'disp_adv' if ($ENV{'form.searchmode'} eq 'advanced');
     my $newloc = '/adm/searchcat'.
         '?persistent_db_id='.$ENV{'form.persistent_db_id'}.
+            '&cleargroupsort=1'.
             '&phase='.$revise_phase;
     my $result = qq{<input type="button" value="Revise search" name="revise"} .
         qq{ onClick="parent.location='$newloc';" /> };
@@ -1972,25 +2165,33 @@ END
     my $time_remaining = $max_time - (time - $starttime) ;
     my $last_time = $time_remaining;
     &update_seconds($r,$time_remaining);
-    while (($time_remaining > 0) && 
+    &update_status($r,'contacting '.$Servers_to_contact[0]);
+    while (($time_remaining > 0) &&
            ((@Servers_to_contact) || keys(%Server_status))) {
         # Send out a search request if it needs to be done.
         if (@Servers_to_contact) {
             # Contact one server
             my $server = shift(@Servers_to_contact);
+            &update_status($r,'contacting '.$server);
             my $reply=&Apache::lonnet::metadata_query($query,$customquery,
                                                       $customshow,[$server]);
             ($server) = keys(%$reply);
             $Server_status{$server} = $reply->{$server};
-            &update_status($r,'contacting '.$server);
         } else {
             # wait a sec. to give time for files to be written
             # This sleep statement is here instead of outside the else 
             # block because we do not want to pause if we have servers
             # left to contact.  
+            if (scalar (keys(%Server_status))) {
+                &update_status($r,
+                               'waiting on '.(join(' ',keys(%Server_status))));
+            }
             sleep(1); 
         }
-        &update_status($r,'waiting on '.(join(' ',keys(%Server_status))));
+        #
+        #
+        # Loop through the servers we have contacted but do not
+        # have results from yet, looking for results.
         while (my ($server,$status) = each(%Server_status)) {
             last if ($connection->aborted());
             if ($status eq 'con_lost') {
@@ -2053,6 +2254,7 @@ END
         }
         last if ($connection->aborted());
         # Finished looping through the servers
+        $starttime = time if (@Servers_to_contact);
         $time_remaining = $max_time - (time - $starttime) ;
         if ($last_time - $time_remaining > 0) {
             $last_time = $time_remaining;
@@ -2144,6 +2346,10 @@ sub display_results {
         return;
     }
     ##
+    ## $checkbox_num is a count of the number of checkboxes output on the 
+    ## page this is used only during catalogmode=groupsearch.
+    my $checkbox_num = 0;
+    ##
     ## Get the catalog controls setup
     ##
     my $action = "/adm/searchcat?phase=results";
@@ -2199,7 +2405,8 @@ sub display_results {
          ."</center>\n"
          );
     if ($total_results == 0) {
-        $r->print("There are currently no results.\n".
+        $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="1">'.
+                  '<h3>There are currently no results.</h3>'.
                   "</form></body></html>");
         return;
     } else {
@@ -2216,13 +2423,13 @@ sub display_results {
     ##
     foreach my $row (@Results) {
         if ($connection->aborted()) {
-            untie %groupsearch_db if (tied(%groupsearch_db));
-            &Apache::lonmysql::disconnect_from_db();
+            &cleanup();
             return;
         }
         my %Fields = %{&parse_row(@$row)};
         my $output="<p>\n";
-        my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'});
+        my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'},
+                                       $Fields{'id'},$checkbox_num++);
         # Render the result into html
         $output.= &$viewfunction($prefix,%Fields);
         # Print them out as they come in.
@@ -2253,22 +2460,18 @@ sub display_results {
 
 =pod
 
-=item &catalogmode_output($title,$url)
+=item &catalogmode_output($title,$url,$fnum,$checkbox_num)
 
 Returns html needed for the various catalog modes.  Gets inputs from
-$ENV{'form.catalogmode'}.  Stores data in %groupsearch_db and $fnum 
-(local variable).
+$ENV{'form.catalogmode'}.  Stores data in %groupsearch_db.
 
 =cut
 
 ######################################################################
 ######################################################################
-{ 
-my $fnum = 0;
-
 sub catalogmode_output {
     my $output = '';
-    my ($title,$url) = @_;
+    my ($title,$url,$fnum,$checkbox_num) = @_;
     if ($ENV{'form.catalogmode'} eq 'interactive') {
         $title=~ s/\'/\\\'/g;
         if ($ENV{'form.catalogmode'} eq 'interactive') {
@@ -2284,15 +2487,12 @@ END
         $output.=<<END;
 <font size='-1'>
 <input type="checkbox" name="returnvalues" value="SELECT"
-onClick="javascript:queue($fnum)" />
+onClick="javascript:queue($checkbox_num,$fnum)" />
 </font>
 END
-        $fnum++;
     }
     return $output;
 }
-
-}
 ######################################################################
 ######################################################################
 
@@ -2310,7 +2510,7 @@ sub parse_row {
     my @Row = @_;
     my %Fields;
     for (my $i=0;$i<=$#Row;$i++) {
-        $Fields{$DataOrder[$i]}=&Apache::lonnet::unescape($Row[$i]);
+        $Fields{$Datatypes[$i]->{'name'}}=&Apache::lonnet::unescape($Row[$i]);
     }
     $Fields{'language'} = 
         &Apache::loncommon::languagedescription($Fields{'lang'});
@@ -2540,8 +2740,8 @@ SCRIPT
     }
     $result.=<<SCRIPT if $ENV{'form.catalogmode'} eq 'groupsearch';
 <script type="text/javascript">
-    function queue(val) {
-        if (document.forms.results.returnvalues[val].checked) {
+    function queue(checkbox_num,val) {
+        if (document.forms.results.returnvalues[checkbox_num].checked) {
             parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b';
         } else {
             parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b';
@@ -2596,6 +2796,7 @@ sub print_frames_interface {
 <head>
 <script>
 var targetwin = opener;
+var queue = '';
 </script>
 <title>LON-CAPA Digital Library Search Results</title>
 </head>
@@ -2934,6 +3135,17 @@ sub start_fresh_session {
 
 1;
 
+sub cleanup {
+    if (tied(%groupsearch_db)) {
+	&Apache::lonnet::logthis('Cleanup searchcat: groupsearch_db');
+        unless (untie(%groupsearch_db)) {
+	  &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db');
+        }
+    }
+    &untiehash();
+    &Apache::lonmysql::disconnect_from_db();
+}
+
 __END__
 
 =pod