# The LearningOnline Network with CAPA # Search Catalog # # $Id: lonsearchcat.pm,v 1.256 2006/03/15 20:56:16 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 :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(); ###################################################################### ###################################################################### ## ## 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; # &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 $bodytag; # LON-CAPA standard body tag, gotten from # &Apache::lonnet::bodytag. # No title, no table, just a
tag. 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']); ## ## 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'}_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; } $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); 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'; ## &Apache::lonhtmlcommon::clear_breadcrumbs(); if (exists($env{'request.course.id'}) && $env{'request.course.id'} ne '') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/searchcat?'. 'catalogmode='.$env{'form.catalogmode'}. '&launch='.$env{'form.launch'}. '&mode='.$env{'form.mode'}, text=>"Course and Catalog Search", target=>'_top', bug=>'Searching',}); } else { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/searchcat?'. 'catalogmode='.$env{'form.catalogmode'}. '&launch='.$env{'form.launch'}. '&mode='.$env{'form.mode'}, text=>"Catalog Search", target=>'_top', bug=>'Searching',}); } # if ($env{'form.phase'} !~ m/(basic|adv|course)_search/) { if (! &get_persistent_form_data($persistent_db_file)) { if ($env{'form.phase'} =~ /(run_search|results)/) { &Apache::lonnet::logthis('lonsearchcat:'. 'Unable to recover data from '. $persistent_db_file); my $msg = 'We were unable to retrieve data describing your search. '. 'This is a serious error and has been logged. '. 'Please alert your LON-CAPA administrator.'; return &error_page($r,$msg); } } } else { &clean_up_environment(); } ## ## Clear out old values from groupsearch database ## untie %groupsearch_db if (tied(%groupsearch_db)); 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. my $msg = 'Unable to tie hash to db file.'; return &error_page($r,$msg); } } ## ## Configure hidden fields ## $hidden_fields = ''."\n"; if (exists($env{'form.catalogmode'})) { $hidden_fields .= &hidden_field('catalogmode'); } if (exists($env{'form.form'})) { $hidden_fields .= &hidden_field('form'); } if (exists($env{'form.element'})) { $hidden_fields .= &hidden_field('element'); } if (exists($env{'form.titleelement'})) { $hidden_fields .= &hidden_field('titleelement'); } if (exists($env{'form.mode'})) { $hidden_fields .= &hidden_field('mode'); } ## ## Configure dynamic components of interface ## if ($env{'form.catalogmode'} eq 'interactive') { $closebutton=" END } else { $closebutton = ''; $importbutton = ''; } ## ## Sanity checks on form elements ## if (!defined($env{'form.viewselect'})) { $env{'form.viewselect'} ="summary"; } $env{'form.phase'} = 'disp_basic' if (! exists($env{'form.phase'})); $env{'form.show'} = 20 if (! exists($env{'form.show'})); # $env{'form.searchmode'} = 'basic' if (! exists($env{'form.searchmode'})); if ($env{'form.phase'} eq 'adv_search' || $env{'form.phase'} eq 'disp_adv') { $env{'form.searchmode'} = 'advanced'; } elsif ($env{'form.phase'} eq 'course_search') { $env{'form.searchmode'} = 'course_search'; } # if ($env{'form.searchmode'} eq 'advanced') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/searchcat?phase=disp_adv&'. 'catalogmode='.$env{'form.catalogmode'}. '&launch='.$env{'form.launch'}. '&mode='.$env{'form.mode'}, text=>"Advanced Search", bug=>'Searching',}); } elsif ($env{'form.searchmode'} eq 'course search') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/searchcat?phase=disp_adv&'. 'catalogmode='.$env{'form.catalogmode'}. '&launch='.$env{'form.launch'}. '&mode='.$env{'form.mode'}, text=>"Course Search", bug=>'Searching',}); } ## ## Switch on the phase ## if ($env{'form.phase'} eq 'disp_basic') { &print_basic_search_form($r,$closebutton,$hidden_fields); } elsif ($env{'form.phase'} eq 'disp_adv') { &print_advanced_search_form($r,$closebutton,$hidden_fields); } elsif ($env{'form.phase'} eq 'results') { &display_results($r,$importbutton,$closebutton,$diropendb); } elsif ($env{'form.phase'} =~ /^(sort|run_search)$/) { my ($query,$customquery,$customshow,$libraries,$pretty_string) = &get_persistent_data($persistent_db_file, ['query','customquery','customshow', 'libraries','pretty_string']); if ($env{'form.phase'} eq 'sort') { &print_sort_form($r,$pretty_string); } elsif ($env{'form.phase'} eq 'run_search') { &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')) { # # We are running a search, try to parse it my ($query,$customquery,$customshow,$libraries) = (undef,undef,undef,undef); my $pretty_string; if ($env{'form.phase'} eq 'basic_search') { ($query,$pretty_string,$libraries) = &parse_basic_search($r,$closebutton,$hidden_fields); return OK if (! defined($query)); &make_persistent({ basicexp => $env{'form.basicexp'}}, $persistent_db_file); } else { # Advanced search ($query,$customquery,$customshow,$libraries,$pretty_string) = &parse_advanced_search($r,$closebutton,$hidden_fields); return OK if (! defined($query)); } &make_persistent({ query => $query, customquery => $customquery, customshow => $customshow, libraries => $libraries, pretty_string => $pretty_string }, $persistent_db_file); # # Set up table if (! defined(&create_results_table())) { my $errorstring=&Apache::lonmysql::get_error(); &Apache::lonnet::logthis('lonsearchcat.pm: Unable to create '. 'needed table. lonmysql error:'. $errorstring); my $msg = 'Unable to create table in which to store search results. '. 'The search has been aborted.'; return &error_page($r,$msg); } delete($env{'form.launch'}); if (! &make_form_data_persistent($r,$persistent_db_file)) { my $msg= 'Unable to properly store search information. '. 'The search has been aborted.'; return &error_page($r,$msg); } ## ## Print out the frames interface ## if (defined($query)) { &print_frames_interface($r); } } return OK; } sub error_page { my ($r,$msg) = @_; $r->print(&Apache::loncommon::start_page('Search Error'). &mt($msg). &Apache::loncommon::end_page()); return OK; } # # The mechanism used to store values away and retrieve them does not # handle the case of missing environment variables being significant. # # This routine sets non existant checkbox form elements to ''. # sub clean_up_environment { if ($env{'form.phase'} eq 'basic_search') { if (! exists($env{'form.related'})) { $env{'form.related'} = ''; } if (! exists($env{'form.domains'})) { $env{'form.domains'} = ''; } } elsif ($env{'form.phase'} eq 'adv_search') { foreach my $field ('title','keywords','notes', 'abstract','standards','mime') { if (! exists($env{'form.'.$field.'_related'})) { $env{'form.'.$field.'_related'} = ''; } } } elsif ($env{'form.phase'} eq 'course_search') { if (! exists($env{'form.crsrelated'})) { $env{'form.crsrelated'} = ''; } } } sub hidden_field { my ($name,$value) = @_; if (! defined($value)) { $value = $env{'form.'.$name}; } return ''.$/; } ###################################################################### ###################################################################### ## ## Course Search ## ###################################################################### ###################################################################### { # Scope the course search to avoid global variables # # Variables For course search my %alreadyseen; my %hash; my $totalfound; sub make_symb { my ($id)=@_; my ($mapid,$resid)=split(/\./,$id); my $map=$hash{'map_id_'.$mapid}; my $res=$hash{'src_'.$id}; my $symb=&Apache::lonnet::encode_symb($map,$resid,$res); return $symb; } sub course_search { my $r=shift; my $pretty_search_string = ''.$env{'form.courseexp'}.''; my $search_string = $env{'form.courseexp'}; my @New_Words; undef(%alreadyseen); if ($env{'form.crsrelated'}) { ($search_string,@New_Words) = &related_version($env{'form.courseexp'}); if (@New_Words) { $pretty_search_string .= ' '.&mt("with related words").": @New_Words."; } else { $pretty_search_string .= ' '.&mt('with no related words')."."; } } my $fulltext=$env{'form.crsfulltext'}; my $discuss=$env{'form.crsdiscuss'}; my @allwords=($search_string,@New_Words); $totalfound=0; $r->print(&Apache::loncommon::start_page('Course Search'). ''.&mt('No matches found in resources').'.
'); } # Check discussions if requested if ($discuss) { my $totaldiscussions = 0; $r->print(''.&mt('No matches found in postings').'.
'); } } # =================================================== Done going through course $r->print(&Apache::loncommon::end_page()); } # =============================== This pulls up a resource and its dependencies sub checkonthis { my ($r,$id,$url,$level,$title,$fulltext,$symb,@allwords)=@_; $alreadyseen{$id}=1; if (&Apache::loncommon::connection_aborted($r)) { return; } $r->rflush(); my $result=$title.' '; if ($env{'request.role.adv'} || !$hash{'encrypted_'.$id}) { $result.=&Apache::lonnet::metadata($url,'title').' '. &Apache::lonnet::metadata($url,'subject').' '. &Apache::lonnet::metadata($url,'abstract').' '. &Apache::lonnet::metadata($url,'keywords'); } my ($extension)=($url=~/\.(\w+)$/); if (&Apache::loncommon::fileembstyle($extension) eq 'ssi' && ($url) && ($fulltext)) { $result.=&Apache::lonnet::ssi_body($url.'?symb='.&Apache::lonnet::escape($symb)); } $result=~s/\s+/ /gs; my $applies = 0; $applies = &checkwords($result,$applies,@allwords); # Does this resource apply? if ($applies) { $r->print('$lt{'note'}.
ENDCOURSESEARCH
$scrout.=' '.
&Apache::lonhtmlcommon::textbox('courseexp',
$env{'form.courseexp'},40);
my $crscheckbox =
&Apache::lonhtmlcommon::checkbox('crsfulltext',
$env{'form.crsfulltext'});
my $relcheckbox =
&Apache::lonhtmlcommon::checkbox('crsrelated',
$env{'form.crsrelated'});
my $discheckbox =
&Apache::lonhtmlcommon::checkbox('crsdiscuss',
$env{'form.crsrelated'});
$scrout.=(< | |
'.
' | '. &prev_next_buttons($min,$env{'form.show'},$total_results). ' | '. &viewoptions().' |
\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 ('
\n";
$result .= ''.$values{'author'}.','.
' '.$values{'owner'}.'
';
foreach my $field
(
{ name=>'url',
translate => 'URL: [_1]',
special => 'url link',},
{ name=>'subject',
translate => 'Subject: [_1]',},
{ name=>'keywords',
translate => 'Keywords: [_1]',},
{ name=>'notes',
translate => 'Notes: [_1]',},
{ name=>'mimetag',
translate => 'MIME Type: [_1]',},
{ name=>'standards',
translate => 'Standards:[_1]',},
{ name=>'copyrighttag',
translate => 'Copyright/Distribution: [_1]',},
{ name=>'count',
format => "%d",
translate => 'Access Count: [_1]',},
{ name=>'stdno',
format => "%d",
translate => 'Number of Students: [_1]',},
{ name=>'avetries',
format => "%.2f",
translate => 'Average Tries: [_1]',},
{ name=>'disc',
format => "%.2f",
translate => 'Degree of Discrimination: [_1]',},
{ name=>'difficulty',
format => "%.2f",
translate => 'Degree of Difficulty: [_1]',},
{ name=>'clear',
format => "%.2f",
translate => 'Clear: [_1]',},
{ name=>'depth',
format => "%.2f",
translate => 'Depth: [_1]',},
{ name=>'helpful',
format => "%.2f",
translate => 'Helpful: [_1]',},
{ name=>'correct',
format => "%.2f",
translate => 'Correct: [_1]',},
{ name=>'technical',
format => "%.2f",
translate => 'Technical: [_1]',},
{ name=>'comefrom_list',
type => 'list',
translate => 'Resources that lead up to this resource in maps',},
{ name=>'goto_list',
type => 'list',
translate => 'Resources that follow this resource in maps',},
{ name=>'sequsage_list',
type => 'list',
translate => 'Resources using or importing resource',},
) {
next if (! exists($values{$field->{'name'}}) ||
$values{$field->{'name'}} eq '');
if (exists($field->{'type'}) && $field->{'type'} eq 'list') {
$result .= ''.&mt($field->{'translate'}).'
'.$values{'extrashow'}.'
'; } if (exists($values{'shortabstract'}) && $values{'shortabstract'} ne '') { $result .= ''.$values{'shortabstract'}.'
'; } $result .= ''. &detailed_citation_view($prefix,%values). ' | '. &Apache::lonindexer::showpreview($values{'url'}). ' |
'. &summary_view($prefix,%values). ' | '. &Apache::lonindexer::showpreview($values{'url'}). ' |
$errorstring
$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 $bodytag=&Apache::loncommon::bodytag('Search'); 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(<$errormsg
$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(<$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