# The LearningOnline Network with CAPA # Search Catalog # # $Id: lonsearchcat.pm,v 1.145 2002/07/28 20:02:14 matthew 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/ # # YEAR=2001 # 3/8, 3/12, 3/13, 3/14, 3/15, 3/19 Scott Harrison # 3/20, 3/21, 3/22, 3/26, 3/27, 4/2, 8/15, 8/24, 8/25 Scott Harrison # 10/12,10/14,10/15,10/16,11/28,11/29,12/10,12/12,12/16 Scott Harrison # YEAR=2002 # 1/17 Scott Harrison # 6/17 Matthew Hall # ############################################################################### ############################################################################### =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 seperate window. =head1 Internals =over 4 =cut ############################################################################### ############################################################################### ############################################################################### ## ## ## ORGANIZATION OF THIS PERL MODULE ## ## ## ## 1. Modules used by this module ## ## 2. Variables used throughout the module ## ## 3. handler subroutine called via Apache and mod_perl ## ## 4. Other subroutines ## ## ## ############################################################################### package Apache::lonsearchcat; # ------------------------------------------------- modules used by this module use strict; use Apache::Constants qw(:common); use Apache::lonnet(); use Apache::File(); use CGI qw(:standard); use Text::Query; use DBI; use GDBM_File; use Apache::loncommon(); use Apache::lonmysql(); # ---------------------------------------- variables used throughout the module ###################################################################### ###################################################################### =pod =item Global variables =over 4 =item $importbutton button to take the select results and go to group sorting =item %groupsearch_db Database hash used to save values for the groupsearch RAT interface. =item $diropendb The full path to the (temporary) search database file. This is set and used in &handler() and is also used in &output_results(). =item %Views Hash which associates an output view description with the function that produces it. Adding a new view type should be as easy as adding a line to the definition of this hash and making sure the function takes the proper parameters. =back =cut ###################################################################### ###################################################################### # -- dynamically rendered interface components my $importbutton; # button to take the selected results and go to group sorting # -- miscellaneous variables my %groupsearch_db; # database hash my $diropendb = ""; # db file # View Description Function Pointer my %Views = ("Detailed Citation View" => \&detailed_citation_view, "Summary View" => \&summary_view, "Fielded Format" => \&fielded_format_view, "XML/SGML" => \&xml_sgml_view ); my $persistent_db_file; my %persistent_db; my $hidden_fields; ###################################################################### ###################################################################### =pod =item &handler() - main handler invoked by httpd child =item Variables =over 4 =item $hidden holds 'hidden' html forms =item $scrout string that holds portions of the screen output =back =cut ###################################################################### ###################################################################### sub handler { my $r = shift; # untie %groupsearch_db if (tied(%groupsearch_db)); # my $closebutton; # button that closes the search window # This button is different for the RAT compared to # normal invocation. # $r->content_type('text/html'); $r->send_http_header; return OK if $r->header_only; ## ## Pick up form fields passed in the links. ## &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['catalogmode','launch','acts','mode','form','element', 'reqinterface','persistent_db_id','table']); ## ## 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"; # # 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.persistent_db_id'} = time; } $persistent_db_file = "/home/httpd/perl/tmp/". &Apache::lonnet::escape($domain). '_'.&Apache::lonnet::escape($ENV{'user.name'}). '_'.$ENV{'form.persistent_db_id'}.'_persistent_search.db'; # # Read in the database. It should (hopefully) not be catastrophic to # fail in this exercise. if (-e $persistent_db_file) { # Read in the previous values, if we can. if (tie(%persistent_db,'GDBM_File',$persistent_db_file, &GDBM_READER,0640)) { &reconstruct_persistent_form_data($r); untie (%persistent_db); } } ## ## Clear out old values from groupsearch database ## if ($ENV{'form.launch'} eq '1') { if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { &start_fresh_session(); untie %groupsearch_db; } else { $r->print('Unable to tie hash to db '. 'file'); return OK; } } ## ## Configure dynamic components of interface ## $hidden_fields = ''; if ($ENV{'form.catalogmode'} eq 'interactive') { $closebutton=""."\n"; } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { $closebutton=< END $importbutton=< END } ## ## Do a search, if needed. ## my $searchtype; $searchtype = 'Basic' if ($ENV{'form.basicsubmit'} eq 'SEARCH'); $searchtype = 'Advanced' if ($ENV{'form.advancedsubmit'} eq 'SEARCH'); if ($searchtype) { ## ## make query information persistent to allow for subsequent revision ## tie(%persistent_db,'GDBM_File',$persistent_db_file,&GDBM_WRCREAT,0640); &make_persistent(\%ENV); untie(%persistent_db); # # We are running a search my ($query,$customquery,$customshow,$libraries) = (undef,undef,undef,undef); my $pretty_string; if ($searchtype eq 'Basic') { ($query,$pretty_string) = &parse_basic_search($r,$closebutton); } elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') { ($query,$customquery,$customshow,$libraries,$pretty_string) = &parse_advanced_search($r,$closebutton); return OK if (! defined($query)); } # Output some information to the user. $r->print(&search_results_header($searchtype,$pretty_string)); $r->print("Sending search request to LON-CAPA servers.
\n"); $r->rflush(); &run_search($r,$query,$customquery,$customshow,$libraries); ## ## Display the results ## &display_results($r,$searchtype,$importbutton,$closebutton); $r->rflush(); } else { # # Set the default view if it is not already set. if (!defined($ENV{'form.viewselect'})) { $ENV{'form.viewselect'} ="Detailed Citation View"; } # # remove the requested interface from the environment. my $interface; if ($ENV{'form.reqinterface'}) { $interface = lc($ENV{'form.reqinterface'}); } else { $interface = 'basic'; } ## ## Determine course of action ## if ($interface eq 'display') { # &display_results($closebutton)); } elsif ($interface eq 'advanced') { $r->print(&advanced_search_form($closebutton)); } elsif ($interface eq 'basic') { # Output normal search interface $r->print(&basic_search_form($closebutton)); } } untie (%persistent_db); return OK; } ###################################################################### ###################################################################### =pod =item &basic_search_form() Returns a scalar which holds html for the basic search form. =cut ###################################################################### ###################################################################### sub basic_search_form{ my ($closebutton) = @_; my $scrout=<<"ENDDOCUMENT"; The LearningOnline Network with CAPA

