--- loncom/interface/lonsearchcat.pm 2001/03/19 16:45:38 1.41
+++ loncom/interface/lonsearchcat.pm 2006/03/15 20:56:16 1.256
@@ -1,680 +1,3733 @@
-# The LearningOnline Network
+# The LearningOnline Network with CAPA
# Search Catalog
#
-# 03/08/2001 Scott Harrison
+# $Id: lonsearchcat.pm,v 1.256 2006/03/15 20:56:16 albertel Exp $
#
-# Functions
+# Copyright Michigan State University Board of Trustees
#
-# handler(server reference) : interacts with the Apache server layer
-# (for /adm/searchcat URLs)
-# simpletextfield(name,value) : returns HTML formatted string for simple text
-# field
-# simplecheckbox(name,value) : returns HTML formatted string for simple
-# checkbox
-# searchphrasefield(title,name,value) : returns HTML formatted string for
-# a search expression phrase field
-# dateboxes(name, defaultmonth, defaultday, defaultyear) : returns HTML
-# formatted string
-# for a calendar date
-# selectbox(title,name,value,%HASH=options) : returns HTML formatted string for
-# a selection box field
-# advancedsearch() :
-# filled(field) : determines whether a given field has been filled
-# basicsearch() :
-# output_blank_field_error() : outputs a message saying that more fields need
-# to be filled in
-# output_results() : outputs results from search
-# build_SQL_query() : builds a SQL query string from a logical expression
-# with AND/OR keywords
+# 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::lonnet();
+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);
-my %language;
-my $scrout;
-my %metadatafields;
-my %cprtag;
-my %mimetag;
-
+######################################################################
+######################################################################
sub handler {
my $r = shift;
-
-# -------------------------------------- see if called from an interactive mode
- map {
- my ($name, $value) = split(/=/,$_);
- $value =~ tr/+/ /;
- $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
- if ($name eq 'catalogmode') {
- $ENV{'form.'.$name}=$value;
- }
- } (split(/&/,$ENV{'QUERY_STRING'}));
-
- $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 $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;
-
- %metadatafields=();
-
- my $hidden='';
- $hidden=<
+ ##
+ ## 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;
+}
-# ------------------------------------------------ First, check out environment
- $metadatafields{'owner'}=$ENV{'user.name'}.'@'.$ENV{'user.domain'};
+sub error_page {
+ my ($r,$msg) = @_;
+ $r->print(&Apache::loncommon::start_page('Search Error').
+ &mt($msg).
+ &Apache::loncommon::end_page());
+ return OK;
+}
-# --------------------------------- Compute various listings of metadata values
-
- %language=();
- $language{'any'}='Any language';
- {
- my $fh=Apache::File->new($r->dir_config('lonTabDir').'/language.tab');
- map {
- $_=~/(\w+)\s+([\w\s\-]+)/;
- $language{$1}=$2;
- } <$fh>;
+#
+# 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'} = '';
+ }
}
+}
- %cprtag=();
- $cprtag{'any'}='Any copyright/distribution';
- {
- my $fh=Apache::File->new($r->dir_config('lonIncludes').'/copyright.tab');
- map {
- $_=~/(\w+)\s+([\w\s\-]+)/;
- $cprtag{$1}=$2;
- } <$fh>;
+sub hidden_field {
+ my ($name,$value) = @_;
+ if (! defined($value)) {
+ $value = $env{'form.'.$name};
}
+ return ''.$/;
+}
- %mimetag=();
- $mimetag{'any'}='Any type';
- {
- my $fh=Apache::File->new($r->dir_config('lonTabDir').'/filetypes.tab');
- map {
- $_=~/(\w+)\s+(\w+)\s+([\w\s\-]+)/;
- $mimetag{$1}=".$1 $3";
- } <$fh>;
- }
+######################################################################
+######################################################################
+##
+## 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;
+}
- if ($ENV{'form.basicsubmit'} eq 'SEARCH') {
- return &basicsearch($r,\%ENV);
+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').".";
+ }
}
- elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') {
- return &advancedsearch($r,\%ENV);
+ 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').
+ '
'.
+ $pretty_search_string.'
'.
+ ''.&mt('Course content').': ');
+ $r->rflush();
+# ======================================================= Go through the course
+ my $c=$r->connection;
+ if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.".db",
+ &GDBM_READER(),0640)) {
+ foreach (sort(keys(%hash))) {
+ if ($c->aborted()) { last; }
+ if (($_=~/^src\_(.+)$/)) {
+ if ($hash{'randomout_'.$1} & !$env{'request.role.adv'}) {
+ next;
+ }
+ my $symb=&make_symb($1);
+ &checkonthis($r,$1,$hash{$_},0,&Apache::lonnet::gettitle($symb),
+ $fulltext,$symb,@allwords);
+ }
+ }
+ untie(%hash);
+ }
+ unless ($totalfound) {
+ $r->print('
'.&mt('No matches found in resources').'.
');
}
- $scrout=''; # building a part of screen output
- $scrout.=&searchphrasefield('Limit by title','title',
- $ENV{'form.title'});
+# Check discussions if requested
+ if ($discuss) {
+ my $totaldiscussions = 0;
+ $r->print('
'.&mt('Discussion postings').': ');
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ my @allres=$navmap->retrieveResources();
+ my %discussiontime = &Apache::lonnet::dump('discussiontimes',
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'});
+ foreach my $resource (@allres) {
+ my $result = '';
+ my $applies = 0;
+ my $symb = $resource->symb();
+ my $ressymb = $symb;
+ if ($symb =~ m#(___adm/\w+/\w+)/(\d+)/bulletinboard$#) {
+ $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard';
+ unless ($ressymb =~ m#bulletin___\d+___adm/wrapper#) {
+ $ressymb=~s#(bulletin___\d+___)#$1adm/wrapper/#;
+ }
+ }
+ if (defined($discussiontime{$ressymb})) {
+ my %contrib = &Apache::lonnet::restore($ressymb,$env{'request.course.id'},
+ $env{'course.'.$env{'request.course.id'}.'.domain'},
+ $env{'course.'.$env{'request.course.id'}.'.num'});
+ if ($contrib{'version'}) {
+ for (my $id=1;$id<=$contrib{'version'};$id++) {
+ unless (($contrib{'hidden'}=~/\.$id\./) || ($contrib{'deleted'}=~/\.$id\./)) {
+ if ($contrib{$id.':subject'}) {
+ $result .= $contrib{$id.':subject'};
+ }
+ if ($contrib{$id.':message'}) {
+ $result .= $contrib{$id.':message'};
+ }
+ if ($contrib{$id,':attachmenturl'}) {
+ if ($contrib{$id,':attachmenturl'} =~ m-/([^/]+)$-) {
+ $result .= $1;
+ }
+ }
+ $applies = &checkwords($result,$applies,@allwords);
+ }
+ }
+ }
+ }
+# Does this discussion apply?
+ if ($applies) {
+ my ($map,$ind,$url)=&Apache::lonnet::decode_symb($ressymb);
+ my $disctype = &mt('resource');
+ if ($url =~ m#/bulletinboard$#) {
+ if ($url =~m#^adm/wrapper/adm/.*/bulletinboard$#) {
+ $url =~s#^adm/wrapper##;
+ }
+ $disctype = &mt('bulletin board');
+ } else {
+ $url = '/res/'.$url;
+ }
+ if ($url =~ /\?/) {
+ $url .= '&symb=';
+ } else {
+ $url .= '?symb=';
+ }
+ $url .= &Apache::lonnet::escape($resource->symb());
+ my $title = $resource->compTitle();
+ $r->print(' '.
+ ($title?$title:$url).' - '.$disctype.' ');
+ $totaldiscussions++;
+ } else {
+ $r->print(' .');
+ }
+ }
+ unless ($totaldiscussions) {
+ $r->print('
'.&mt('No matches found in postings').'.
');
+ }
+ }
+
+# =================================================== Done going through course
+ $r->print(&Apache::loncommon::end_page());
+}
- $scrout.=&searchphrasefield('Limit by author','author',
- $ENV{'form.author'});
+# =============================== This pulls up a resource and its dependencies
- $scrout.=&searchphrasefield('Limit by subject','subject',
- $ENV{'form.subject'});
+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(' ');
+ for (my $i=0;$i<=$level*5;$i++) {
+ $r->print(' ');
+ }
+ my $href=$url;
+ if ($hash{'encrypted_'.$id} && !$env{'request.role.adv'}) {
+ $href=&Apache::lonenc::encrypted($href)
+ .'?symb='.&Apache::lonenc::encrypted($symb);
+ } else {
+ $href.='?symb='.&Apache::lonnet::escape($symb);
+ }
+ $r->print(''.($title?$title:$url).
+ ' ');
+ $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{$id})) {
+ &checkonthis($r,$id,$_,$level+1,'',$fulltext,undef,@allwords);
+ }
+ }
+}
- $scrout.=&searchphrasefield('Limit by keywords','keywords',
- $ENV{'form.keywords'});
+sub checkwords {
+ my ($result,$applies,@allwords) = @_;
+ foreach (@allwords) {
+ if ($_=~/\w/) {
+ if ($result=~/$_/si) {
+ $applies++;
+ }
+ }
+ }
+ return $applies;
+}
- $scrout.=&searchphrasefield('Limit by URL','url',
- $ENV{'form.url'});
+sub untiehash {
+ if (tied(%hash)) {
+ untie(%hash);
+ }
+}
- $scrout.=&searchphrasefield('Limit by version','version',
- $ENV{'form.version'});
+} # End of course search scoping
- $scrout.=&searchphrasefield('Limit by notes','notes',
- $ENV{'form.notes'});
- $scrout.=&searchphrasefield('Limit by abstract','abstract',
- $ENV{'form.abstract'});
+######################################################################
+######################################################################
- $ENV{'form.mime'}='notxxx' unless length($ENV{'form.mime'});
- $scrout.=&selectbox('Limit by MIME type','mime',
- $ENV{'form.mime'},%mimetag);
+=pod
- $ENV{'form.language'}='any' unless length($ENV{'form.language'});
+=item &print_basic_search_form()
+
+Prints the form for the basic search. Sorry the name is so cryptic.
+
+=cut
+
+######################################################################
+######################################################################
+sub print_basic_search_form {
+ my ($r,$closebutton,$hidden_fields) = @_;
+ my $result = ($env{'form.catalogmode'} ne 'groupsearch');
+ my $bread_crumb =
+ &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching','Search_Basic',
+ undef,undef,
+ $env{'form.catalogmode'} ne 'groupsearch');
+ my $scrout = &Apache::loncommon::start_page('Search').$bread_crumb;
+ if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) {
+ # Define interface components
+ my $userelatedwords= '';
+ my $onlysearchdomain='';
+ my $inclext= '';
+ my $adv_search_link =
+ ''.&mt('Advanced Search').'';
+ #
+ $scrout.='';
+ }
+ if ($env{'request.course.id'}) {
+ my %lt=&Apache::lonlocal::texthash('srch' => 'Search',
+ 'header' => 'Course Search',
+ 'note' => 'Enter terms or phrases, then press "Search" below',
+ 'use' => 'use related words',
+ 'full' =>'fulltext search (time consuming)',
+ 'disc' => 'search discussion postings (resources and bulletin boards)',
+ );
+ $scrout.=(<
+
+
+ENDENDCOURSE
+ }
+ $scrout .= &Apache::loncommon::end_page();
+ $r->print($scrout);
+ return;
+}
+######################################################################
+######################################################################
- $scrout.=&selectbox('Limit by language','language',
- $ENV{'form.language'},%language);
-
+=pod
-# ------------------------------------------------ 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.=<";
+=item &advanced_search_form()
- $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.=<';
-
- $scrout.=&searchphrasefield('Limit by publisher/owner','owner',
- $ENV{'form.owner'});
-# $metadatafields{'owner'});
-
- $ENV{'form.copyright'}='any' unless length($ENV{'form.copyright'});
- $scrout.=&selectbox('Limit by copyright/distribution','copyright',
- $ENV{'form.copyright'},%cprtag);
+Prints the advanced search form.
-# ------------------------------------------- Compute customized metadata field
- $scrout.=<
-LIMIT BY OTHER METADATA FIELDS:
-
-For author-specific metadata, enter in an expression in the form of
-key=value separated by operators such as AND or OR.
-Example: grandmother=75 OR grandfather=85
-
-CUSTOMMETADATA
-$scrout.=&simpletextfield('custommetadata',$ENV{'form.custommetadata'});
-$scrout.=' initial users of this system do not need to worry about this option';
-
-# ---------------------------------------------------------------- Print screen
- $r->print(<
-
-The LearningOnline Network with CAPA
-
-
-
-
Search Catalog
-".
+ &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;
}
-sub output_blank_field_error {
- my ($r)=@_;
- # make query information persistent to allow for subsequent revision
- my $persistent='';
- map {
- if (/^form\./ && !/submit/) {
- my $name=$_;
- my $key=$name;
- $name=~s/^form\.//;
- $persistent.=<
-END
- }
- } (keys %ENV);
-
- $r->print(<
-
-The LearningOnline Network with CAPA
-BEGINNING
- $r->print(<
-
-
-
Search Catalog
-".
+ &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 $result;
+ my $html = &Apache::lonxml::xmlbegin();
+ my $head = &Apache::lonxml::headtag('Results');
+ my $end_head = &Apache::loncommon::endheadtag();
+ $result.=<
+ 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_head
+$bodytag
+
+END
+ $r->print($result.&Apache::loncommon::end_page());
+ return;
}
-# ----------------------------- format and output results based on a reply list
-sub output_results {
- my ($r,$envhash,@replylist)=@_;
- my %ENV=%{$envhash};
- foreach my $reply (@replylist) {
-
- my @results;
-
- my $replyfile='';
- $reply=~/^([\.\w]+)$/; # must do since 'use strict' checks for tainting
- $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1;
- $reply=~/(.*?)\_/;
- my $hostname=$1;
-
- {
- while (1) {
- last if -e $replyfile;
- sleep 1;
- }
- # QUESTION: how should I handle this error condition..
- # I'm sure there is syntax elsewhere I can use..
- my $fh=Apache::File->new($replyfile) or
- ($r->print('file cannot be opened') and return OK);
- @results=<$fh>;
- }
+#####################################################################
+#####################################################################
- my $compiledresult='';
+=pod
- foreach my $result (@results) {
- my ($title,$author,$subject,$url,$keywords,$version,
- $notes,$abstract,$mime,$lang,
- $creationdate,$lastrevisiondate,$owner,$copyright
- )=map {&Apache::lonnet::unescape($_)} (split(/\,/,$result));
- my $shortabstract=$abstract;
- $shortabstract=substr($abstract,0,200) if length($abstract)>200;
- $compiledresult.=<
+=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: none.
+
+Returns: the identifier of the table on success, undef on error.
+
+=cut
+
+######################################################################
+######################################################################
+sub set_up_table_structure {
+ my ($datatypes,$fullindicies) =
+ &LONCAPA::lonmetadata::describe_metadata_storage();
+ # 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 {
+ &set_up_table_structure();
+ 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) = @_;
+ my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
+ $bodytag.=&Apache::lonhtmlcommon::breadcrumbs
+ (undef,'Searching','Searching',undef,undef,
+ $env{'form.catalogmode'} ne 'groupsearch');
+ my $connection = $r->connection;
+ #
+ # Print run_search header
+ #
+ my $html = &Apache::lonxml::xmlbegin();
+ my $head = &Apache::loncommon::head('Search Status');
+ $r->print(<
+
END
- $compiledresult.=<
+ $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 {
+ @Servers_to_contact = sort(keys(%Apache::lonnet::libserv));
+ }
+ 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 $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.
+ foreach my $row (@Results) {
+ if ($connection->aborted()) {
+ &cleanup();
+ return;
+ }
+ my %Fields = %{&parse_row(@$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 $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(<
+$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
-
-$persistent
-
-