--- loncom/interface/lonsearchcat.pm 2002/06/24 15:26:28 1.127
+++ loncom/interface/lonsearchcat.pm 2016/08/04 20:59:22 1.349
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Search Catalog
#
-# $Id: lonsearchcat.pm,v 1.127 2002/06/24 15:26:28 matthew Exp $
+# $Id: lonsearchcat.pm,v 1.349 2016/08/04 20:59:22 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -25,14 +25,6 @@
#
# http://www.lon-capa.org/
#
-# YEAR=2001
-# 3/8, 3/12, 3/13, 3/14, 3/15, 3/19 Scott Harrison
-# 3/20, 3/21, 3/22, 3/26, 3/27, 4/2, 8/15, 8/24, 8/25 Scott Harrison
-# 10/12,10/14,10/15,10/16,11/28,11/29,12/10,12/12,12/16 Scott Harrison
-# YEAR=2002
-# 1/17 Scott Harrison
-# 6/17 Matthew Hall
-#
###############################################################################
###############################################################################
@@ -40,7 +32,7 @@
=head1 NAME
-lonsearchcat
+lonsearchcat - LONCAPA Search Interface
=head1 SYNOPSIS
@@ -56,410 +48,905 @@ described at http://www.lon-capa.org.
lonsearchcat presents the user with an interface to search the LON-CAPA
digital library. lonsearchcat also initiates the execution of a search
by sending the search parameters to LON-CAPA servers. The progress of
-search (on a server basis) is displayed to the user in a seperate window.
+search (on a server basis) is displayed to the user in a separate window.
=head1 Internals
-=over 4
-
=cut
###############################################################################
###############################################################################
-## ##
-## ORGANIZATION OF THIS PERL MODULE ##
-## ##
-## 1. Modules used by this module ##
-## 2. Choices for different output views (detailed, summary, xml, etc) ##
-## 3. BEGIN block (to be run once after compilation) ##
-## 4. Handling routine called via Apache and mod_perl ##
-## 5. Other subroutines ##
-## ##
-###############################################################################
-
package Apache::lonsearchcat;
-# ------------------------------------------------- modules used by this module
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();
-
-# ---------------------------------------- variables used throughout the module
+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 Apache::lonwishlist();
+use LONCAPA;
+use Time::HiRes qw(sleep);
######################################################################
######################################################################
+##
+## 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);
-=pod
+######################################################################
+######################################################################
+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().
-=item Global variables
+ #
+ 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(0.1);
+ delete($env{'form.pause'});
+ }
+ ##
+ ## Initialize global variables
+ ##
+ my $domain = $r->dir_config('lonDefDomain');
+ my $temp_file_dir = LONCAPA::tempdir();
+ $diropendb= $temp_file_dir .
+ "$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;
+ }
-=over 4
+ my $persistent_db_file = $temp_file_dir .
+ &escape($domain).
+ '_'.&escape($env{'user.name'}).
+ '_'.$env{'form.persistent_db_id'}.'_persistent_search.db';
+ ##
+ &Apache::lonhtmlcommon::clear_breadcrumbs();
-=item $closebutton
+ my @allowed_searches = ('portfolio');
+ if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'}) eq 'F') {
+ push(@allowed_searches,'res');
+ }
+ my $crumb_text = 'Portfolio Search';
+ if (@allowed_searches ==2) {
+ $crumb_text = 'Portfolio and Catalog 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 =
+ &mt('We were unable to retrieve data describing your search.').
+ ' '.&mt('This is a serious error and has been logged.').
+ '
'.
+ &mt('Please alert your LON-CAPA administrator.');
+ &Apache::loncommon::simple_error_page(
+ $r,'Search Error',
+ $msg,
+ {'no_auto_mt_msg' => 1});
+ 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';
+ }
+ #
+ 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',});
+ }
+ ##
+ ## 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,$domainsref) =
+ &get_persistent_data($persistent_db_file,
+ ['query','customquery','customshow',
+ 'libraries','pretty_string','domains']);
+ 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'},$domainsref);
+ }
+ } 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,$domains) =
+ (undef,undef,undef,undef,undef);
+ my $pretty_string;
+ if ($env{'form.phase'} eq 'basic_search') {
+ ($query,$pretty_string,$libraries,$domains) =
+ &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,$domains)
+ = &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,
+ domains => $domains },
+ $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;
+}
-button that closes the search window
+#
+# 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'} = '';
+ }
+ }
+}
-=item $importbutton
+sub hidden_field {
+ my ($name,$value) = @_;
+ if (! defined($value)) {
+ $value = $env{'form.'.$name};
+ }
+ return ''.$/;
+}
-button to take the selecte results and go to group sorting
+######################################################################
-=item %hash
+=pod
-The ubiquitous database hash
+=over 4
+
+=item &print_basic_search_form()
-=item $diropendb
+Prints the form for the basic search. Sorry the name is so cryptic.
-The full path to the (temporary) search database file. This is set and
-used in &handler() and is also used in &output_results().
+=cut
-=back
+######################################################################
+######################################################################
+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('Content Library').$bread_crumb;
+# Search form for resource space
+ if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'}) eq 'F') {
+ $scrout .= &setup_basic_search($r,'res',$hidden_fields,$closebutton);
+ $scrout .= '
+ + +$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 .= $bread_crumb; + + $scrout .= ''; -=over 4 + $scrout .= &Apache::loncommon::end_page(); + $r->print($scrout); + return; +} -=item $hidden +###################################################################### +###################################################################### -holds 'hidden' html forms +=pod -=item $scrout +=item &titlefield() -string that holds portions of the screen output +Inputs: title text -=back +Outputs: titletext with font wrapper =cut ###################################################################### ###################################################################### -sub handler { - my $r = shift; - untie %hash; +sub titlefield { + my $title=shift; + return $title; +} - $r->content_type('text/html'); - $r->send_http_header; - return OK if $r->header_only; +###################################################################### +###################################################################### - my $domain = $r->dir_config('lonDefDomain'); - $diropendb= "/home/httpd/perl/tmp/".&Apache::lonnet::escape($domain). - "\_".&Apache::lonnet::escape($ENV{'user.name'})."_searchcat.db"; +=pod - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, - ['catalogmode','launch','acts','mode','form','element', - 'reqinterface']); - ## - ## Clear out old values from database - ## - if ($ENV{'form.launch'} eq '1') { - if (tie(%hash,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { - &start_fresh_session(); - untie %hash; - } else { - $r->print('Unable to tie hash to db '. - 'file'); - return OK; - } - } - ## - ## Produce some output, so people know it is working - ## - $r->print("\n"); - $r->rflush; - ## - ## Configure dynamic components of interface - ## - my $hidden; # Holds 'hidden' html forms - if ($ENV{'form.catalogmode'} eq 'interactive') { - $hidden="". - "\n"; - $closebutton=""."\n"; - } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { - $hidden=<'.
- ''.$uctitle.':
';
+ return &make_persistent(\%save,$filename);
}
######################################################################
@@ -635,129 +1138,354 @@ sub selectbox {
=pod
-=item &advancedsearch()
+=item &parse_advanced_search()
+
+Parse advanced search form and return the following:
+
+=over 4
-Parse advanced search results.
+=item $query Scalar containing an SQL query.
+
+=item $customquery Scalar containing a custom query.
+
+=item $customshow Scalar containing commands to show custom metadata.
+
+=item $libraries_to_query Reference to array of domains to search.
+
+=back
=cut
######################################################################
######################################################################
-sub advancedsearch {
- my ($r,$envhash,$hidden)=@_;
- my %ENV=%{$envhash};
+sub parse_advanced_search {
+ my ($r,$closebutton,$hidden_fields)=@_;
+ my @BasicFields = ('title','author','subject','keywords','url','version',
+ 'notes','abstract','extension','owner','authorspace',
+# 'custommetadata','customshow',
+ 'modifyinguser','standards','mime');
+ my @StatsFields = &statfields();
+ my @EvalFields = &evalfields();
my $fillflag=0;
+ my $pretty_search_string = "";
# Clean up fields for safety
- for my $field ('title','author','subject','keywords','url','version',
- 'creationdatestart_month','creationdatestart_day',
+ for my $field (@BasicFields,
+ 'creationdatestart_month','creationdatestart_day',
'creationdatestart_year','creationdateend_month',
'creationdateend_day','creationdateend_year',
'lastrevisiondatestart_month','lastrevisiondatestart_day',
'lastrevisiondatestart_year','lastrevisiondateend_month',
- 'lastrevisiondateend_day','lastrevisiondateend_year',
- 'notes','abstract','mime','language','owner',
- 'custommetadata','customshow') {
- $ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
+ 'lastrevisiondateend_day','lastrevisiondateend_year') {
+ $env{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\'.\*]//g;
}
foreach ('mode','form','element') {
# is this required? Hmmm.
- next unless (exists($ENV{"form.$_"}));
- $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
- $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
- }
+ next if (! exists($env{'form.'.$_}));
+ $env{'form.'.$_}=&unescape($env{'form.'.$_});
+ $env{'form.'.$_}=~s/[^\w\/\s\(\)\=\-\"\']//g;
+ }
+ # Preprocess the category form element.
+ $env{'form.category'} = 'any' if (! defined($env{'form.category'}) ||
+ ref($env{'form.category'}));
+ #
# Check to see if enough information was filled in
- for my $field ('title','author','subject','keywords','url','version',
- 'notes','abstract','mime','language','owner',
- 'custommetadata') {
- if (&filled($ENV{"form.$field"})) {
+ foreach my $field (@BasicFields) {
+ if (&filled($env{'form.'.$field})) {
$fillflag++;
}
}
- unless ($fillflag) {
- &output_blank_field_error($r);
- return OK;
+ foreach my $field (@StatsFields,@EvalFields) {
+ if (&filled($env{'form.'.$field.'_max'})) {
+ $fillflag++;
+ }
+ if (&filled($env{'form.'.$field.'_min'})) {
+ $fillflag++;
+ }
+ }
+
+ for my $field ('lowestgradelevel','highestgradelevel') {
+ if ( $env{'form.'.$field} =~ /^\d+$/ &&
+ $env{'form.'.$field} > 0) {
+ $fillflag++;
+ }
+ }
+ if ($env{'form.area'} eq 'portfolio') {
+ # Added metadata fields
+ for (my $i=0; $i<$env{'form.numaddedfields'} ; $i++) {
+ my $field = $env{'form.addedfield_'.$i};
+ $field =~ s/^\s*(\S*)\s*$/$1/;
+ $field =~ s/\W/_/g;
+ if ($field ne '') {
+ $fillflag++;
+ }
+ }
+ }
+ if (! $fillflag) {
+ &output_blank_field_error($r,$closebutton,
+ 'phase=disp_adv',$hidden_fields);
+ return ;
}
# Turn the form input into a SQL-based query
my $query='';
my @queries;
+ my $font = '';
# Evaluate logical expression AND/OR/NOT phrase fields.
- foreach my $field ('title','author','subject','notes','abstract','url',
- 'keywords','version','owner') {
- if ($ENV{'form.'.$field}) {
- push @queries,&build_SQL_query($field,$ENV{'form.'.$field});
- }
+ foreach my $field (@BasicFields) {
+ next if (!defined($env{'form.'.$field}) || $env{'form.'.$field} eq '');
+ my ($error,$SQLQuery) =
+ &process_phrase_input($env{'form.'.$field},
+ $env{'form.'.$field.'_related'},$field);
+ if (defined($error)) {
+ &output_unparsed_phrase_error($r,$closebutton,'phase=disp_adv',
+ $hidden_fields,$field);
+ return;
+ } else {
+ $pretty_search_string .=
+ $font.$field.': '.$env{'form.'.$field};
+ if ($env{'form.'.$field.'_related'}) {
+ my @Words =
+ &Apache::loncommon::get_related_words
+ ($env{'form.'.$field});
+ if (@Words) {
+ $pretty_search_string.= ' with related words: '.
+ join(', ',@Words[0..4]);
+ } else {
+ $pretty_search_string.= ' with related words.';
+ }
+ }
+ $pretty_search_string .= '
';
+ push (@queries,$SQLQuery);
+ }
}
- # Evaluate option lists
- if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') {
- push @queries,"(language like \"$ENV{'form.language'}\")";
+ #
+ # Make the 'mime' from 'form.category' and 'form.extension'
+ #
+ my $searchphrase;
+ if (exists($env{'form.category'}) &&
+ $env{'form.category'} !~ /^\s*$/ &&
+ $env{'form.category'} ne 'any') {
+ my @extensions = &Apache::loncommon::filecategorytypes
+ ($env{'form.category'});
+ if (scalar(@extensions) > 0) {
+ $searchphrase = join(' OR ',@extensions);
+ }
}
- if ($ENV{'form.mime'} and $ENV{'form.mime'} ne 'any') {
- push @queries,"(mime like \"$ENV{'form.mime'}\")";
+ if (defined($searchphrase)) {
+ my ($error,$SQLsearch) = &process_phrase_input($searchphrase,0,'mime');
+ push @queries,$SQLsearch;
+ $pretty_search_string .=$font.'mime contains '.
+ $searchphrase.'
';
}
- if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') {
- push @queries,"(copyright like \"$ENV{'form.copyright'}\")";
+ #
+ # Evaluate option lists
+ if ($env{'form.lowestgradelevel'} &&
+ $env{'form.lowestgradelevel'} ne '0' &&
+ $env{'form.lowestgradelevel'} =~ /^\d+$/) {
+ push(@queries,
+ '(lowestgradelevel>='.$env{'form.lowestgradelevel'}.')');
+ $pretty_search_string.="lowestgradelevel>=".
+ $env{'form.lowestgradelevel'}."
\n";
+ }
+ if ($env{'form.highestgradelevel'} &&
+ $env{'form.highestgradelevel'} ne '0' &&
+ $env{'form.highestgradelevel'} =~ /^\d+$/) {
+ push(@queries,
+ '(highestgradelevel<='.$env{'form.highestgradelevel'}.')');
+ $pretty_search_string.="highestgradelevel<=".
+ $env{'form.highestgradelevel'}."
\n";
+ }
+ if ($env{'form.language'} and $env{'form.language'} ne 'any') {
+ push @queries,"(language like \"$env{'form.language'}\")";
+ $pretty_search_string.=$font."language= ".
+ &Apache::loncommon::languagedescription($env{'form.language'}).
+ "
\n";
+ }
+ if ($env{'form.copyright'} and $env{'form.copyright'} ne 'any') {
+ push @queries,"(copyright like \"$env{'form.copyright'}\")";
+ $pretty_search_string.=$font."copyright = ".
+ &Apache::loncommon::copyrightdescription($env{'form.copyright'}).
+ "
\n";
}
- # Evaluate date windows
- my $datequery=&build_date_queries(
- $ENV{'form.creationdatestart_month'},
- $ENV{'form.creationdatestart_day'},
- $ENV{'form.creationdatestart_year'},
- $ENV{'form.creationdateend_month'},
- $ENV{'form.creationdateend_day'},
- $ENV{'form.creationdateend_year'},
- $ENV{'form.lastrevisiondatestart_month'},
- $ENV{'form.lastrevisiondatestart_day'},
- $ENV{'form.lastrevisiondatestart_year'},
- $ENV{'form.lastrevisiondateend_month'},
- $ENV{'form.lastrevisiondateend_day'},
- $ENV{'form.lastrevisiondateend_year'},
- );
- # Test to see if date windows are legitimate
- if ($datequery=~/^Incorrect/) {
- &output_date_error($r,$datequery);
- return OK;
+ if ($env{'form.area'} eq 'portfolio') {
+ #
+ # Added metadata fields
+ for (my $i=0; $i<$env{'form.numaddedfields'} ; $i++) {
+ my $field = $env{'form.addedfield_'.$i};
+ $field =~ s/^\s*(\S*)\s*$/$1/;
+ $field =~ s/\W/_/g;
+ $field =~ tr/A-Z/a-z/;
+ if ($field ne '') {
+ my $value = $env{'form.addedvalues_'.$i};
+ if ($value ne '') {
+ $value =~ s/'/''/g; #' stupid emacs
+ my ($error,$query) =
+ &process_phrase_input($value,0,'pf.value');
+ if (!defined($error)) {
+ push(@queries,"pf.field = '$field' AND $query");
+ $pretty_search_string .=
+ $font.$field.': '.
+ $env{'form.addedvalues_'.$i}.'
';
+ }
+ } else {
+ push(@queries,"pf.field = '$field' AND pf.value IS NULL");
+ }
+ }
+ }
+ } else {
+ #
+ # Statistics
+ foreach my $field (@StatsFields,@EvalFields) {
+ my ($min,$max);
+ if (exists($env{'form.'.$field.'_min'}) &&
+ $env{'form.'.$field.'_min'} ne '') {
+ $min = $env{'form.'.$field.'_min'};
+ }
+ if (exists($env{'form.'.$field.'_max'}) &&
+ $env{'form.'.$field.'_max'} ne '') {
+ $max = $env{'form.'.$field.'_max'};
+ }
+ next if (! defined($max) && ! defined($min));
+ if (defined($min) && defined($max)) {
+ ($min,$max) = sort {$a <=>$b} ($min,$max);
+ }
+ if (defined($min) && $min =~ /^(\d+\.\d+|\d+|\.\d+)$/) {
+ push(@queries,'('.$field.'>'.$min.')');
+ $pretty_search_string.=$font.$field.'>'.$min.'
';
+ }
+ if (defined($max) && $max =~ /^(\d+\.\d+|\d+|\.\d+)$/) {
+ push(@queries,'('.$field.'<'.$max.')');
+ $pretty_search_string.=$font.$field.'<'.$max.'
';
+ }
+ }
}
- elsif ($datequery) {
+ #
+ # Evaluate date windows
+ my $cafter =
+ &Apache::lonhtmlcommon::get_date_from_form('creationdate1');
+ my $cbefore =
+ &Apache::lonhtmlcommon::get_date_from_form('creationdate2');
+ if ($cafter > $cbefore) {
+ my $tmp = $cafter;
+ $cafter = $cbefore;
+ $cbefore = $tmp;
+ }
+ my $mafter =
+ &Apache::lonhtmlcommon::get_date_from_form('revisiondate1');
+ my $mbefore =
+ &Apache::lonhtmlcommon::get_date_from_form('revisiondate2');
+ if ($mafter > $mbefore) {
+ my $tmp = $mafter;
+ $mafter = $mbefore;
+ $mbefore = $tmp;
+ }
+ my ($datequery,$error,$prettydate)=&build_date_queries($cafter,$cbefore,
+ $mafter,$mbefore);
+ if (defined($error)) {
+ &output_date_error($r,$error,$closebutton,$hidden_fields);
+ } elsif (defined($datequery)) {
+ # Here is where you would set up pretty_search_string to output
+ # date query information.
+ $pretty_search_string .= '
'.$prettydate.'
';
push @queries,$datequery;
}
+ #
# Process form information for custom metadata querying
- my $customquery='';
- if ($ENV{'form.custommetadata'}) {
- $customquery=&build_custommetadata_query('custommetadata',
- $ENV{'form.custommetadata'});
+ my $customquery=undef;
+ ##
+ ## The custom metadata search was removed q long time ago mostly
+ ## because I was unable to figureout exactly how it worked and could
+ ## not imagine people actually using it. MH
+ ##
+ # if ($env{'form.custommetadata'}) {
+ # $pretty_search_string .=$font."Custom Metadata Search: ".
+ # $env{'form.custommetadata'}."
\n";
+ # $customquery=&build_custommetadata_query('custommetadata',
+ # $env{'form.custommetadata'});
+ # }
+ my $customshow=undef;
+ # if ($env{'form.customshow'}) {
+ # $pretty_search_string .=$font."Custom Metadata Display: ".
+ # $env{'form.customshow'}."
\n";
+ # $customshow=$env{'form.customshow'};
+ # $customshow=~s/[^\w\s]//g;
+ # my @fields=split(/\s+/,$customshow);
+ # $customshow=join(" ",@fields);
+ # }
+ ##
+ ## Deal with restrictions to given domains
+ ##
+ my ($libraries_to_query,$pretty_domains_string,$domains_to_query) =
+ &parse_domain_restrictions();
+ if ($pretty_domains_string) {
+ $pretty_search_string .= $pretty_domains_string."
\n";
}
- my $customshow='';
- if ($ENV{'form.customshow'}) {
- $customshow=$ENV{'form.customshow'};
- $customshow=~s/[^\w\s]//g;
- my @fields=split(/\s+/,$customshow);
- $customshow=join(" ",@fields);
- }
- # Send query statements over the network to be processed by either the SQL
- # database or a recursive scheme of 'grep'-like actions (for custom
- # metadata).
+ #
if (@queries) {
- $query=join(" AND ",@queries);
- $query="select * from metadata where $query";
- my $reply; # reply hash reference
- unless ($customquery or $customshow) {
- $reply=&Apache::lonnet::metadata_query($query);
- }
- else {
- $reply=&Apache::lonnet::metadata_query($query,
- $customquery,$customshow);
- }
- &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden);
- return OK;
+ if ($env{'form.area'} eq 'portfolio') {
+ $query ="SELECT pm.*,pa.keynum,pa.scope FROM portfolio_metadata pm, portfolio_access pa, portfolio_addedfields pf WHERE (pm.url = pa.url AND pf.url = pm.url AND (pa.start < UTC_TIMESTAMP() AND (pa.end IS NULL OR pa.end > UTC_TIMESTAMP())) AND (".join(') AND (',@queries).'))';
+ } else {
+ $query="SELECT * FROM metadata WHERE (".join(") AND (",@queries).')';
+ }
} elsif ($customquery) {
- my $reply; # reply hash reference
- $reply=&Apache::lonnet::metadata_query('',
- $customquery,$customshow);
- &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden);
- return OK;
+ $query = '';
+ }
+ #&Apache::lonnet::logthis('advanced query = '.$/.$query);
+ return ($query,$customquery,$customshow,$libraries_to_query,
+ $pretty_search_string,$domains_to_query);
+}
+
+sub parse_domain_restrictions {
+ my $libraries_to_query = undef;
+ my $domains_to_query = undef;
+ # $env{'form.domains'} can be either a scalar or an array reference.
+ # We need an array.
+ if (! exists($env{'form.domains'}) || $env{'form.domains'} eq '') {
+ return (undef,'',undef);
}
- # should not get to this point
- return 'Error. Should not have gone to this point.';
+ my @allowed_domains = &Apache::loncommon::get_env_multiple('form.domains');
+ #
+ my %domain_hash = ();
+ my $pretty_domains_string;
+ foreach (@allowed_domains) {
+ $domain_hash{$_}++;
+ }
+ if ($domain_hash{'any'}) {
+ $pretty_domains_string = &mt("in all LON-CAPA domains.");
+ } else {
+ if (@allowed_domains > 1) {
+ $pretty_domains_string = &mt("in LON-CAPA domains:");
+ } else {
+ $pretty_domains_string = &mt("in LON-CAPA domain ");
+ }
+ foreach (sort @allowed_domains) {
+ $pretty_domains_string .= "".$_." ";
+ }
+ my %library_servers = &Apache::lonnet::get_unique_servers(\@allowed_domains,
+ 'library');
+ my (%older_library_servers,%okdoms,%domains_for_id);
+ map { $okdoms{$_} = 1; } @allowed_domains;
+ foreach my $key (keys(%library_servers)) {
+ if (&Apache::lonnet::get_server_loncaparev('',$key) =~ /^\'?(\d+)\.(\d+)/) {
+ my $major = $1;
+ my $minor = $2;
+ if (($major < 2) || (($major == 2) && ($minor < 11))) {
+ map { $older_library_servers{$_} = 1; }
+ &Apache::lonnet::machine_ids($library_servers{$key});
+ } else {
+ my %possdoms;
+ map { $possdoms{$_}=1 if ($okdoms{$_}); }
+ &Apache::lonnet::machine_domains($library_servers{$key});
+ $domains_for_id{$key} = join(',',sort(keys(%possdoms)));
+ }
+ }
+ }
+ my %servers = (%library_servers,%older_library_servers);
+ $libraries_to_query = [keys(%servers)];
+ $domains_to_query = \%domains_for_id;
+ }
+ return ($libraries_to_query,
+ $pretty_domains_string,
+ $domains_to_query);
}
######################################################################
@@ -765,80 +1493,285 @@ sub advancedsearch {
=pod
-=item &basicsearch()
+=item &parse_basic_search()
-Parse basic search form.
+Parse the basic search form and return a scalar containing an sql query.
=cut
######################################################################
######################################################################
-sub basicsearch {
- my ($r,$envhash,$hidden)=@_;
- my %ENV=%{$envhash};
+sub parse_basic_search {
+ my ($r,$closebutton)=@_;
+ #
# Clean up fields for safety
for my $field ('basicexp') {
- $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g;
+ $env{"form.$field"}=~s/[^\w\s\'\"\!\(\)\-\*]//g;
}
foreach ('mode','form','element') {
# is this required? Hmmm.
- next unless (exists($ENV{"form.$_"}));
- $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
- $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
+ next unless (exists($env{"form.$_"}));
+ $env{"form.$_"}=&unescape($env{"form.$_"});
+ $env{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
}
-
- # Check to see if enough is filled in
- unless (&filled($ENV{'form.basicexp'})) {
- &output_blank_field_error($r);
+ my ($libraries_to_query,$pretty_domains_string,$domains_to_query) =
+ &parse_domain_restrictions();
+ #
+ # Check to see if enough of a query is filled in
+ my $search_string = $env{'form.basicexp'};
+ if (! &filled($search_string)) {
+ &output_blank_field_error($r,$closebutton,'phase=disp_basic');
return OK;
}
+ my $pretty_search_string=$search_string;
+ my @Queries;
+ my @fields = ('title','author','subject','notes','abstract','keywords');
+ my $searchfield;
+ if ($env{'form.area'} eq 'portfolio') {
+ $searchfield = 'concat_ws(" ",pm.'.join(',pm.',@fields).')';
+ } else {
+ $searchfield = 'concat_ws(" ",'.join(',',@fields).')';
+ }
+ my ($error,$SQLQuery) = &process_phrase_input($search_string,
+ $env{'form.related'},
+ $searchfield);
+ if ($error) {
+ &output_unparsed_phrase_error($r,$closebutton,'phase=disp_basic',
+ '','basicexp');
+ return;
+ }
+ push(@Queries,$SQLQuery);
+ #foreach my $q (@Queries) {
+ # &Apache::lonnet::logthis(' '.$q);
+ #}
+ my $final_query;
+ if ($env{'form.area'} eq 'portfolio') {
+ $final_query = 'SELECT pm.*,pa.keynum,pa.scope FROM portfolio_metadata pm, portfolio_access pa WHERE (pm.url = pa.url AND (pa.start < UTC_TIMESTAMP() AND (pa.end IS NULL OR pa.end > UTC_TIMESTAMP())) AND '.join(" AND ",@Queries).')';
+ } else {
+ $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries);
+ }
+ #
+ if ($env{'form.related'}) {
+ $pretty_search_string.=' '.&mt('(including related words)');
+ }
+ if (defined($pretty_domains_string) && $pretty_domains_string ne '') {
+ $pretty_search_string .= ' '.$pretty_domains_string;
+ }
+ $pretty_search_string .= "
\n";
+ $pretty_search_string =~ s:^
and ::;
+ &Apache::lonnet::logthis('simple search final query = '.$/.$final_query);
+ return ($final_query,$pretty_search_string,
+ $libraries_to_query,$domains_to_query);
+}
- # Build SQL query string based on form page
- my $query='';
- my $concatarg=join('," ",',
- ('title', 'author', 'subject', 'notes', 'abstract',
- 'keywords'));
- $concatarg='title' if $ENV{'form.titleonly'};
-
- $query=&build_SQL_query('concat('.$concatarg.')',$ENV{'form.'.'basicexp'});
-
- # Get reply (either a hash reference to filehandles or bad connection)
-# &Apache::lonnet::logthis("metadata query started:".time);
- my $reply=&Apache::lonnet::metadata_query('select * from metadata where '.$query);
-# &Apache::lonnet::logthis("metadata query finished:".time);
- # Output search results
- &output_results('Basic',$r,$envhash,$query,$reply,$hidden);
+###############################################################
+###############################################################
- return OK;
+my @Phrases;
+
+sub concat {
+ my ($item) = @_;
+ my $results = '';
+ foreach (@$item) {
+ if (ref($_) eq 'ARRAY') {
+ $results .= join(' ',@$_);
+ }
+ }
+ return $results;
}
+sub process_phrase_input {
+ my ($phrase,$related,$field)=@_;
+ #&Apache::lonnet::logthis('phrase = :'.$phrase.':');
+ my $grammar = <<'ENDGRAMMAR';
+ searchphrase:
+ expression /^\Z/ {
+ # &Apache::lonsearchcat::print_item(\@item,0);
+ [@item];
+ }
+ expression:
+ phrase(s) {
+ [@item];
+ }
+ phrase:
+ orword {
+ [@item];
+ }
+ | andword {
+ [@item];
+ }
+ | minusword {
+ unshift(@::Phrases,$item[1]->[0]);
+ unshift(@::Phrases,$item[1]->[1]);
+ [@item];
+ }
+ | word {
+ unshift(@::Phrases,$item[1]);
+ [@item];
+ }
+ #
+ orword:
+ word 'OR' phrase {
+ unshift(@::Phrases,'OR');
+ unshift(@::Phrases,$item[1]);
+ [@item];
+ }
+ | word 'or' phrase {
+ unshift(@::Phrases,'OR');
+ unshift(@::Phrases,$item[1]);
+ [@item];
+ }
+ | minusword 'OR' phrase {
+ unshift(@::Phrases,'OR');
+ unshift(@::Phrases,$item[1]->[0]);
+ unshift(@::Phrases,$item[1]->[1]);
+ [@item];
+ }
+ | minusword 'or' phrase {
+ unshift(@::Phrases,'OR');
+ unshift(@::Phrases,$item[1]->[0]);
+ unshift(@::Phrases,$item[1]->[1]);
+ [@item];
+ }
+ andword:
+ word phrase {
+ unshift(@::Phrases,'AND');
+ unshift(@::Phrases,$item[1]);
+ [@item];
+ }
+ | minusword phrase {
+ unshift(@::Phrases,'AND');
+ unshift(@::Phrases,$item[1]->[0]);
+ unshift(@::Phrases,$item[1]->[1]);
+ [@item];
+ }
+ #
+ minusword:
+ '-' word {
+ [$item[2],'NOT'];
+ }
+ word:
+ "'" term(s) "'" {
+ &Apache::lonsearchcat::concat(\@item);
+ }
+ | '"' term(s) '"' {
+ &Apache::lonsearchcat::concat(\@item);
+ }
+ | term {
+ $item[1];
+ }
+ term:
+ /[\w\Q:!@#$%^&*()+_=|{}<>,.;\\\/?\E\-]+/ {
+ $item[1];
+ }
+ENDGRAMMAR
+ #
+ # The end result of parsing the phrase with the grammar is an array
+ # @::Phrases.
+ # $phrase = "gene splicing" or cat -> "gene splicing","OR","cat"
+ # $phrase = "genetic engineering" -dna ->
+ # "genetic engineering","AND","NOT","dna"
+ # $phrase = cat or dog -poodle -> "cat","OR","dog","AND","NOT","poodle"
+ undef(@::Phrases);
+ my $p = new Parse::RecDescent($grammar);
+ if (! defined($p->searchphrase($phrase))) {
+ &Apache::lonnet::logthis('lonsearchcat:unable to process:'.$phrase);
+ return 'Unable to process phrase '.$phrase;
+ }
+ #
+ # Go through the phrases and make sense of them.
+ # Apply modifiers NOT OR and AND to the phrases.
+ my @NewPhrases;
+ while(@::Phrases) {
+ my $phrase = shift(@::Phrases);
+ # &Apache::lonnet::logthis('phrase = '.$phrase);
+ my $phrasedata;
+ if ($phrase =~ /^(NOT|OR|AND)$/) {
+ if ($phrase eq 'OR') {
+ $phrasedata->{'or'}++;
+ if (! @::Phrases) { $phrasedata = undef; last; }
+ $phrase = shift(@::Phrases);
+ } elsif ($phrase eq 'AND') {
+ $phrasedata->{'and'}++;
+ if (! @::Phrases) { $phrasedata = undef; last; }
+ $phrase = shift(@::Phrases);
+ }
+ if ($phrase eq 'NOT') {
+ $phrasedata->{'negate'}++;
+ if (! @::Phrases) { $phrasedata = undef; last; }
+ $phrase = shift(@::Phrases);
+ }
+ }
+ $phrasedata->{'phrase'} = $phrase;
+ if ($related) {
+ my @NewWords;
+ (undef,@NewWords) = &related_version($phrasedata->{'phrase'});
+ $phrasedata->{'related_words'} = \@NewWords;
+ }
+ push(@NewPhrases,$phrasedata);
+ }
+ #
+ # Actually build the sql query from the phrases
+ my $SQLQuery;
+ foreach my $phrase (@NewPhrases) {
+ my $query;
+ if ($phrase->{'negate'}) {
+ $query .= $field.' NOT LIKE "%'.$phrase->{'phrase'}.'%"';
+ } else {
+ $query .= $field.' LIKE "%'.$phrase->{'phrase'}.'%"';
+ }
+ foreach my $related (@{$phrase->{'related_words'}}) {
+ if ($phrase->{'negate'}) {
+ $query .= ' AND '.$field.' NOT LIKE "%'.$related.'%"';
+ } else {
+ $query .= ' OR '.$field.' LIKE "%'.$related.'%"';
+ }
+ }
+ if ($SQLQuery) {
+ if ($phrase->{'or'}) {
+ $SQLQuery .= ' OR ('.$query.')';
+ } else {
+ $SQLQuery .= ' AND ('.$query.')';
+ }
+ } else {
+ $SQLQuery = '('.$query.')';
+ }
+ }
+ #
+ # &Apache::lonnet::logthis("SQLQuery = $SQLQuery");
+ #
+ return undef,$SQLQuery;
+}
######################################################################
######################################################################
=pod
-=item &build_SQL_query()
+=item &related_version()
+
+Modifies an input string to include related words. Words in the string
+are replaced with parenthesized lists of 'OR'd words. For example
+"torque" is replaced with "(torque OR word1 OR word2 OR ...)".
-Builds a SQL query string from a logical expression with AND/OR keywords
-using Text::Query and &recursive_SQL_query_builder()
+Note: Using this twice on a string is probably silly.
=cut
######################################################################
######################################################################
-sub build_SQL_query {
- my ($field_name,$logic_statement)=@_;
- my $q=new Text::Query('abc',
- -parse => 'Text::Query::ParseAdvanced',
- -build => 'Text::Query::Build');
- $q->prepare($logic_statement);
- my $matchexp=${$q}{'matchexp'}; chomp $matchexp;
- my $sql_query=&recursive_SQL_query_build($field_name,$matchexp);
- return $sql_query;
+sub related_version {
+ my ($word) = @_;
+ return (undef) if (lc($word) =~ /\b(or|and|not)\b/);
+ my @Words = &Apache::loncommon::get_related_words($word);
+ # Only use 4 related words
+ @Words = ($#Words>4? @Words[0..4] : @Words);
+ my $result = join " OR ", ($word,@Words);
+ return $result,sort(@Words);
}
+
######################################################################
######################################################################
@@ -872,349 +1805,1139 @@ sub build_custommetadata_query {
return $matchexp;
}
+
######################################################################
######################################################################
=pod
-=item &recursive_SQL_query_build()
+=item &build_date_queries()
-Recursively constructs an SQL query. Takes as input $dkey and $pattern.
+Builds a SQL logic query to check time/date entries.
+Also reports errors (check for /^Incorrect/).
=cut
######################################################################
######################################################################
-sub recursive_SQL_query_build {
- my ($dkey,$pattern)=@_;
- my @matches=($pattern=~/(\[[^\]|\[]*\])/g);
- return $pattern unless @matches;
- foreach my $match (@matches) {
- $match=~/\[ (\w+)\s(.*) \]/;
- my ($key,$value)=($1,$2);
- my $replacement='';
- if ($key eq 'literal') {
- $replacement="($dkey like \"\%$value\%\")";
- }
- elsif ($key eq 'not') {
- $value=~s/like/not like/;
-# $replacement="($dkey not like $value)";
- $replacement="$value";
- }
- elsif ($key eq 'and') {
- $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
- $replacement="($1 AND $2)";
- }
- elsif ($key eq 'or') {
- $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
- $replacement="($1 OR $2)";
- }
- substr($pattern,
- index($pattern,$match),
- length($match),
- $replacement
- );
+sub build_date_queries {
+ my ($cafter,$cbefore,$mafter,$mbefore) = @_;
+ my ($result,$error,$pretty_string);
+ #
+ # Verify the input
+ if (! defined($cafter) && ! defined($cbefore) &&
+ ! defined($mafter) && ! defined($mbefore)) {
+ # This is an okay situation, so return undef for the error
+ return (undef,undef,undef);
+ }
+ if ((defined($cafter) && ! defined($cbefore)) ||
+ (defined($cbefore) && ! defined($cafter))) {
+ # This is bad, so let them know
+ $error = &mt('Incorrect entry for the creation date. '.
+ 'You must specify both the beginning and ending dates.');
+ }
+ if (! defined($error) &&
+ ((defined($mafter) && ! defined($mbefore)) ||
+ (defined($mbefore) && ! defined($mafter)))) {
+ # This is also bad, so let them know
+ $error = &mt('Incorrect entry for the last revision date. '.
+ 'You must specify both the beginning and ending dates.');
}
- &recursive_SQL_query_build($dkey,$pattern);
+ if (! defined($error)) {
+ #
+ # Build the queries
+ my @queries;
+ if (defined($cbefore) && defined($cafter)) {
+ my (undef,undef,undef,$caday,$camon,$cayear) = localtime($cafter);
+ my (undef,undef,undef,$cbday,$cbmon,$cbyear) = localtime($cbefore);
+ # Correct for year being relative to 1900
+ $cayear+=1900; $cbyear+=1900;
+ my $cquery=
+ '(creationdate BETWEEN '.
+ "'".$cayear.'-'.$camon.'-'.$caday."'".
+ ' AND '.
+ "'".$cbyear.'-'.$cbmon.'-'.$cbday." 23:59:59')";
+ $pretty_string .= '
' if (defined($pretty_string));
+ $pretty_string .=
+ &mt('created between [_1] and [_2]',
+ &Apache::lonlocal::locallocaltime($cafter),
+ &Apache::lonlocal::locallocaltime($cbefore+24*60*60-1));
+ push(@queries,$cquery);
+ $pretty_string =~ s/ 00:00:00//g;
+ }
+ if (defined($mbefore) && defined($mafter)) {
+ my (undef,undef,undef,$maday,$mamon,$mayear) = localtime($mafter);
+ my (undef,undef,undef,$mbday,$mbmon,$mbyear) = localtime($mbefore);
+ # Correct for year being relative to 1900
+ $mayear+=1900; $mbyear+=1900;
+ my $mquery=
+ '(lastrevisiondate BETWEEN '.
+ "'".$mayear.'-'.$mamon.'-'.$maday."'".
+ ' AND '.
+ "'".$mbyear.'-'.$mbmon.'-'.$mbday." 23:59:59')";
+ push(@queries,$mquery);
+ $pretty_string .= '
' if (defined($pretty_string));
+ $pretty_string .=
+ &mt('last revised between [_1] and [_2]',
+ &Apache::lonlocal::locallocaltime($mafter),
+ &Apache::lonlocal::locallocaltime($mbefore+24*60*60-1));
+ $pretty_string =~ s/ 00:00:00//g;
+ }
+ if (@queries) {
+ $result .= join(" AND ",@queries);
+ }
+ }
+ return ($result,$error,$pretty_string);
}
######################################################################
######################################################################
-=pod
+=pod
-=item &build_date_queries()
+=item ©right_check()
-Builds a SQL logic query to check time/date entries.
-Also reports errors (check for /^Incorrect/).
+Inputs: $Metadata, a hash pointer of metadata for a resource.
+
+Returns: 1 if the resource is available to the user making the query,
+ 0 otherwise.
=cut
######################################################################
######################################################################
-sub build_date_queries {
- my ($cmonth1,$cday1,$cyear1,$cmonth2,$cday2,$cyear2,
- $lmonth1,$lday1,$lyear1,$lmonth2,$lday2,$lyear2)=@_;
- my @queries;
- if ($cmonth1 or $cday1 or $cyear1 or $cmonth2 or $cday2 or $cyear2) {
- unless ($cmonth1 and $cday1 and $cyear1 and
- $cmonth2 and $cday2 and $cyear2) {
- return "Incorrect entry for the creation date. You must specify ".
- "a starting month, day, and year and an ending month, ".
- "day, and year.";
- }
- my $cnumeric1=sprintf("%d%2d%2d",$cyear1,$cmonth1,$cday1);
- $cnumeric1+=0;
- my $cnumeric2=sprintf("%d%2d%2d",$cyear2,$cmonth2,$cday2);
- $cnumeric2+=0;
- if ($cnumeric1>$cnumeric2) {
- return "Incorrect entry for the creation date. The starting ".
- "date must occur before the ending date.";
- }
- my $cquery="(creationdate BETWEEN '$cyear1-$cmonth1-$cday1' AND '".
- "$cyear2-$cmonth2-$cday2 23:59:59')";
- push @queries,$cquery;
- }
- if ($lmonth1 or $lday1 or $lyear1 or $lmonth2 or $lday2 or $lyear2) {
- unless ($lmonth1 and $lday1 and $lyear1 and
- $lmonth2 and $lday2 and $lyear2) {
- return "Incorrect entry for the last revision date. You must ".
- "specify a starting month, day, and year and an ending ".
- "month, day, and year.";
- }
- my $lnumeric1=sprintf("%d%2d%2d",$lyear1,$lmonth1,$lday1);
- $lnumeric1+=0;
- my $lnumeric2=sprintf("%d%2d%2d",$lyear2,$lmonth2,$lday2);
- $lnumeric2+=0;
- if ($lnumeric1>$lnumeric2) {
- return "Incorrect entry for the last revision date. The ".
- "starting date must occur before the ending date.";
- }
- my $lquery="(lastrevisiondate BETWEEN '$lyear1-$lmonth1-$lday1' AND '".
- "$lyear2-$lmonth2-$lday2 23:59:59')";
- push @queries,$lquery;
+sub copyright_check {
+ my $Metadata = shift;
+ # Check copyright tags and skip results the user cannot use
+ my (undef,undef,$resdom,$resname) = split('/',
+ $Metadata->{'url'});
+ # Check for priv
+ if ($Metadata->{'copyright'} eq 'priv') {
+ unless (($env{'user.name'} eq $resname) &&
+ ($env{'user.domain'} eq $resdom)) {
+ return 0;
+ }
}
- if (@queries) {
- return join(" AND ",@queries);
+ # Check for domain
+ if (($Metadata->{'copyright'} eq 'domain') &&
+ ($env{'user.domain'} ne $resdom)) {
+ return 0;
+ }
+ # Check for custom rights
+ if ($Metadata->{'copyright'} eq 'custom') {
+ return &Apache::lonnet::customaccess('bre',$Metadata->{'url'});
}
- return '';
+ return 1;
}
######################################################################
######################################################################
-=pod
+=pod
-=item &output_results()
+=item &ensure_db_and_table()
-Format and output results based on a reply list.
-There are two windows that this function writes to. The main search
-window ("srch") has a listing of the results. A secondary window ("popwin")
-gives the status of the network search (time elapsed, number of machines
-contacted, etc.)
+Ensure we can get lonmysql to connect to the database and the table we
+need exists.
+
+Inputs: $r, table id
+
+Returns: undef on error, 1 if the table exists.
=cut
######################################################################
######################################################################
-sub output_results {
-# &Apache::lonnet::logthis("output_results:".time);
- my $fnum; # search result counter
- my ($mode,$r,$envhash,$query,$replyref,$hidden)=@_;
- my %ENV=%{$envhash};
- my %rhash=%{$replyref};
- my $compiledresult='';
- my $timeremain=300; # (seconds)
- my $elapsetime=0;
- my $resultflag=0;
- my $tflag=1;
- #
- # make query information persistent to allow for subsequent revision
- my $persistent=&make_persistent();
- # spit out the generic header
- $r->print(&search_results_header());
+sub ensure_db_and_table {
+ my ($r,$table) = @_;
+ ##
+ ## Sanity check the table id.
+ ##
+ if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
+ $r->print(&Apache::loncommon::start_page('Error')
+ .'
' + .&mt('Unable to retrieve search results. ' + .'Unable to determine the table results were saved in.') + .'
' + . ''.&mt('Internal info:').'
' + .''.$table.'' + .&Apache::loncommon::end_page() + ); + return undef; + } + ## + ## Make sure we can connect and the table exists. + ## + my $connection_result = &Apache::lonmysql::connect_to_db(); + if (!defined($connection_result)) { + $r->print( + '
' + .&mt('Unable to connect to the MySQL database where your results are saved.') + .'
' + .&Apache::loncommon::end_page() + ); + &Apache::lonnet::logthis("lonsearchcat: unable to get lonmysql to". + " connect to database."); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + return undef; + } + my $table_check = &Apache::lonmysql::check_table($table); + if (! defined($table_check)) { + $r->print( + '' + .&mt('A MySQL error has occurred.') + .'
' + .&Apache::loncommon::end_page()); + &Apache::lonnet::logthis("lonmysql was unable to determine the status". + " of table ".$table); + return undef; + } elsif (! $table_check) { + $r->print( + '' + .&mt('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 $start_page = &Apache::loncommon::start_page('Results',undef); + my $breadcrumbs= + &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching', + $env{'form.catalogmode'} ne 'import'); + + my $result = <' + .&mt('Total of [quant,_1,match,matches] to your query.',$total_results) + .' '.$revise.'
' + .''.&mt('Search: ').$pretty_query_string + .'
'; + $r->print($result.&Apache::loncommon::end_page()); + return; +} + +##################################################################### +##################################################################### + +=pod + +=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: search area - either res or portfolio + +Returns: the identifier of the table on success, undef on error. + +=cut + +###################################################################### +###################################################################### +sub set_up_table_structure { + my ($tabletype) = @_; + my ($datatypes,$fullindicies) = + &LONCAPA::lonmetadata::describe_metadata_storage($tabletype); + # 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 { + my ($area) = @_; + if ($area eq 'portfolio') { + &set_up_table_structure('portfolio_search'); + } else { + &set_up_table_structure('metadata'); + } + 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(< +SCRIPT + $r->rflush(); - # begin showing the cataloged results - $r->print(<Basic search: '.$ENV{'form.basicexp'}.'
'); - } elsif ($mode eq 'Advanced') { - $r->print('Advanced search '.$query.'
'); + my $start_page = &Apache::loncommon::start_page('Search Status',undef); + my $breadcrumbs = + &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching', + $env{'form.catalogmode'} ne 'import'); + $r->print(<\n";
- if ($ENV{'form.catalogmode'} eq 'interactive') {
- my $titleesc=$Fields{'title'};
- $titleesc=~s/\'/\\'/; # '
- $compiledresult.=< '
+ .''
+ .' '
+ .''
+ .' '
+ .''
+ .' '
+ .&mt('Internal Error - Bad view selected.')
+ .' '.
+ &mt('Unable to save import results.').
+ ' '.
+ &mt('A MySQL error has occurred.').
+ ' '.&mt('There are currently no results.').' '
+ .&mt('There were no results matching your query.')
+ .''
+ .$output
+ .' '
+ .&Apache::loncommon::end_data_table_row()
+ );
+ $r->rflush();
+ }
+ $r->print(&Apache::loncommon::end_data_table());
+ if (@Results < 1) {
+ $r->print('
END
- }
- if ($ENV{'form.catalogmode'} eq 'groupsearch') {
- $fnum+=0;
- $hash{"pre_${fnum}_link"}=$Fields{'url'};
- $hash{"pre_${fnum}_title"}=$Fields{'title'};
- $compiledresult.=<
END
-#
-#
- $fnum++;
- }
- my $viewselect;
- if ($mode eq 'Basic') {
- $viewselect=$ENV{'form.basicviewselect'};
- }
- elsif ($mode eq 'Advanced') {
- $viewselect=$ENV{'form.advancedviewselect'};
- }
- if ($viewselect eq 'Detailed Citation View') {
- $compiledresult.=&detailed_citation_view
- (%Fields, hostname => $rkey );
- }
- elsif ($viewselect eq 'Summary View') {
- $compiledresult.=&summary_view
- (%Fields, hostname => $rkey );
- }
- elsif ($viewselect eq 'Fielded Format') {
- $compiledresult.=&fielded_format_view
- (%Fields, hostname => $rkey );
- }
- elsif ($viewselect eq 'XML/SGML') {
- $compiledresult.=&xml_sgml_view
- (%Fields, hostname => $rkey );
- }
- }
- untie %hash;
- }
- if ($compiledresult) {
- $resultflag=1;
- $r->print($compiledresult);
- }
- my $percent=sprintf('%3.0f',($servercount/$servernum*100));
- } # End of foreach loop over servers remaining
- } # End of big loop - while($serversleft && $timeremain)
- unless ($resultflag) {
- $r->print("\nThere were no results that matched your query\n");
}
-# $r->print(''."\n"); $r->rflush();
- $r->print("\n