Search Catalog

$hidden_fields

Basic Search

Enter terms or phrases separated by AND, OR, or NOT then press SEARCH below.

ENDDOCUMENT $scrout.=' '.&simpletextfield('basicexp',$ENV{'form.basicexp'},40). ' '; # $scrout.=&simplecheckbox('allversions',$ENV{'form.allversions'}); # $scrout.='Search historic archives'; my $checkbox = &simplecheckbox('related',$ENV{'form.related'}); $scrout.=<Advanced Search
$checkbox use related words

   $closebutton END $scrout.=&selectbox(undef,'viewselect', $ENV{'form.viewselect'}, undef,undef,undef, sort(keys(%Views))); $scrout.=<

ENDDOCUMENT return $scrout; } ###################################################################### ###################################################################### =pod =item &advanced_search_form() Returns a scalar which holds html for the advanced search form. =cut ###################################################################### ###################################################################### sub advanced_search_form{ my ($closebutton) = @_; my $advanced_buttons = <<"END";

$closebutton

END if (!defined($ENV{'form.viewselect'})) { $ENV{'form.viewselect'} ="Detailed Citation View"; } my $scrout=<<"ENDHEADER"; The LearningOnline Network with CAPA

Advanced Catalog Search


Enter terms or phrases separated by search operators such as AND, OR, or NOT.
$advanced_buttons $hidden_fields \n"; $scrout.=&searchphrasefield_with_related('title', 'title' , $ENV{'form.title'}); $scrout.=&searchphrasefield('author', 'author' ,$ENV{'form.author'}); $scrout.=&searchphrasefield_with_related('subject', 'subject' , $ENV{'form.subject'}); $scrout.=&searchphrasefield_with_related('keywords','keywords', $ENV{'form.keywords'}); $scrout.=&searchphrasefield('URL', 'url' ,$ENV{'form.url'}); $scrout.=&searchphrasefield_with_related('notes', 'notes' , $ENV{'form.notes'}); $scrout.=&searchphrasefield_with_related('abstract','abstract', $ENV{'form.abstract'}); # Hack - an empty table row. $scrout.="\n"; $scrout.=&searchphrasefield('file
extension','mime', $ENV{'form.mime'}); $scrout.="\n"; $scrout.=&searchphrasefield('publisher
owner','owner', $ENV{'form.owner'}); $scrout.="
VIEW: ENDHEADER $scrout.=&selectbox(undef,'viewselect', $ENV{'form.viewselect'}, undef,undef,undef, sort(keys(%Views))); $scrout.="Related
Words
   
   
\n"; $ENV{'form.category'}='any' unless length($ENV{'form.category'}); $scrout.=&selectbox('File Category','category', $ENV{'form.category'}, 'any','Any category', undef, (&Apache::loncommon::filecategories())); $ENV{'form.language'}='any' unless length($ENV{'form.language'}); #---------------------------------------------------------------- # Allow restriction to multiple domains. # I make the crazy assumption that there will never be a domain 'any'. # $ENV{'form.domains'} = 'any' if (! exists($ENV{'form.domains'})); my @allowed_domains = (ref($ENV{'form.domains'}) ? @{$ENV{'form.domains'}} : ($ENV{'form.domains'}) ); my %domain_hash = (); foreach (@allowed_domains) { $domain_hash{$_}++; } 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); $scrout.="\n".''. 'DOMAINS
'. '\n"; #---------------------------------------------------------------- $scrout.=&selectbox('Limit by language','language', $ENV{'form.language'},'any','Any Language', \&{Apache::loncommon::languagedescription}, (&Apache::loncommon::languageids), ); # ------------------------------------------------ Compute date selection boxes $scrout.=< LIMIT BY CREATION DATE RANGE:
between: CREATIONDATESTART $scrout.=&dateboxes('creationdatestart',1,1,1976, $ENV{'form.creationdatestart_month'}, $ENV{'form.creationdatestart_day'}, $ENV{'form.creationdatestart_year'}, ); $scrout.="and:\n"; $scrout.=&dateboxes('creationdateend',12,31,2051, $ENV{'form.creationdateend_month'}, $ENV{'form.creationdateend_day'}, $ENV{'form.creationdateend_year'}, ); $scrout.="

