# The LearningOnline Network with CAPA # Search Catalog # # $Id: lonsearchcat.pm,v 1.324 2010/06/06 02:40:30 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 $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; &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',}); $r->print(&Apache::loncommon::start_page('Course Search')); $r->print(&Apache::lonhtmlcommon::breadcrumbs('Searching','Searching', $env{'form.catalogmode'} ne 'import')); $r->print( '
'.&mt('No matches found in resources.').'
'); } # Check discussions if requested if ($discuss) { my $totaldiscussions = 0; $r->print(''.&mt('No matches found in postings.').'
'); } } else { $r->print('$lt{'note'}.
ENDCOURSESEARCH
$scrout.=' '.
&Apache::lonhtmlcommon::textbox('courseexp',
$env{'form.courseexp'},40);
my $crscheckbox =
&Apache::lonhtmlcommon::checkbox('crsfulltext',
$env{'form.crsfulltext'});
my $relcheckbox =
&Apache::lonhtmlcommon::checkbox('crsrelated',
$env{'form.crsrelated'});
my $discheckbox =
&Apache::lonhtmlcommon::checkbox('crsdiscuss',
$env{'form.crsrelated'});
$scrout.=(< | |
$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 .= ''; $scrout .= &Apache::loncommon::end_page(); $r->print($scrout); return; } ###################################################################### ###################################################################### =pod =item &titlefield() Inputs: title text Outputs: titletext with font wrapper =cut ###################################################################### ###################################################################### sub titlefield { my $title=shift; return $title; } ###################################################################### ###################################################################### =pod =item viewoptiontext() Inputs: codename for view option Outputs: displayed text =cut ###################################################################### ###################################################################### sub viewoptiontext { my $code=shift; my %desc=&Apache::lonlocal::texthash ('detailed' => "Detailed Citation View", 'xml' => 'XML/SGML', 'compact' => 'Compact View', 'fielded' => 'Fielded Format', 'summary' => 'Summary View', 'summarypreview' => 'Summary Preview', 'detailedpreview' => 'Detailed Citation Preview'); return $desc{$code}; } ###################################################################### ###################################################################### =pod =item viewoptions() Inputs: none Outputs: text for box with view options =cut ###################################################################### ###################################################################### sub viewoptions { my $scrout; if (! defined($env{'form.viewselect'})) { $env{'form.viewselect'}='detailed'; } $scrout .= '' .&mt('Type:').' ' .&Apache::lonmeta::selectbox('viewselect', $env{'form.viewselect'}, \&viewoptiontext, sort(keys(%Views))) .''; my $countselect = &Apache::lonmeta::selectbox('show', $env{'form.show'}, undef, (10,20,50,100,1000,10000)); $scrout .= ' ' .&mt('Records per Page:').' '.$countselect .''.$/; return $scrout; } ###################################################################### ###################################################################### =pod =item searchhelp() Inputs: none Outputs: return little blurb on how to enter searches =cut ###################################################################### ###################################################################### sub searchhelp { return &mt('Enter words and quoted phrases'); } ###################################################################### ###################################################################### =pod =item &get_persistent_form_data() Inputs: filename of database Outputs: returns undef on database errors. This function is the reverse of &make_persistent() for form data. Retrieve persistent data from %persistent_db. Retrieved items will have their values unescaped. If a form value already exists in $env, it will not be overwritten. Form values that are array references may have values appended to them. =cut ###################################################################### ###################################################################### sub get_persistent_form_data { my $filename = shift; return 0 if (! -e $filename); return undef if (! tie(%persistent_db,'GDBM_File',$filename, &GDBM_READER(),0640)); # # These make sure we do not get array references printed out as 'values'. my %arrays_allowed = ('form.domains'=>1); # # Loop through the keys, looking for 'form.' foreach my $name (keys(%persistent_db)) { next if ($name !~ /^form./); # Kludgification begins! if ($name eq 'form.domains' && $env{'form.searchmode'} eq 'basic' && $env{'form.phase'} ne 'disp_basic') { next; } # End kludge (hopefully) next if (exists($env{$name})); my @values = map { &unescape($_); } split(',',$persistent_db{$name}); next if (@values <1); if ($arrays_allowed{$name}) { $env{$name} = [@values]; } else { $env{$name} = $values[0] if ($values[0]); } } untie (%persistent_db); return 1; } ###################################################################### ###################################################################### =pod =item &get_persistent_data() Inputs: filename of database, ref to array of values to recover. Outputs: array of values. Returns undef on error. This function is the reverse of &make_persistent(); Retrieve persistent data from %persistent_db. Retrieved items will have their values unescaped. If the item contains commas (before unescaping), the returned value will be an array pointer. =cut ###################################################################### ###################################################################### sub get_persistent_data { my $filename = shift; my @Vars = @{shift()}; my @Values; # Return array return undef if (! -e $filename); return undef if (! tie(%persistent_db,'GDBM_File',$filename, &GDBM_READER(),0640)); foreach my $name (@Vars) { if (! exists($persistent_db{$name})) { push @Values, undef; next; } my @values = map { &unescape($_); } split(',',$persistent_db{$name}); if (@values <= 1) { push @Values,$values[0]; } else { push @Values,\@values; } } untie (%persistent_db); return @Values; } ###################################################################### ###################################################################### =pod =item &make_persistent() Inputs: Hash of values to save, filename of persistent database. Store variables away to the %persistent_db. Values will be escaped. Values that are array pointers will have their elements escaped and concatenated in a comma separated string. =cut ###################################################################### ###################################################################### sub make_persistent { my %save = %{shift()}; my $filename = shift; return undef if (! tie(%persistent_db,'GDBM_File', $filename,&GDBM_WRCREAT(),0640)); foreach my $name (keys(%save)) { my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); # We handle array references, but not recursively. my $store = join(',', map { &escape($_); } @values ); $persistent_db{$name} = $store; } untie(%persistent_db); return 1; } ###################################################################### ###################################################################### =pod =item &make_form_data_persistent() Inputs: filename of persistent database. Store most form variables away to the %persistent_db. Values will be escaped. Values that are array pointers will have their elements escaped and concatenated in a comma separated string. =cut ###################################################################### ###################################################################### sub make_form_data_persistent { my $r = shift; my $filename = shift; my %save; foreach (keys(%env)) { next if (!/^form/ || /submit/); $save{$_} = $env{$_}; } return &make_persistent(\%save,$filename); } ###################################################################### ###################################################################### =pod =item &parse_advanced_search() Parse advanced search form and return the following: =over 4 =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 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 (@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') { $env{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\'.\*]//g; } foreach ('mode','form','element') { # is this required? Hmmm. 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 foreach my $field (@BasicFields) { if (&filled($env{'form.'.$field})) { $fillflag++; } } 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 (@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 .= 'table: |'.$table.'|
' # SB .''
.&mt('Unable to retrieve search results. '
.'Unable to determine the table results were saved in.')
.&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("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("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("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 =< '
.&mt('There are [_1] matches to your query.',$total_results)
.' '.$revise.' '.&mt('Search: ').$pretty_query_string
.' '
.''
.' '
.''
.' '
.''
.' '
.&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.')
.' \n";
$result .= ''.$values{'author'}.','.
' '.$values{'owner'}.' '.$values{'extrashow'}.' '.$values{'shortabstract'}.'
$errorstring
'.$closebutton.'
$errormsg
$message
Sort Results
#Sort by: \n";
my $revise = &revise_button();
$result.='
::i;
$pretty_string =~ s:(
)*\s*$::im;
my @Lines = split("
",$pretty_string);
# I keep getting blank items at the end of the list, hence the following:
while ($Lines[-1] =~ /^\s*$/ && @Lines) {
pop(@Lines);
}
if (@Lines > 2) {
$pretty_string = join '
',(@Lines[0..2],'...
');
}
$r->print(&mt("Search: [_1]",$pretty_string));
$r->rflush();
#
# Determine the servers we need to contact.
my @Servers_to_contact;
if (defined($serverlist)) {
if (ref($serverlist) eq 'ARRAY') {
@Servers_to_contact = @$serverlist;
} else {
@Servers_to_contact = ($serverlist);
}
} else {
my %all_library_servers = &Apache::lonnet::unique_library();
@Servers_to_contact = sort(keys(%all_library_servers));
}
my %Server_status;
#
# Check on the mysql table we will use to store results.
my $table =$env{'form.table'};
if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
$r->print("Unable to determine table id to save search results in.".
"The search has been aborted.".
&Apache::loncommon::end_page());
return;
}
my $table_status = &Apache::lonmysql::check_table($table);
if (! defined($table_status)) {
$r->print("Unable to determine status of table.".
&Apache::loncommon::end_page());
&Apache::lonnet::logthis("Bogus table id of $table for ".
"$env{'user.name'} @ $env{'user.domain'}");
&Apache::lonnet::logthis("lonmysql error = ".
&Apache::lonmysql::get_error());
return;
}
if (! $table_status) {
&Apache::lonnet::logthis("lonmysql error = ".
&Apache::lonmysql::get_error());
&Apache::lonnet::logthis("lonmysql debug = ".
&Apache::lonmysql::get_debug());
&Apache::lonnet::logthis('table status = "'.$table_status.'"');
$r->print("The table id,$table, we tried to use is invalid.".
"The search has been aborted.".
&Apache::loncommon::end_page());
return;
}
##
## Prepare for the big loop.
my $hitcountsum;
my %matches;
my $server;
my $status;
my $revise = &revise_button();
$r->print('');
$r->rflush();
&reset_timing();
&update_seconds($r);
&update_status($r,&mt('contacting [_1]',$Servers_to_contact[0]));
while (&time_left() &&
((@Servers_to_contact) || keys(%Server_status))) {
&update_seconds($r);
#
# Send out a search request
if (@Servers_to_contact) {
# Contact one server
my $server = shift(@Servers_to_contact);
&update_status($r,&mt('contacting [_1]',$server));
my $reply=&Apache::lonnet::metadata_query($query,$customquery,
$customshow,[$server]);
($server) = keys(%$reply);
$Server_status{$server} = $reply->{$server};
} else {
# wait a sec. to give time for files to be written
# This sleep statement is here instead of outside the else
# block because we do not want to pause if we have servers
# left to contact.
if (scalar (keys(%Server_status))) {
&update_status($r,
&mt('waiting on [_1]',join(' ',keys(%Server_status))));
}
sleep(1);
}
#
# Loop through the servers we have contacted but do not
# have results from yet, looking for results.
foreach my $server (keys(%Server_status)) {
last if ($connection->aborted());
&update_seconds($r);
my $status = $Server_status{$server};
if ($status eq 'con_lost') {
delete ($Server_status{$server});
next;
}
$status=~s|/||g;
my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$status;
if (-e $datafile && ! -e "$datafile.end") {
&update_status($r,&mt('Receiving results from [_1]',$server));
next;
}
last if ($connection->aborted());
if (-e "$datafile.end") {
&update_status($r,&mt('Reading results from [_1]',$server));
if (-z "$datafile") {
delete($Server_status{$server});
next;
}
my $fh;
if (!($fh=Apache::File->new($datafile))) {
$r->print("Unable to open search results file for ".
"server $server. Omitting from search");
delete($Server_status{$server});
next;
}
# Read in the whole file.
while (my $result = <$fh>) {
last if ($connection->aborted());
#
# Records are stored one per line
chomp($result);
next if (! $result);
#
# Parse the result.
my %Fields = &parse_raw_result($result,$server,$tabletype);
$Fields{'hostname'} = $server;
#
# Skip based on copyright
next if (! ©right_check(\%Fields));
if ($area eq 'portfolio') {
next if (defined($matches{$Fields{'url'}}));
# Skip if inaccessible
next if (!&Apache::lonnet::portfolio_access($Fields{'url'}));
$matches{$Fields{'url'}} = 1;
}
#
# Store the result in the mysql database
my $result = &Apache::lonmysql::store_row($table,\%Fields);
if (! defined($result)) {
$r->print(&Apache::lonmysql::get_error());
}
#
$hitcountsum ++;
&update_seconds($r);
if ($hitcountsum % 50 == 0) {
&update_count_status($r,$hitcountsum);
}
}
$fh->close();
# $server is only deleted if the results file has been
# found and (successfully) opened. This may be a bad idea.
delete($Server_status{$server});
}
last if ($connection->aborted());
&update_count_status($r,$hitcountsum);
}
last if ($connection->aborted());
&update_seconds($r);
}
&update_status($r,&mt('Search Complete on Server [_1]',$server));
&update_seconds($r);
#
&Apache::lonmysql::disconnect_from_db(); # This is unneccessary
#
# We have run out of time or run out of servers to talk to and
# results to get, so let the client know the top frame needs to be
# loaded from /adm/searchcat
$r->print(&Apache::loncommon::end_page());
# if ($env{'form.catalogmode'} ne 'import') {
$r->print(<
SCRIPT
# }
return;
}
######################################################################
######################################################################
=pod
=item &prev_next_buttons()
Returns html for the previous and next buttons on the search results page.
=cut
######################################################################
######################################################################
sub prev_next_buttons {
my ($current_min,$show,$total,$parms) = @_;
return '' if ($show eq 'all'); # No links if you get them all at once.
#
# Create buttons
return ''
.$output
.' '
.&Apache::loncommon::end_data_table_row()
);
$r->rflush();
}
$r->print(&Apache::loncommon::end_data_table());
if (@Results < 1) {
$r->print('
\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=<
';
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,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,1).'
';
} else {
$result.= &mt($field->{'translate'},
$values{$field->{'name'}});
}
$result .= "
\n";
}
}
$result .= "'.
&Apache::lonindexer::showpreview($values{'url'});
}
######################################################################
######################################################################
=pod
=item &summary_view()
=cut
######################################################################
######################################################################
sub summary_view {
my ($prefix,%values) = @_;
my $icon=&Apache::loncommon::icon($values{'url'});
my $result=qq{$prefix};
if (exists($env{'form.sortfield'}) &&
$env{'form.sortfield'} !~ /^(default|
author|
url|
title|
owner|
lastrevisiondate|
copyright)$/x) {
my $tmp = $values{$env{'form.sortfield'}};
if (! defined($tmp)) { $tmp = 'undefined'; }
$result .= ' '.$tmp.' ';
}
my $jumpurl=$values{'url'};
$jumpurl=~s|^/ext/|http://|;
my $link = '
'.&display_url($jumpurl,1).'
';
$result.=<
$link
$values{'author'}, $values{'owner'} -- $values{'lastrevisiondate'}
$values{'copyrighttag'}
$values{'extrashow'}
END
return $result;
}
sub summary_preview {
my ($prefix,%values)=@_;
return &summary_view($prefix,%values).
''.
&Apache::lonindexer::showpreview($values{'url'});
}
######################################################################
######################################################################
=pod
=item &compact_view()
=cut
######################################################################
######################################################################
sub compact_view {
my ($prefix,%values) = @_;
my $jumpurl=$values{'url'};
$jumpurl=~s|^/ext/|http://|;
my $link = &display_url($jumpurl,1);
my $result =
$prefix.'';
if (exists($env{'form.sortfield'}) &&
$env{'form.sortfield'} !~ /^(default|author|url|title)$/) {
my $tmp = $values{$env{'form.sortfield'}};
if (! defined($tmp)) { $tmp = 'undefined'; }
$result .= ' '.$tmp.' ';
}
$jumpurl = &HTML::Entities::encode($jumpurl,'<>&"');
$result.=' '.
''.
&HTML::Entities::encode($values{'title'},'<>&"').' '.
$link.' '.$values{'author'}.' ('.$values{'domain'}.')';
return $result;
}
sub display_url {
my ($url,$skiplast) = @_;
my $link;
if ($url=~m|^/ext/|) {
$url=~s|^/ext/|http://|;
$link=''.$url.'';
} elsif ($url=~m{^(http://|/uploaded/)}) {
$link=''.$url.'';
} else {
$link=&Apache::lonhtmlcommon::crumbs(
$url,
'preview',
'',
(($env{'form.catalogmode'} eq 'import')?'parent.statusframe.document.forms.statusform':''),
$skiplast).' ';
}
return $link;
}
######################################################################
######################################################################
=pod
=item &fielded_format_view()
=cut
######################################################################
######################################################################
sub fielded_format_view {
my ($prefix,%values) = @_;
my $icon=&Apache::loncommon::icon($values{'url'});
my %Translated = &Apache::lonmeta::fieldnames();
my $jumpurl=$values{'url'};
$jumpurl=~s|^/ext/|http://|;
my $result=<
\n";
$result .= $values{'extrashow'};
return $result;
}
######################################################################
######################################################################
=pod
=item &xml_sgml_view()
=back
=cut
######################################################################
######################################################################
sub xml_sgml_view {
my ($prefix,%values) = @_;
my $xml = '
$heading
';
} else {
&Apache::lonhtmlcommon::add_breadcrumb
({href=>'',
text=>$heading,});
$start_page .= &Apache::lonhtmlcommon::breadcrumbs();
}
$r->print(<$heading
$heading