--- loncom/interface/lonsearchcat.pm 2001/03/08 13:32:04 1.2
+++ loncom/interface/lonsearchcat.pm 2007/03/12 17:06:15 1.281
@@ -1,64 +1,3904 @@
-# The LearningOnline Network
+# The LearningOnline Network with CAPA
# Search Catalog
#
-# 03/08/2001 Scott Harrison
+# $Id: lonsearchcat.pm,v 1.281 2007/03/12 17:06:15 albertel Exp $
#
+# Copyright Michigan State University Board of Trustees
+#
+# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
+#
+# LON-CAPA is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LON-CAPA is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LON-CAPA; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# /home/httpd/html/adm/gpl.txt
+#
+# http://www.lon-capa.org/
+#
+###############################################################################
+###############################################################################
+
+=pod
+
+=head1 NAME
+
+lonsearchcat - LONCAPA Search Interface
+
+=head1 SYNOPSIS
+
+Search interface to LON-CAPAs digital library
+
+=head1 DESCRIPTION
+
+This module enables searching for a distributed browseable catalog.
+
+This is part of the LearningOnline Network with CAPA project
+described at http://www.lon-capa.org.
+
+lonsearchcat presents the user with an interface to search the LON-CAPA
+digital library. lonsearchcat also initiates the execution of a search
+by sending the search parameters to LON-CAPA servers. The progress of
+search (on a server basis) is displayed to the user in a separate window.
+
+=head1 Internals
+
+=over 4
+
+=cut
+
+###############################################################################
+###############################################################################
+
package Apache::lonsearchcat;
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 GDBM_File;
+use Apache::loncommon();
+use Apache::lonmysql();
+use Apache::lonmeta;
+use Apache::lonhtmlcommon;
+use Apache::lonlocal;
+use LONCAPA::lonmetadata();
+use HTML::Entities();
+use Parse::RecDescent;
+use Apache::lonnavmaps;
+use Apache::lonindexer();
+use LONCAPA;
+
+######################################################################
+######################################################################
+##
+## Global variables
+##
+######################################################################
+######################################################################
+my %groupsearch_db; # Database hash used to save values for the
+ # groupsearch RAT interface.
+my %persistent_db; # gdbm hash which holds data which is supposed to
+ # persist across calls to lonsearchcat.pm
+# The different view modes and associated functions
+
+my %Views = ("detailed" => \&detailed_citation_view,
+ "detailedpreview" => \&detailed_citation_preview,
+ "summary" => \&summary_view,
+ "summarypreview" => \&summary_preview,
+ "fielded" => \&fielded_format_view,
+ "xml" => \&xml_sgml_view,
+ "compact" => \&compact_view);
+
+######################################################################
+######################################################################
sub handler {
my $r = shift;
- $r->content_type('text/html');
+# &set_defaults();
+ #
+ # set form defaults
+ #
+ my $hidden_fields;# Hold all the hidden fields used to keep track
+ # of the search system state
+ my $importbutton; # button to take the selected results and go to group
+ # sorting
+ my $diropendb; # The full path to the (temporary) search database file.
+ # This is set and used in &handler() and is also used in
+ # &output_results().
+
+ my $loaderror=&Apache::lonnet::overloaderror($r);
+ if ($loaderror) { return $loaderror; }
+ #
+ my $closebutton; # button that closes the search window
+ # This button is different for the RAT compared to
+ # normal invocation.
+ #
+ &Apache::loncommon::content_type($r,'text/html');
$r->send_http_header;
return OK if $r->header_only;
+ ##
+ ## Prevent caching of the search interface window. Hopefully this means
+ ## we will get the launch=1 passed in a little more.
+ &Apache::loncommon::no_cache($r);
+ ##
+ ## Pick up form fields passed in the links.
+ ##
+ &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
+ ['catalogmode','launch','acts','mode','form','element','pause',
+ 'phase','persistent_db_id','table','start','show',
+ 'cleargroupsort','titleelement','area']);
+ ##
+ ## 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
+ ## printing the results. We only need (theoretically) to do
+ ## this once, so the pause indicator is deleted
+ ##
+ if (exists($env{'form.pause'})) {
+ sleep(1);
+ delete($env{'form.pause'});
+ }
+ ##
+ ## Initialize global variables
+ ##
+ my $domain = $r->dir_config('lonDefDomain');
+ $diropendb= "/home/httpd/perl/tmp/".
+ "$env{'user.domain'}_$env{'user.name'}_sel_res.db";
+ #
+ # set the name of the persistent database
+ # $env{'form.persistent_db_id'} can only have digits in it.
+ if (! exists($env{'form.persistent_db_id'}) ||
+ ($env{'form.persistent_db_id'} =~ /\D/) ||
+ ($env{'form.launch'} eq '1')) {
+ $env{'form.persistent_db_id'} = time;
+ }
-# ---------------------------------------------------------------- Print screen
- $r->print(<
-
-The LearningOnline Network with CAPA
-
-
-
Search Catalog
-
+ENDENDCOURSE
+ }
+ $scrout .= &Apache::loncommon::end_page();
+ $r->print($scrout);
+ return;
+}
+
+sub setup_basic_search {
+ my ($r,$area,$hidden_fields,$closebutton) = @_;
+ # Define interface components
+ my %lt = &Apache::lonlocal::texthash (
+ res => 'LON-CAPA Catalog Search',
+ portfolio => 'Portfolio Search',
+ );
+ my ($userelatedwords,$onlysearchdomain,$inclext,$adv_search_link,$scrout);
+ $userelatedwords = '';
+ $onlysearchdomain = '';
+ if ($area eq 'res') {
+ $inclext= '';
+ }
+ $adv_search_link = ''.&mt('Advanced Search').'';
+ #
+ $scrout.='';
+ return $scrout;
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &advanced_search_form()
+
+Prints the advanced search form.
+
+=cut
+
+######################################################################
+######################################################################
+sub print_advanced_search_form{
+ my ($r,$closebutton,$hidden_fields) = @_;
+ my $bread_crumb =
+ &Apache::lonhtmlcommon::breadcrumbs('Searching','Search_Advanced',
+ $env{'form.catalogmode'} ne 'import');
+ my %lt=&Apache::lonlocal::texthash('srch' => 'Search',
+ 'reset' => 'Reset',
+ 'help' => 'Help');
+ my $advanced_buttons=<<"END";
+
+
+$closebutton
+END
+ my $srchtype = 'Catalog';
+ my $jscript;
+ if ($env{'form.area'} eq 'portfolio') {
+ $srchtype = 'Portfolio';
+ $jscript = '';
+ }
+ my $scrout= &Apache::loncommon::start_page("Advanced $srchtype Search",
+ $jscript);
+ $scrout .= <<"ENDHEADER";
+$bread_crumb
+".
+ &Apache::loncommon::end_page());
+ &Apache::lonnet::logthis("lonmysql was unable to determine the status".
+ " of table ".$table);
+ return undef;
+ } elsif (! $table_check) {
+ $r->print("The table of results could not be found.");
+ &Apache::lonnet::logthis("The user requested a table, ".$table.
+ ", that could not be found.");
+ return undef;
+ }
+ return 1;
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &print_sort_form()
+
+The sort feature is not implemented at this time. This form just prints
+a link to change the search query.
+
+=cut
+
+######################################################################
+######################################################################
+sub print_sort_form {
+ my ($r,$pretty_query_string) = @_;
+
+ ##
+ my %SortableFields=&Apache::lonlocal::texthash(
+ id => 'Default',
+ title => 'Title',
+ author => 'Author',
+ subject => 'Subject',
+ url => 'URL',
+ version => 'Version Number',
+ mime => 'Mime type',
+ lang => 'Language',
+ owner => 'Owner/Publisher',
+ copyright => 'Copyright',
+ hostname => 'Host',
+ creationdate => 'Creation Date',
+ lastrevisiondate => 'Revision Date'
+ );
+ ##
+ my $table = $env{'form.table'};
+ return if (! &ensure_db_and_table($r,$table));
+ ##
+ ## Get the number of results
+ ##
+ my $total_results = &Apache::lonmysql::number_of_rows($table);
+ if (! defined($total_results)) {
+ $r->print("A MySQL error has occurred.".
+ &Apache::loncommon::end_page());
+ &Apache::lonnet::logthis("lonmysql was unable to determine the number".
+ " of rows in table ".$table);
+ &Apache::lonnet::logthis(&Apache::lonmysql::get_error());
+ return;
+ }
+ my $js =<
+ function change_sort() {
+ var newloc = "/adm/searchcat?phase=results";
+ newloc += "&persistent_db_id=$env{'form.persistent_db_id'}";
+ newloc += "&sortby=";
+ newloc += document.forms.statusform.elements.sortby.value;
+ parent.resultsframe.location= newloc;
+ }
+
+END
+
+ my $start_page = &Apache::loncommon::start_page('Results',$js,
+ {'no_title' => 1});
+ my $breadcrumbs=
+ &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching',
+ $env{'form.catalogmode'} ne 'import');
+
+ my $result = <
+
+
+END
+
+#
Sort Results
+#Sort by: \n";
+ my $revise = &revise_button();
+ $result.=<
+There are $total_results matches to your query. $revise
+
+Search: $pretty_query_string
+
+
+END
+ $r->print($result.&Apache::loncommon::end_page());
+ return;
+}
+
+#####################################################################
+#####################################################################
+
+=pod
+
+=item MySQL Table Description
+
+MySQL table creation requires a precise description of the data to be
+stored. The use of the correct types to hold data is vital to efficient
+storage and quick retrieval of records. The columns must be described in
+the following format:
+
+=cut
+
+#####################################################################
+#####################################################################
+#
+# These should probably be scoped but I don't have time right now...
+#
+my @Datatypes;
+my @Fullindicies;
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &create_results_table()
+
+Creates the table of search results by calling lonmysql. Stores the
+table id in $env{'form.table'}
+
+Inputs: search area - either res or portfolio
+
+Returns: the identifier of the table on success, undef on error.
+
+=cut
+
+######################################################################
+######################################################################
+sub set_up_table_structure {
+ my ($tabletype) = @_;
+ my ($datatypes,$fullindicies) =
+ &LONCAPA::lonmetadata::describe_metadata_storage($tabletype);
+ # Copy the table description before modifying it...
+ @Datatypes = @{$datatypes};
+ unshift(@Datatypes,{name => 'id',
+ type => 'MEDIUMINT',
+ restrictions => 'UNSIGNED NOT NULL',
+ primary_key => 'yes',
+ auto_inc => 'yes' });
+ @Fullindicies = @{$fullindicies};
+ return;
+}
+
+sub create_results_table {
+ my ($area) = @_;
+ if ($area eq 'portfolio') {
+ &set_up_table_structure('portfolio_search');
+ } else {
+ &set_up_table_structure('metadata');
+ }
+ my $table = &Apache::lonmysql::create_table
+ ( { columns => \@Datatypes,
+ FULLTEXT => [{'columns' => \@Fullindicies},],
+ } );
+ if (defined($table)) {
+ $env{'form.table'} = $table;
+ return $table;
+ }
+ return undef; # Error...
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item Search Status update functions
+
+Each of the following functions changes the values of one of the
+input fields used to display the search status to the user. The names
+should be explanatory.
+
+Inputs: Apache request handler ($r), text to display.
+
+Returns: Nothing.
+
+=over 4
+
+=item &update_count_status()
+
+=item &update_status()
+
+=item &update_seconds()
+
+=back
+
+=cut
+
+######################################################################
+######################################################################
+sub update_count_status {
+ my ($r,$text) = @_;
+ $text =~ s/\'/\\\'/g;
+ $r->print
+ ("\n");
+ $r->rflush();
+}
+
+sub update_status {
+ my ($r,$text) = @_;
+ $text =~ s/\'/\\\'/g;
+ $r->print
+ ("\n");
+ $r->rflush();
+}
+
+{
+ my $max_time = 300; # seconds for the search to complete
+ my $start_time = 0;
+ my $last_time = 0;
+
+sub reset_timing {
+ $start_time = 0;
+ $last_time = 0;
+}
+
+sub time_left {
+ if ($start_time == 0) {
+ $start_time = time;
+ }
+ my $time_left = $max_time - (time - $start_time);
+ $time_left = 0 if ($time_left < 0);
+ return $time_left;
+}
+
+sub update_seconds {
+ my ($r) = @_;
+ my $time = &time_left();
+ if (($last_time-$time) > 0) {
+ $r->print("\n");
+ $r->rflush();
+ }
+ $last_time = $time;
+}
+
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &revise_button()
+
+Inputs: None
+
+Returns: html string for a 'revise search' button.
+
+=cut
+
+######################################################################
+######################################################################
+sub revise_button {
+ my $revise_phase = 'disp_basic';
+ $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{ };
+ return $result;
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &run_search()
+
+Executes a search query by sending it the the other servers and putting the
+results into MySQL.
+
+=cut
+
+######################################################################
+######################################################################
+sub run_search {
+ my ($r,$query,$customquery,$customshow,$serverlist,
+ $pretty_string,$area) = @_;
+ my $tabletype = 'metadata';
+ if ($area eq 'portfolio') {
+ $tabletype = 'portfolio_search';
+ }
+ my $connection = $r->connection;
+ #
+ # Print run_search header
+ #
+ my $start_page = &Apache::loncommon::start_page('Search Status',undef,
+ {'no_title' => 1});
+ my $breadcrumbs =
+ &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching',
+ $env{'form.catalogmode'} ne 'import');
+ $r->print(<
+
+END
+ # Remove leading and trailing
+ $pretty_string =~ s:^\s* ::i;
+ $pretty_string =~ s:( )*\s*$::im;
+ my @Lines = split(" ",$pretty_string);
+ # I keep getting blank items at the end of the list, hence the following:
+ while ($Lines[-1] =~ /^\s*$/ && @Lines) {
+ pop(@Lines);
+ }
+ if (@Lines > 2) {
+ $pretty_string = join ' ',(@Lines[0..2],'.... ');
+ }
+ $r->print(&mt("Search: [_1]",$pretty_string));
+ $r->rflush();
+ #
+ # Determine the servers we need to contact.
+ my @Servers_to_contact;
+ if (defined($serverlist)) {
+ if (ref($serverlist) eq 'ARRAY') {
+ @Servers_to_contact = @$serverlist;
+ } else {
+ @Servers_to_contact = ($serverlist);
+ }
+ } else {
+ my %all_library_servers = &Apache::lonnet::all_library();
+ @Servers_to_contact = sort(keys(%all_library_servers));
+ }
+ my %Server_status;
+ #
+ # Check on the mysql table we will use to store results.
+ my $table =$env{'form.table'};
+ if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
+ $r->print("Unable to determine table id to store search results in.".
+ "The search has been aborted.".
+ &Apache::loncommon::end_page());
+ return;
+ }
+ my $table_status = &Apache::lonmysql::check_table($table);
+ if (! defined($table_status)) {
+ $r->print("Unable to determine status of table.".
+ &Apache::loncommon::end_page());
+ &Apache::lonnet::logthis("Bogus table id of $table for ".
+ "$env{'user.name'} @ $env{'user.domain'}");
+ &Apache::lonnet::logthis("lonmysql error = ".
+ &Apache::lonmysql::get_error());
+ return;
+ }
+ if (! $table_status) {
+ &Apache::lonnet::logthis("lonmysql error = ".
+ &Apache::lonmysql::get_error());
+ &Apache::lonnet::logthis("lonmysql debug = ".
+ &Apache::lonmysql::get_debug());
+ &Apache::lonnet::logthis('table status = "'.$table_status.'"');
+ $r->print("The table id,$table, we tried to use is invalid.".
+ "The search has been aborted.".
+ &Apache::loncommon::end_page());
+ return;
+ }
+ ##
+ ## Prepare for the big loop.
+ my $hitcountsum;
+ my %matches;
+ my $server;
+ my $status;
+ my $revise = &revise_button();
+ $r->print(<
+
'.
+ mt('Results [_1] to [_2] out of [_3]',
+ $min,$max,$total_results).
+ "
\n");
+ }
+ ##
+ ## Get results from MySQL table
+ my $sort_command = 'id>='.$min.' AND id<='.$max;
+ my $order;
+ if (exists($env{'form.sortorder'})) {
+ if ($env{'form.sortorder'} eq 'asc') {
+ $order = 'ASC';
+ } elsif ($env{'form.sortorder'} eq 'desc') {
+ $order = 'DESC';
+ } else {
+ $order = '';
+ }
+ } else {
+ $order = '';
+ }
+ if ($env{'form.sortfield'} ne 'default' &&
+ exists($sort_fields{$env{'form.sortfield'}})) {
+ $sort_command = $env{'form.sortfield'}.' IS NOT NULL '.
+ 'ORDER BY '.$env{'form.sortfield'}.' '.$order.
+ ' LIMIT '.($min-1).','.($max-$min+1);
+ }
+ my @Results = &Apache::lonmysql::get_rows($table,$sort_command);
+ ##
+ ## Loop through the results and output them.
+ my $tabletype = 'metadata';
+ if ($area eq 'portfolio') {
+ $tabletype = 'portfolio_search';
+ }
+ foreach my $row (@Results) {
+ if ($connection->aborted()) {
+ &cleanup();
+ return;
+ }
+ my %Fields = %{&parse_row($tabletype,@$row)};
+ my $output="
\n";
+ if (! defined($Fields{'title'}) || $Fields{'title'} eq '') {
+ $Fields{'title'} = 'Untitled';
+ }
+ 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.
+ $r->print($output);
+ $r->rflush();
+ }
+ if (@Results < 1) {
+ $r->print(&mt("There were no results matching your query"));
+ } else {
+ $r->print
+ ('
+$end_page
+ENDPAGE
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &output_blank_field_error()
+
+Output a complete page that indicates the user has not filled in enough
+information to do a search.
+
+Inputs: $r (Apache request handle), $closebutton, $parms.
+
+Returns: nothing
+
+$parms is extra information to include in the 'Revise search request' link.
+
+=cut
+
+######################################################################
+######################################################################
+sub output_blank_field_error {
+ my ($r,$closebutton,$parms,$hidden_fields)=@_;
+ my $errormsg = &mt('You did not fill in enough information for the search to be started. You need to fill in relevant fields on the search page in order for a query to be processed.');
+ my $revise = &mt('Revise Search Request');
+ my $heading = &mt('Unactionable Search Queary');
+ my $start_page = &Apache::loncommon::start_page('Search');
+ my $end_page = &Apache::loncommon::end_page();
+ $r->print(<
+$hidden_fields
+$closebutton
+
+
+$end_page
+ENDPAGE
+ return;
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &output_date_error()
+
+Output a full html page with an error message.
+
+Inputs:
+
+ $r, the request pointer.
+ $message, the error message for the user.
+ $closebutton, the specialized close button needed for groupsearch.
+
+=cut
+
+######################################################################
+######################################################################
+sub output_date_error {
+ my ($r,$message,$closebutton,$hidden_fields)=@_;
+ # make query information persistent to allow for subsequent revision
+ my $start_page = &Apache::loncommon::start_page('Search');
+ my $end_page = &Apache::loncommon::end_page();
+ $r->print(<
+$hidden_fields
+
+$closebutton
+
+
Error
+
+$message
+
+$end_page
+RESULTS
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &start_fresh_session()
+
+Cleans the global %groupsearch_db by removing all fields which begin with
+'pre_' or 'store'.
+
+=cut
+
+######################################################################
+######################################################################
+sub start_fresh_session {
+ delete $groupsearch_db{'mode_catalog'};
+ foreach (keys %groupsearch_db) {
+ if ($_ =~ /^pre_/) {
+ delete $groupsearch_db{$_};
+ }
+ if ($_ =~ /^store/) {
+ delete $groupsearch_db{$_};
+ }
+ }
+}
+
1;
+
+sub cleanup {
+ if (tied(%groupsearch_db)) {
+ unless (untie(%groupsearch_db)) {
+ &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db');
+ }
+ }
+ &untiehash();
+ &Apache::lonmysql::disconnect_from_db();
+ return OK;
+}
+
__END__
+
+=pod
+
+=back
+
+=cut