"; $scrout.=< LIMIT BY LAST REVISION DATE RANGE:
between: LASTREVISIONDATESTART $scrout.=&dateboxes('lastrevisiondatestart',1,1,1976, $ENV{'form.lastrevisiondatestart_month'}, $ENV{'form.lastrevisiondatestart_day'}, $ENV{'form.lastrevisiondatestart_year'}, ); $scrout.=< LIMIT BY SPECIAL METADATA FIELDS: For resource-specific metadata, enter in an expression in the form of key=value separated by operators such as AND, OR or NOT.
Example: grandmother=75 OR grandfather=85
CUSTOMMETADATA $scrout.=&simpletextfield('custommetadata',$ENV{'form.custommetadata'}); $scrout.=< SHOW SPECIAL METADATA FIELDS: Enter in a space-separated list of special metadata fields to show in a fielded listing for each record result.
CUSTOMSHOW $scrout.=&simpletextfield('customshow',$ENV{'form.customshow'}); $scrout.=< ENDDOCUMENT return $scrout; } ###################################################################### ###################################################################### =pod =item &reconstruct_persistent_form_data This function is the reverse of &make_persistent(); Retrieve persistent data from %persistent_db. Retrieved items will have their values unescaped. If the item contains commas (before unescaping), the returned value will be an array pointer. Items will be returned in the environment in $ENV{"form.$name"}. =cut ###################################################################### ###################################################################### sub reconstruct_persistent_form_data { foreach my $name (keys %persistent_db) { # &Apache::lonnet::logthis("Reconstructing $name = $persistent_db{$name}"); my @Values = split(',',$persistent_db{$name}); my @value = map { &Apache::lonnet::unescape($_) } @Values; $name = 'form.'.$name; if (exists($ENV{$name})) { if (ref($ENV{$name})) { # Assume it is an array reference $ENV{$name} = [@{$ENV{$name}},@value]; } else { $ENV{$name} = [$ENV{$name},@value]; } } else { if (@value > 1) { $ENV{$name} = [@value]; } else { $ENV{$name} = $value[0]; } } } return; } ###################################################################### ###################################################################### =pod =item &make_persistent() Store (environment) variables away to the %persistent_db. Values will be escaped. Values that are array pointers will have their elements escaped and concatenated in a comma seperated string. =cut ###################################################################### ###################################################################### sub make_persistent { my %save = %{shift()}; foreach my $name (keys %save) { next if ($name !~ /^form\./ || $name =~ /submit/); my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); # We handle array references, but not recursively. my $store = join(',', map { &Apache::lonnet::escape($_); } @values ); $name=~s/^form\.//; $persistent_db{$name} = $store; } return ''; } ###################################################################### # HTML form building functions # ###################################################################### =pod =item HTML form building functions =over 4 =cut ############################################### ############################################### =pod =item &simpletextfield() Inputs: $name,$value,$size Returns a text input field with the given name, value, and size. If size is not specified, a value of 20 is used. =cut ############################################### ############################################### sub simpletextfield { my ($name,$value,$size)=@_; $size = 20 if (! defined($size)); return ''; } ############################################### ############################################### =pod =item &simplecheckbox() Inputs: $name,$value Returns a simple check box with the given $name. If $value eq 'on' the box is checked. =cut ############################################### ############################################### sub simplecheckbox { my ($name,$value)=@_; my $checked=''; $checked="checked" if $value eq 'on'; return ''; } ############################################### ############################################### =pod =item &fieldtitle() Input: $title Returns a scalar with html which will display $title as a search field heading. =cut ############################################### ############################################### sub fieldtitle { my $title = uc(shift()); return ''.$title. ': '; } ############################################### ############################################### =pod =item &searchphrasefield() Inputs: $title,$name,$value Returns html for a title line and an input field for entering search terms. The entry field (which is where the $name and $value are used) is a 50 column simpletextfield. The html returned is for a row in a three column table. =cut ############################################### ############################################### sub searchphrasefield { my ($title,$name,$value)=@_; return ''.&fieldtitle($title).''. &simpletextfield($name,$value,50)." \n"; } ############################################### ############################################### =pod =item &searchphrasefield_with_related() Inputs: $title,$name,$value Returns html for a title line and an input field for entering search terms and a check box for 'related words'. The entry field (which is where the $name and $value are used) is a 50 column simpletextfield. The name of the related words checkbox is "$name_related". =cut ############################################### ############################################### sub searchphrasefield_with_related { my ($title,$name,$value)=@_; return ''.&fieldtitle($title).''. &simpletextfield($name,$value,50).' '. &simplecheckbox($name.'_related',$ENV{'form.'.$name.'_related'}). " \n"; } ############################################### ############################################### =pod =item &dateboxes() Returns html selection form elements for the specification of the day, month, and year. =cut ############################################### ############################################### sub dateboxes { my ($name,$defaultmonth,$defaultday,$defaultyear, $currentmonth,$currentday,$currentyear)=@_; ($defaultmonth,$defaultday,$defaultyear)=('','',''); # # Day my $day=< END for (my $i = 1; $i<=31; $i++) { $day.="\n"; } $day.="\n"; $day=~s/(\"$currentday\")/$1 SELECTED/ if length($currentday); # # Month my $month=< END my $i = 1; foreach (qw/January February March April May June July August September October November December /){ $month .="\n"; $i++; } $month.="\n"; $month=~s/(\"$currentmonth\")/$1 SELECTED/ if length($currentmonth); # # Year (obviously) my $year=< END my $maxyear = 2051; for (my $i = 1976; $i<=$maxyear; $i++) { $year.="\n"; } $year.="\n"; $year=~s/(\"$currentyear\")/$1 SELECTED/ if length($currentyear); return "$month$day$year"; } ############################################### ############################################### =pod =item &selectbox() Returns a scalar containing an html tag. =item $default The default value of the form. Can be $anyvalue, or in @idlist. =item $anyvalue The