# The LearningOnline Network with CAPA
# Search Catalog
#
# $Id: lonsearchcat.pm,v 1.303 2008/12/19 23:24:49 raeburn 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();
use LONCAPA;
######################################################################
######################################################################
##
## Global variables
##
######################################################################
######################################################################
my %groupsearch_db; # Database hash used to save values for the
# groupsearch RAT interface.
my %persistent_db; # gdbm hash which holds data which is supposed to
# persist across calls to lonsearchcat.pm
# The different view modes and associated functions
my %Views = ("detailed" => \&detailed_citation_view,
"detailedpreview" => \&detailed_citation_preview,
"summary" => \&summary_view,
"summarypreview" => \&summary_preview,
"fielded" => \&fielded_format_view,
"xml" => \&xml_sgml_view,
"compact" => \&compact_view);
######################################################################
######################################################################
sub handler {
my $r = shift;
# &set_defaults();
#
# set form defaults
#
my $hidden_fields;# Hold all the hidden fields used to keep track
# of the search system state
my $importbutton; # button to take the selected results and go to group
# sorting
my $diropendb; # The full path to the (temporary) search database file.
# This is set and used in &handler() and is also used in
# &output_results().
my $loaderror=&Apache::lonnet::overloaderror($r);
if ($loaderror) { return $loaderror; }
#
my $closebutton; # button that closes the search window
# This button is different for the RAT compared to
# normal invocation.
#
&Apache::loncommon::content_type($r,'text/html');
$r->send_http_header;
return OK if $r->header_only;
##
## Prevent caching of the search interface window. Hopefully this means
## we will get the launch=1 passed in a little more.
&Apache::loncommon::no_cache($r);
##
## Pick up form fields passed in the links.
##
&Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
['catalogmode','launch','acts','mode','form','element','pause',
'phase','persistent_db_id','table','start','show',
'cleargroupsort','titleelement','area','inhibitmenu']);
##
## The following is a trick - we wait a few seconds if asked to so
## the daemon running the search can get ahead of the daemon
## printing the results. We only need (theoretically) to do
## this once, so the pause indicator is deleted
##
if (exists($env{'form.pause'})) {
sleep(1);
delete($env{'form.pause'});
}
##
## Initialize global variables
##
my $domain = $r->dir_config('lonDefDomain');
$diropendb= "/home/httpd/perl/tmp/".
"$env{'user.domain'}_$env{'user.name'}_sel_res.db";
#
# set the name of the persistent database
# $env{'form.persistent_db_id'} can only have digits in it.
if (! exists($env{'form.persistent_db_id'}) ||
($env{'form.persistent_db_id'} =~ /\D/) ||
($env{'form.launch'} eq '1')) {
$env{'form.persistent_db_id'} = time;
}
my $persistent_db_file = "/home/httpd/perl/tmp/".
&escape($domain).
'_'.&escape($env{'user.name'}).
'_'.$env{'form.persistent_db_id'}.'_persistent_search.db';
##
&Apache::lonhtmlcommon::clear_breadcrumbs();
my @allowed_searches = ('portfolio');
if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) {
push(@allowed_searches,'res');
}
if (exists($env{'request.course.id'}) && $env{'request.course.id'} ne '') {
push(@allowed_searches,'course');
}
my $crumb_text = 'Portfolio Search';
if (@allowed_searches == 3) {
$crumb_text = 'Course, Portfolio and Catalog Search';
} elsif (@allowed_searches ==2) {
if (grep(/^res$/,@allowed_searches)) {
$crumb_text = 'Portfolio and Catalog Search';
} elsif (grep(/^course$/,@allowed_searches)) {
$crumb_text = 'Portfolio and Course Search';
}
}
&Apache::lonhtmlcommon::add_breadcrumb
({href=>'/adm/searchcat?'.
&Apache::loncommon::inhibit_menu_check().
'&catalogmode='.$env{'form.catalogmode'}.
'&launch='.$env{'form.launch'}.
'&mode='.$env{'form.mode'},
text=>"$crumb_text",
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.';
&Apache::loncommon::simple_error_page($r,'Search Error',
$msg);
return OK;
}
}
} 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 'import'))) {
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.';
&Apache::loncommon::simple_error_page($r,'Search Error',
$msg);
return OK;
}
}
##
## 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');
}
if (exists($env{'form.area'})) {
$hidden_fields .= &hidden_field('area');
}
if (exists($env{'form.inhibitmenu'})) {
$hidden_fields .= &hidden_field('inhibitmenu');
}
##
## 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') {
my $srchtype = 'Catalog';
if ($env{'form.area'} eq 'portfolio') {
$srchtype = 'Portfolio';
}
&Apache::lonhtmlcommon::add_breadcrumb
({href=>'/adm/searchcat?'.&Apache::loncommon::inhibit_menu_check().
'&phase=disp_adv'.
'&catalogmode='.$env{'form.catalogmode'}.
'&launch='.$env{'form.launch'}.
'&mode='.$env{'form.mode'},
text=>"Advanced $srchtype Search",
bug=>'Searching',});
} elsif ($env{'form.searchmode'} eq 'course search') {
&Apache::lonhtmlcommon::add_breadcrumb
({href=>'/adm/searchcat?'.&Apache::loncommon::inhibit_menu_check().
'&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,
$env{'form.area'});
} 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,$env{'form.area'});
}
} 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($env{'form.area'}))) {
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 save search results. '.
'The search has been aborted.';
&Apache::loncommon::simple_error_page($r,'Search Error',
$msg);
return OK;
}
delete($env{'form.launch'});
if (! &make_form_data_persistent($r,$persistent_db_file)) {
my $msg=
'Unable to properly save search information. '.
'The search has been aborted.';
&Apache::loncommon::simple_error_page($r,'Search Error',
$msg);
return OK;
}
##
## Print out the frames interface
##
if (defined($query)) {
&print_frames_interface($r);
}
}
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').
'
'.
$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').'.
');
}
# Check discussions if requested
if ($discuss) {
my $totaldiscussions = 0;
$r->print('
'.&mt('Discussion postings').': ');
my $navmap = Apache::lonnavmaps::navmap->new();
if (defined($navmap)) {
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/$LONCAPA::domain_re/$LONCAPA::username_re)/(\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 .= &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').'.
');
}
} else {
$r->print('
'.&mt('An error occurred retrieving information about resources in the course.').' '.&mt('It is recommended that you [_1]re-initialize the course[_2] and then try your search again.','','').'
');
}
}
# =================================================== 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='.&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='.&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);
}
}
}
sub checkwords {
my ($result,$applies,@allwords) = @_;
foreach (@allwords) {
if ($_=~/\w/) {
if ($result=~/$_/si) {
$applies++;
}
}
}
return $applies;
}
sub untiehash {
if (tied(%hash)) {
untie(%hash);
}
}
} # End of course search scoping
######################################################################
######################################################################
=pod
=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 'import');
my $bread_crumb =
&Apache::lonhtmlcommon::breadcrumbs('Searching','Search_Basic',
$env{'form.catalogmode'} ne 'import');
my $scrout = &Apache::loncommon::start_page('Search').$bread_crumb;
# Search form for resource space
if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) {
$scrout .= &setup_basic_search($r,'res',$hidden_fields,$closebutton);
$scrout .= ' ';
}
# Search form for accessible portfolio files
$scrout.= &setup_basic_search($r,'portfolio',$hidden_fields,$closebutton);
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.=(<
$lt{'header'}
$hidden_fields
$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.=(<
ENDENDCOURSE
}
$scrout .= &Apache::loncommon::end_page();
$r->print($scrout);
return;
}
sub setup_basic_search {
my ($r,$area,$hidden_fields,$closebutton) = @_;
# Define interface components
my %lt = &Apache::lonlocal::texthash (
res => 'LON-CAPA Catalog Search',
portfolio => 'Portfolio Search',
);
my ($userelatedwords,$onlysearchdomain,$inclext,$adv_search_link,$scrout);
$userelatedwords = '';
$onlysearchdomain = '';
$adv_search_link = ''.&mt('Advanced Search').'';
#
$scrout.='';
return $scrout;
}
######################################################################
######################################################################
=pod
=item &advanced_search_form()
Prints the advanced search form.
=cut
######################################################################
######################################################################
sub print_advanced_search_form{
my ($r,$closebutton,$hidden_fields) = @_;
my $bread_crumb =
&Apache::lonhtmlcommon::breadcrumbs('Searching','Search_Advanced',
$env{'form.catalogmode'} ne 'import');
my %lt=&Apache::lonlocal::texthash('srch' => 'Search',
'reset' => 'Reset',
'help' => 'Help');
my $advanced_buttons=<<"END";
$closebutton
END
my $srchtype = 'Catalog';
my $jscript;
if ($env{'form.area'} eq 'portfolio') {
$srchtype = 'Portfolio';
$jscript = '';
}
my $scrout= &Apache::loncommon::start_page("Advanced $srchtype Search",
$jscript);
$scrout .= <<"ENDHEADER";
$bread_crumb
".
&Apache::loncommon::end_page());
&Apache::lonnet::logthis("lonmysql was unable to determine the status".
" of table ".$table);
return undef;
} elsif (! $table_check) {
$r->print("The table of results could not be found.");
&Apache::lonnet::logthis("The user requested a table, ".$table.
", that could not be found.");
return undef;
}
return 1;
}
######################################################################
######################################################################
=pod
=item &print_sort_form()
The sort feature is not implemented at this time. This form just prints
a link to change the search query.
=cut
######################################################################
######################################################################
sub print_sort_form {
my ($r,$pretty_query_string) = @_;
##
my %SortableFields=&Apache::lonlocal::texthash(
id => 'Default',
title => 'Title',
author => 'Author',
subject => 'Subject',
url => 'URL',
version => 'Version Number',
mime => 'Mime type',
lang => 'Language',
owner => 'Owner/Publisher',
copyright => 'Copyright',
hostname => 'Host',
creationdate => 'Creation Date',
lastrevisiondate => 'Revision Date'
);
##
my $table = $env{'form.table'};
return if (! &ensure_db_and_table($r,$table));
##
## Get the number of results
##
my $total_results = &Apache::lonmysql::number_of_rows($table);
if (! defined($total_results)) {
$r->print("A MySQL error has occurred.".
&Apache::loncommon::end_page());
&Apache::lonnet::logthis("lonmysql was unable to determine the number".
" of rows in table ".$table);
&Apache::lonnet::logthis(&Apache::lonmysql::get_error());
return;
}
my $js =<
function change_sort() {
var newloc = "/adm/searchcat?phase=results";
newloc += "&persistent_db_id=$env{'form.persistent_db_id'}";
newloc += "&sortby=";
newloc += document.forms.statusform.elements.sortby.value;
parent.resultsframe.location= newloc;
}
END
my $start_page = &Apache::loncommon::start_page('Results',$js,
{'no_title' => 1});
my $breadcrumbs=
&Apache::lonhtmlcommon::breadcrumbs('Searching','Searching',
$env{'form.catalogmode'} ne 'import');
my $result = <
END
#
Sort Results
#Sort by: \n";
my $revise = &revise_button();
$result.='
'
.&mt('There are [_1] matches to your query.',$total_results)
.' '.$revise.'
'.
mt('Results [_1] to [_2] out of [_3]',
$min,$max,$total_results).
"
\n");
}
##
## Get results from MySQL table
my $sort_command = 'id>='.$min.' AND id<='.$max;
my $order;
if (exists($env{'form.sortorder'})) {
if ($env{'form.sortorder'} eq 'asc') {
$order = 'ASC';
} elsif ($env{'form.sortorder'} eq 'desc') {
$order = 'DESC';
} else {
$order = '';
}
} else {
$order = '';
}
if ($env{'form.sortfield'} ne 'default' &&
exists($sort_fields{$env{'form.sortfield'}})) {
$sort_command = $env{'form.sortfield'}.' IS NOT NULL '.
'ORDER BY '.$env{'form.sortfield'}.' '.$order.
' LIMIT '.($min-1).','.($max-$min+1);
}
my @Results = &Apache::lonmysql::get_rows($table,$sort_command);
##
## Loop through the results and output them.
my $tabletype = 'metadata';
if ($area eq 'portfolio') {
$tabletype = 'portfolio_search';
}
foreach my $row (@Results) {
if ($connection->aborted()) {
&cleanup();
return;
}
my %Fields = %{&parse_row($tabletype,@$row)};
my $output="
\n";
if (! defined($Fields{'title'}) || $Fields{'title'} eq '') {
$Fields{'title'} = 'Untitled';
}
my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'},
$Fields{'id'},$checkbox_num++);
# Render the result into html
$output.= &$viewfunction($prefix,%Fields);
# Print them out as they come in.
$r->print($output);
$r->rflush();
}
if (@Results < 1) {
$r->print(&mt("There were no results matching your query"));
} else {
$r->print
('
\n"
);
}
$r->print("".&Apache::loncommon::end_page());
$r->rflush();
untie %groupsearch_db if (tied(%groupsearch_db));
return;
}
######################################################################
######################################################################
=pod
=item &catalogmode_output($title,$url,$fnum,$checkbox_num)
Returns html needed for the various catalog modes. Gets inputs from
$env{'form.catalogmode'}. Stores data in %groupsearch_db.
=cut
######################################################################
######################################################################
sub catalogmode_output {
my $output = '';
my ($title,$url,$fnum,$checkbox_num) = @_;
if ($env{'form.catalogmode'} eq 'interactive') {
$title=~ s/\'/\\\'/g;
if ($env{'form.catalogmode'} eq 'interactive') {
$output.=<
END
}
} elsif ($env{'form.catalogmode'} eq 'import') {
$groupsearch_db{"pre_${fnum}_link"}=$url;
$groupsearch_db{"pre_${fnum}_title"}=$title;
$output.=<
END
}
return $output;
}
######################################################################
######################################################################
=pod
=item &parse_row()
Parse a row returned from the database.
=cut
######################################################################
######################################################################
sub parse_row {
my ($tabletype,@Row) = @_;
my %Fields;
if (! scalar(@Datatypes)) {
&set_up_table_structure($tabletype);
}
for (my $i=0;$i<=$#Row;$i++) {
$Fields{$Datatypes[$i]->{'name'}}=&unescape($Row[$i]);
}
$Fields{'language'} =
&Apache::loncommon::languagedescription($Fields{'language'});
$Fields{'copyrighttag'} =
&Apache::loncommon::copyrightdescription($Fields{'copyright'});
$Fields{'mimetag'} =
&Apache::loncommon::filedescription($Fields{'mime'});
return \%Fields;
}
###########################################################
###########################################################
=pod
=item &parse_raw_result()
Takes a line from the file of results and parse it. Returns a hash
with keys according to column labels
In addition, the following tags are set by calling the appropriate
lonnet function: 'language', 'copyrighttag', 'mimetag'.
The 'title' field is set to "Untitled" if the title field is blank.
'abstract' and 'keywords' are truncated to 200 characters.
=cut
###########################################################
###########################################################
sub parse_raw_result {
my ($result,$hostname,$tabletype) = @_;
# conclude from self to others regarding fields
my %Fields=&LONCAPA::lonmetadata::metadata_col_to_hash
($tabletype,
map {
&unescape($_);
} (split(/\,/,$result)) );
return %Fields;
}
###########################################################
###########################################################
=pod
=item &handle_custom_fields()
=cut
###########################################################
###########################################################
sub handle_custom_fields {
my @results = @{shift()};
my $customshow='';
my $extrashow='';
my @customfields;
if ($env{'form.customshow'}) {
$customshow=$env{'form.customshow'};
$customshow=~s/[^\w\s]//g;
my @fields=map {
"$_:";
} split(/\s+/,$customshow);
@customfields=split(/\s+/,$customshow);
if ($customshow) {
$extrashow="
".join("
",@fields)."
\n";
}
}
my $customdata='';
my %customhash;
foreach my $result (@results) {
if ($result=~/^(custom\=.*)$/) { # grab all custom metadata
my $tmp=$result;
$tmp=~s/^custom\=//;
my ($k,$v)=map {&unescape($_);
} split(/\,/,$tmp);
$customhash{$k}=$v;
}
}
return ($extrashow,\@customfields,\%customhash);
}
######################################################################
######################################################################
=pod
=item &search_results_header()
Output the proper html headers and javascript code to deal with different
calling modes.
Takes most inputs directly from %env, except $mode.
=over 4
=item $mode is either (at this writing) 'Basic' or 'Advanced'
=back
The following environment variables are checked:
=over 4
=item 'form.catalogmode'
Checked for 'interactive' and 'import'.
=item 'form.mode'
Checked for existance & 'edit' mode.
=item 'form.form'
Contains the name of the form that has the input fields to set
=item 'form.element'
the name of the input field to put the URL into
=item 'form.titleelement'
the name of the input field to put the title into
=back
=cut
######################################################################
######################################################################
sub search_results_header {
my ($importbutton,$closebutton) = @_;
my $js;
# output beginning of search page
# conditional output of script functions dependent on the mode in
# which the search was invoked
if ($env{'form.catalogmode'} eq 'interactive'){
if (! exists($env{'form.mode'}) || $env{'form.mode'} ne 'edit') {
$js.=<
SCRIPT
} elsif ($env{'form.mode'} eq 'edit') {
my $form = $env{'form.form'};
my $element = $env{'form.element'};
my $titleelement = $env{'form.titleelement'};
my $changetitle;
if (!$titleelement) {
$changetitle='function changeTitle(val) {}';
} else {
$changetitle=<
function select_data(title,url) {
changeURL(url);
changeTitle(title);
parent.close();
}
$changetitle
function changeURL(val) {
if (parent.targetwin.document) {
parent.targetwin.document.forms["$form"].elements["$element"].value=val;
} else {
var url = 'forms[\"$form\"].elements[\"$element\"].value';
alert("Unable to transfer data to "+url);
}
}
SCRIPT
}
}
my $inhibit_menu = "&".&Apache::loncommon::inhibit_menu_check();
$js.=<
SCRIPT
my $start_page = &Apache::loncommon::start_page(undef,$js,
{'only_body' =>1});
my $result=<
$importbutton
END
return $result;
}
sub results_link {
my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}.
"&persistent_db_id=".$env{'form.persistent_db_id'};
my $results_link = $basic_link."&phase=results".
"&pause=1"."&start=1";
return $results_link;
}
######################################################################
######################################################################
sub print_frames_interface {
my $r = shift;
my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}.
"&persistent_db_id=".$env{'form.persistent_db_id'};
my $run_search_link = $basic_link."&phase=run_search";
my $results_link = &results_link();
my $js = <
var targetwin = opener;
var queue = '';
JS
my $start_page =
&Apache::loncommon::start_page('LON-CAPA Digital Library Search Results',
$js,
{'frameset' => 1,
'add_entries' => {
'rows' => "150,*",},});
my $end_page =
&Apache::loncommon::end_page({'frameset' => 1});
my $result = <<"ENDFRAMES";
$start_page
$end_page
ENDFRAMES
$r->print($result);
return;
}
######################################################################
######################################################################
sub has_stat_data {
my ($values) = @_;
if ( (defined($values->{'count'}) && $values->{'count'} ne '') ||
(defined($values->{'stdno'}) && $values->{'stdno'} ne '') ||
(defined($values->{'disc'}) && $values->{'disc'} ne '') ||
(defined($values->{'avetries'}) && $values->{'avetries'} ne '') ||
(defined($values->{'difficulty'}) && $values->{'difficulty'} ne '')) {
return 1;
}
return 0;
}
sub statfields {
return ('count','stdno','disc','avetries','difficulty');
}
sub has_eval_data {
my ($values) = @_;
if ( (defined($values->{'clear'}) && $values->{'clear'} ne '') ||
(defined($values->{'technical'}) && $values->{'technical'} ne '') ||
(defined($values->{'correct'}) && $values->{'correct'} ne '') ||
(defined($values->{'helpful'}) && $values->{'helpful'} ne '') ||
(defined($values->{'depth'}) && $values->{'depth'} ne '')) {
return 1;
}
return 0;
}
sub evalfields {
return ('clear','technical','correct','helpful','depth');
}
######################################################################
######################################################################
=pod
=item Metadata Viewing Functions
Output is a HTML-ified string.
Input arguments are title, author, subject, url, keywords, version,
notes, short abstract, mime, language, creation date,
last revision date, owner, copyright, hostname, and
extra custom metadata to show.
=over 4
=item &detailed_citation_view()
=cut
######################################################################
######################################################################
sub detailed_citation_view {
my ($prefix,%values) = @_;
my $result;
my $jumpurl=$values{'url'};
$jumpurl=~s|^/ext/|http://|;
$result .= ''.$prefix.
''.' '.
''.$values{'title'}."\n";
$result .= "
\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'}).'';
foreach my $item (split(',',$values{$field->{'name'}})){
$item = &Apache::lonnet::clutter($item);
$result .= &display_url($item,[2,0,1]);
}
} elsif (exists($field->{'format'}) && $field->{'format'} ne ''){
$result.= &mt($field->{'translate'},
sprintf($field->{'format'},
$values{$field->{'name'}}))." \n";
} else {
if ($field->{'special'} eq 'url link') {
$result .= &display_url($jumpurl,[3,0,1]);
} else {
$result.= &mt($field->{'translate'},
$values{$field->{'name'}});
}
$result .= " \n";
}
}
$result .= "
";
if (exists($values{'extrashow'}) && $values{'extrashow'} ne '') {
$result .= '
'.$values{'extrashow'}.'
';
}
if (exists($values{'shortabstract'}) && $values{'shortabstract'} ne '') {
$result .= '
'.$values{'shortabstract'}.'
';
}
$result .= ''."\n";
return $result;
}
sub detailed_citation_preview {
my ($prefix,%values)=@_;
return '
$end_page
ENDPAGE
}
######################################################################
######################################################################
=pod
=item &output_blank_field_error()
Output a complete page that indicates the user has not filled in enough
information to do a search.
Inputs: $r (Apache request handle), $closebutton, $parms.
Returns: nothing
$parms is extra information to include in the 'Revise search request' link.
=cut
######################################################################
######################################################################
sub output_blank_field_error {
my ($r,$closebutton,$parms,$hidden_fields)=@_;
my $errormsg = &mt('You did not fill in enough information for the search to be started. You need to fill in relevant fields on the search page in order for a query to be processed.');
my $revise = &mt('Revise Search Request');
my $heading = &mt('Unactionable Search Queary');
my $start_page = &Apache::loncommon::start_page('Search');
my $end_page = &Apache::loncommon::end_page();
$r->print(<
$hidden_fields
$closebutton
$end_page
ENDPAGE
return;
}
######################################################################
######################################################################
=pod
=item &output_date_error()
Output a full html page with an error message.
Inputs:
$r, the request pointer.
$message, the error message for the user.
$closebutton, the specialized close button needed for groupsearch.
=cut
######################################################################
######################################################################
sub output_date_error {
my ($r,$message,$closebutton,$hidden_fields)=@_;
# make query information persistent to allow for subsequent revision
my $start_page = &Apache::loncommon::start_page('Search');
my $end_page = &Apache::loncommon::end_page();
$r->print(<
$hidden_fields
$closebutton
Error
$message
$end_page
RESULTS
}
######################################################################
######################################################################
=pod
=item &start_fresh_session()
Cleans the global %groupsearch_db by removing all fields which begin with
'pre_' or 'store'.
=cut
######################################################################
######################################################################
sub start_fresh_session {
delete $groupsearch_db{'mode_catalog'};
foreach (keys %groupsearch_db) {
if ($_ =~ /^pre_/) {
delete $groupsearch_db{$_};
}
if ($_ =~ /^store/) {
delete $groupsearch_db{$_};
}
}
}
1;
sub cleanup {
if (tied(%groupsearch_db)) {
unless (untie(%groupsearch_db)) {
&Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db');
}
}
&untiehash();
&Apache::lonmysql::disconnect_from_db();
return OK;
}
__END__
=pod
=back
=cut