# The LearningOnline Network with CAPA # Search Catalog # # $Id: lonsearchcat.pm,v 1.149 2002/07/30 20:26:05 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; 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; # 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','pause', 'phase','persistent_db_id','table','start','show']); ## ## 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(3); 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"; # # 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; } my $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'; ## if (! &get_persistent_form_data($r,$persistent_db_file)) { &write_status($r,"Unable to get persistent data"); } ## ## Clear out old values from groupsearch database ## untie %groupsearch_db if (tied(%groupsearch_db)); 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 } else { $closebutton = ''; $importbutton = ''; } ## ## Sanity checks on form elements ## if (!defined($ENV{'form.viewselect'})) { $ENV{'form.viewselect'} ="Detailed Citation View"; } $ENV{'form.phase'} = 'disp_basic' if (! exists($ENV{'form.phase'})); ## ## Switch on the phase ## if ($ENV{'form.phase'} eq 'disp_basic') { &print_basic_search_form($r,$closebutton); } elsif ($ENV{'form.phase'} eq 'disp_adv') { &print_advanced_search_form($r,$closebutton); } elsif ($ENV{'form.phase'} eq 'results') { &display_results($r,$importbutton,$closebutton); } elsif($ENV{'form.phase'} eq 'run_search') { my ($query,$customquery,$customshow,$libraries,$pretty_string) = &get_persistent_data($persistent_db_file, ['query','customquery','customshow', 'libraries','pretty_string']); &write_status($r,"query = $query"); &write_status($r,"customquery = $customquery"); &write_status($r,"customshow = $customshow"); &write_status($r,"libraries = $libraries"); &write_status($r,"pretty_string = $pretty_string"); &run_search($r,$query,$customquery,$customshow, $libraries,$pretty_string); } elsif(($ENV{'form.phase'} eq 'basic_search') || ($ENV{'form.phase'} eq 'adv_search')) { # Set up table if (! defined(&create_results_table())) { $r->print(<Search Error Unable to create table in which to store search results. The search has been aborted. END return OK; } delete($ENV{'form.launch'}); if (! &make_form_data_persistent($r,$persistent_db_file)) { $r->print(<Search Error Unable to properly store search information. The search has been aborted. END return OK; } # # We are running a search my ($query,$customquery,$customshow,$libraries) = (undef,undef,undef,undef); my $pretty_string; if ($ENV{'form.phase'} eq 'basic_search') { ($query,$pretty_string) = &parse_basic_search($r,$closebutton); } else { # Advanced search ($query,$customquery,$customshow,$libraries,$pretty_string) = &parse_advanced_search($r,$closebutton); return OK if (! defined($query)); } &make_persistent($r, { query => $query, customquery => $customquery, customshow => $customshow, libraries => $libraries, pretty_string => $pretty_string }, $persistent_db_file); ## ## Print out the frames interface ## &print_frames_interface($r); } return OK; } ###################################################################### ###################################################################### =pod =item &print_basic_search_form() Returns a scalar which holds html for the basic search form. =cut ###################################################################### ###################################################################### sub print_basic_search_form{ my ($r,$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 $r->print($scrout); return; } ###################################################################### ###################################################################### =pod =item &advanced_search_form() Returns a scalar which holds html for the advanced search form. =cut ###################################################################### ###################################################################### sub print_advanced_search_form{ my ($r,$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 $r->print($scrout); return; } ###################################################################### ###################################################################### =pod =item &get_persistent_form_data Inputs: filename of database Outputs: returns undef on database errors. This function is the reverse of &make_persistent() for form data. Retrieve persistent data from %persistent_db. Retrieved items will have their values unescaped. If a form value already exists in $ENV, it will not be overwritten. Form values that are array references may have values appended to them. =cut ###################################################################### ###################################################################### sub get_persistent_form_data { my $r = shift; my $filename = shift; return 0 if (! -e $filename); return undef if (! tie(%persistent_db,'GDBM_File',$filename, &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); # # Loop through the keys, looking for 'form.' foreach my $name (keys(%persistent_db)) { next if ($name !~ /^form./); my @values = map { &Apache::lonnet::unescape($_); } split(',',$persistent_db{$name}); next if (@values <1); if (exists($ENV{$name})) { if (ref($ENV{$name}) eq 'ARRAY') { # If it is an array, tack @values on the end of it. $ENV{$name} = [@$ENV{$name},@values]; } elsif (! ref($ENV{$name}) && $arrays_allowed{$name}) { # if arrays are allowed, turn it into one and add @values $ENV{$name} = [$ENV{$name},@values]; } # otherwise, assume the value in $ENV{$name} is better than ours. } else { if ($arrays_allowed{$name}) { $ENV{$name} = [@values]; } else { $ENV{$name} = $values[0] if ($values[0]); } } } untie (%persistent_db); return 1; } ###################################################################### ###################################################################### =pod =item &get_persistent_data Inputs: filename of database, ref to array of values to recover. Outputs: array of values. Returns undef on error. 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. =cut ###################################################################### ###################################################################### sub get_persistent_data { my $filename = shift; my @Vars = @{shift()}; my @Values; # Return array return undef if (! -e $filename); return undef if (! tie(%persistent_db,'GDBM_File',$filename, &GDBM_READER(),0640)); foreach my $name (@Vars) { if (! exists($persistent_db{$name})) { push @Values, undef; next; } my @values = map { &Apache::lonnet::unescape($_); } split(',',$persistent_db{$name}); if (@values == 1) { push @Values,$values[0]; } else { push @Values,\@values; } } untie (%persistent_db); return @Values; } ###################################################################### ###################################################################### =pod =item &make_persistent() Inputs: Hash of values to save, filename of persistent database. Store 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 $r = shift; my %save = %{shift()}; my $filename = shift; return undef if (! tie(%persistent_db,'GDBM_File', $filename,&GDBM_WRCREAT(),0640)); foreach my $name (keys(%save)) { next if (! exists($save{$name})); next if (! defined($save{$name}) || $save{$name} eq ''); my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); # We handle array references, but not recursively. my $store = join(',', map { &Apache::lonnet::escape($_); } @values ); $persistent_db{$name} = $store; } untie(%persistent_db); return 1; } ###################################################################### ###################################################################### =pod =item &make_form_data_persistent() Inputs: filename of persistent database. Store most form 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_form_data_persistent { my $r = shift; my $filename = shift; my %save; foreach (keys(%ENV)) { next if (! /^form/ || /submit/); $save{$_} = $ENV{$_}; } return &make_persistent($r,\%save,$filename); } ###################################################################### # 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