--- loncom/interface/lonsearchcat.pm 2001/03/12 17:28:32 1.7 +++ loncom/interface/lonsearchcat.pm 2006/03/15 20:56:16 1.256 @@ -1,810 +1,3733 @@ -# The LearningOnline Network +# The LearningOnline Network with CAPA # Search Catalog # -# 03/08/2001 Scott Harrison +# $Id: lonsearchcat.pm,v 1.256 2006/03/15 20:56:16 albertel Exp $ # +# 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); -use Apache::lonnet(); +use Apache::Constants qw(:common :http); +use Apache::lonnet; use Apache::File(); use CGI qw(:standard); +use Text::Query; +use GDBM_File; +use Apache::loncommon(); +use Apache::lonmysql(); +use Apache::lonmeta; +use Apache::lonhtmlcommon; +use Apache::lonlocal; +use LONCAPA::lonmetadata(); +use HTML::Entities(); +use Parse::RecDescent; +use Apache::lonnavmaps; +use Apache::lonindexer(); + +###################################################################### +###################################################################### +## +## Global variables +## +###################################################################### +###################################################################### +my %groupsearch_db; # Database hash used to save values for the + # groupsearch RAT interface. +my %persistent_db; # gdbm hash which holds data which is supposed to + # persist across calls to lonsearchcat.pm + +# The different view modes and associated functions + +my %Views = ("detailed" => \&detailed_citation_view, + "detailedpreview" => \&detailed_citation_preview, + "summary" => \&summary_view, + "summarypreview" => \&summary_preview, + "fielded" => \&fielded_format_view, + "xml" => \&xml_sgml_view, + "compact" => \&compact_view); -my %language; -my $scrout; -my %metadatafields; -my %cprtag; -my %mimetag; - +###################################################################### +###################################################################### sub handler { my $r = shift; - -# -------------------------------------- see if called from an interactive mode - map { - my ($name, $value) = split(/=/,$_); - $value =~ tr/+/ /; - $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - if ($name eq 'catalogmode') { - $ENV{'form.'.$name}=$value; - } - } (split(/&/,$ENV{'QUERY_STRING'})); - - $r->content_type('text/html'); +# &set_defaults(); + # + # set form defaults + # + my $hidden_fields;# Hold all the hidden fields used to keep track + # of the search system state + my $importbutton; # button to take the selected results and go to group + # sorting + my $diropendb; # The full path to the (temporary) search database file. + # This is set and used in &handler() and is also used in + # &output_results(). + my $bodytag; # LON-CAPA standard body tag, gotten from + # &Apache::lonnet::bodytag. + # No title, no table, just a
tag. + + my $loaderror=&Apache::lonnet::overloaderror($r); + if ($loaderror) { return $loaderror; } + # + my $closebutton; # button that closes the search window + # This button is different for the RAT compared to + # normal invocation. + # + &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK if $r->header_only; + ## + ## Prevent caching of the search interface window. Hopefully this means + ## we will get the launch=1 passed in a little more. + &Apache::loncommon::no_cache($r); + ## + ## Pick up form fields passed in the links. + ## + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['catalogmode','launch','acts','mode','form','element','pause', + 'phase','persistent_db_id','table','start','show', + 'cleargroupsort','titleelement']); + ## + ## The following is a trick - we wait a few seconds if asked to so + ## the daemon running the search can get ahead of the daemon + ## printing the results. We only need (theoretically) to do + ## this once, so the pause indicator is deleted + ## + if (exists($env{'form.pause'})) { + sleep(1); + delete($env{'form.pause'}); + } + ## + ## Initialize global variables + ## + my $domain = $r->dir_config('lonDefDomain'); + $diropendb= "/home/httpd/perl/tmp/". + "$env{'user.domain'}_$env{'user.name'}_searchcat.db"; + # + # set the name of the persistent database + # $env{'form.persistent_db_id'} can only have digits in it. + if (! exists($env{'form.persistent_db_id'}) || + ($env{'form.persistent_db_id'} =~ /\D/) || + ($env{'form.launch'} eq '1')) { + $env{'form.persistent_db_id'} = time; + } + $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + my $persistent_db_file = "/home/httpd/perl/tmp/". + &Apache::lonnet::escape($domain). + '_'.&Apache::lonnet::escape($env{'user.name'}). + '_'.$env{'form.persistent_db_id'}.'_persistent_search.db'; + ## + &Apache::lonhtmlcommon::clear_breadcrumbs(); + if (exists($env{'request.course.id'}) && $env{'request.course.id'} ne '') { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?'. + 'catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"Course and Catalog Search", + target=>'_top', + bug=>'Searching',}); + } else { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?'. + 'catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"Catalog Search", + target=>'_top', + bug=>'Searching',}); + } + # + if ($env{'form.phase'} !~ m/(basic|adv|course)_search/) { + if (! &get_persistent_form_data($persistent_db_file)) { + if ($env{'form.phase'} =~ /(run_search|results)/) { + &Apache::lonnet::logthis('lonsearchcat:'. + 'Unable to recover data from '. + $persistent_db_file); + my $msg = + 'We were unable to retrieve data describing your search. '. + 'This is a serious error and has been logged. '. + 'Please alert your LON-CAPA administrator.'; + return &error_page($r,$msg); + } + } + } else { + &clean_up_environment(); + } + ## + ## Clear out old values from groupsearch database + ## + untie %groupsearch_db if (tied(%groupsearch_db)); + if (($env{'form.cleargroupsort'} eq '1') || + (($env{'form.launch'} eq '1') && + ($env{'form.catalogmode'} eq 'groupsearch'))) { + if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) { + &start_fresh_session(); + untie %groupsearch_db; + delete($env{'form.cleargroupsort'}); + } else { + # This is a stupid error to give to the user. + # It really tells them nothing. + my $msg = 'Unable to tie hash to db file.'; + return &error_page($r,$msg); + } + } + ## + ## Configure hidden fields + ## + $hidden_fields = ''."\n"; + if (exists($env{'form.catalogmode'})) { + $hidden_fields .= &hidden_field('catalogmode'); + } + if (exists($env{'form.form'})) { + $hidden_fields .= &hidden_field('form'); + } + if (exists($env{'form.element'})) { + $hidden_fields .= &hidden_field('element'); + } + if (exists($env{'form.titleelement'})) { + $hidden_fields .= &hidden_field('titleelement'); + } + if (exists($env{'form.mode'})) { + $hidden_fields .= &hidden_field('mode'); + } + ## + ## Configure dynamic components of interface + ## + if ($env{'form.catalogmode'} eq 'interactive') { + $closebutton=" +END + } else { + $closebutton = ''; + $importbutton = ''; + } + ## + ## Sanity checks on form elements + ## + if (!defined($env{'form.viewselect'})) { + $env{'form.viewselect'} ="summary"; + } + $env{'form.phase'} = 'disp_basic' if (! exists($env{'form.phase'})); + $env{'form.show'} = 20 if (! exists($env{'form.show'})); + # + $env{'form.searchmode'} = 'basic' if (! exists($env{'form.searchmode'})); + if ($env{'form.phase'} eq 'adv_search' || + $env{'form.phase'} eq 'disp_adv') { + $env{'form.searchmode'} = 'advanced'; + } elsif ($env{'form.phase'} eq 'course_search') { + $env{'form.searchmode'} = 'course_search'; + } + # + if ($env{'form.searchmode'} eq 'advanced') { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?phase=disp_adv&'. + 'catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"Advanced Search", + bug=>'Searching',}); + } elsif ($env{'form.searchmode'} eq 'course search') { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?phase=disp_adv&'. + 'catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"Course Search", + bug=>'Searching',}); + } + ## + ## Switch on the phase + ## + if ($env{'form.phase'} eq 'disp_basic') { + &print_basic_search_form($r,$closebutton,$hidden_fields); + } elsif ($env{'form.phase'} eq 'disp_adv') { + &print_advanced_search_form($r,$closebutton,$hidden_fields); + } elsif ($env{'form.phase'} eq 'results') { + &display_results($r,$importbutton,$closebutton,$diropendb); + } elsif ($env{'form.phase'} =~ /^(sort|run_search)$/) { + my ($query,$customquery,$customshow,$libraries,$pretty_string) = + &get_persistent_data($persistent_db_file, + ['query','customquery','customshow', + 'libraries','pretty_string']); + if ($env{'form.phase'} eq 'sort') { + &print_sort_form($r,$pretty_string); + } elsif ($env{'form.phase'} eq 'run_search') { + &run_search($r,$query,$customquery,$customshow, + $libraries,$pretty_string); + } + } elsif ($env{'form.phase'} eq 'course_search') { + &course_search($r); + } elsif(($env{'form.phase'} eq 'basic_search') || + ($env{'form.phase'} eq 'adv_search')) { + # + # We are running a search, try to parse it + my ($query,$customquery,$customshow,$libraries) = + (undef,undef,undef,undef); + my $pretty_string; + if ($env{'form.phase'} eq 'basic_search') { + ($query,$pretty_string,$libraries) = + &parse_basic_search($r,$closebutton,$hidden_fields); + return OK if (! defined($query)); + &make_persistent({ basicexp => $env{'form.basicexp'}}, + $persistent_db_file); + } else { # Advanced search + ($query,$customquery,$customshow,$libraries,$pretty_string) + = &parse_advanced_search($r,$closebutton,$hidden_fields); + return OK if (! defined($query)); + } + &make_persistent({ query => $query, + customquery => $customquery, + customshow => $customshow, + libraries => $libraries, + pretty_string => $pretty_string }, + $persistent_db_file); + # + # Set up table + if (! defined(&create_results_table())) { + my $errorstring=&Apache::lonmysql::get_error(); + &Apache::lonnet::logthis('lonsearchcat.pm: Unable to create '. + 'needed table. lonmysql error:'. + $errorstring); + + my $msg = + 'Unable to create table in which to store search results. '. + 'The search has been aborted.'; + return &error_page($r,$msg); + } + delete($env{'form.launch'}); + if (! &make_form_data_persistent($r,$persistent_db_file)) { + my $msg= + 'Unable to properly store search information. '. + 'The search has been aborted.'; + return &error_page($r,$msg); + } + ## + ## Print out the frames interface + ## + if (defined($query)) { + &print_frames_interface($r); + } + } + return OK; +} - %metadatafields=(); +sub error_page { + my ($r,$msg) = @_; + $r->print(&Apache::loncommon::start_page('Search Error'). + &mt($msg). + &Apache::loncommon::end_page()); + return OK; +} -# ------------------------------------------------ First, check out environment - $metadatafields{'owner'}=$ENV{'user.name'}.'@'.$ENV{'user.domain'}; +# +# 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'} = ''; + } + } +} - - %language=(); - $language{'any'}='Any language'; - { - my $fh=Apache::File->new($r->dir_config('lonTabDir').'/language.tab'); - map { - $_=~/(\w+)\s+([\w\s\-]+)/; - $language{$1}=$2; - } <$fh>; +sub hidden_field { + my ($name,$value) = @_; + if (! defined($value)) { + $value = $env{'form.'.$name}; } + return ''.$/; +} - %cprtag=(); - $cprtag{'any'}='Any copyright/distribution'; - { - my $fh=Apache::File->new($r->dir_config('lonIncludes').'/copyright.tab'); - map { - $_=~/(\w+)\s+([\w\s\-]+)/; - $cprtag{$1}=$2; - } <$fh>; +###################################################################### +###################################################################### +## +## Course Search +## +###################################################################### +###################################################################### +{ # Scope the course search to avoid global variables +# +# Variables For course search +my %alreadyseen; +my %hash; +my $totalfound; + +sub make_symb { + my ($id)=@_; + my ($mapid,$resid)=split(/\./,$id); + my $map=$hash{'map_id_'.$mapid}; + my $res=$hash{'src_'.$id}; + my $symb=&Apache::lonnet::encode_symb($map,$resid,$res); + return $symb; +} + +sub course_search { + my $r=shift; + my $pretty_search_string = ''.$env{'form.courseexp'}.''; + my $search_string = $env{'form.courseexp'}; + my @New_Words; + undef(%alreadyseen); + if ($env{'form.crsrelated'}) { + ($search_string,@New_Words) = &related_version($env{'form.courseexp'}); + if (@New_Words) { + $pretty_search_string .= ' '.&mt("with related words").": @New_Words."; + } else { + $pretty_search_string .= ' '.&mt('with no related words')."."; + } + } + my $fulltext=$env{'form.crsfulltext'}; + my $discuss=$env{'form.crsdiscuss'}; + my @allwords=($search_string,@New_Words); + $totalfound=0; + $r->print(&Apache::loncommon::start_page('Course Search'). + ''.&mt('No matches found in resources').'.
'); } - %mimetag=(); - $mimetag{'any'}='Any type'; - { - my $fh=Apache::File->new($r->dir_config('lonTabDir').'/filetypes.tab'); - map { - $_=~/(\w+)\s+(\w+)\s+([\w\s\-]+)/; - $mimetag{$1}=".$1 $3"; - } <$fh>; +# Check discussions if requested + if ($discuss) { + my $totaldiscussions = 0; + $r->print(''.&mt('No matches found in postings').'.
'); + } } + +# =================================================== Done going through course + $r->print(&Apache::loncommon::end_page()); +} - if ($ENV{'form.basicsubmit'} eq 'SEARCH') { - return &basicsearch($r,$ENV{'form.basicexp'}); +# =============================== This pulls up a resource and its dependencies + +sub checkonthis { + my ($r,$id,$url,$level,$title,$fulltext,$symb,@allwords)=@_; + $alreadyseen{$id}=1; + if (&Apache::loncommon::connection_aborted($r)) { return; } + $r->rflush(); + + my $result=$title.' '; + if ($env{'request.role.adv'} || !$hash{'encrypted_'.$id}) { + $result.=&Apache::lonnet::metadata($url,'title').' '. + &Apache::lonnet::metadata($url,'subject').' '. + &Apache::lonnet::metadata($url,'abstract').' '. + &Apache::lonnet::metadata($url,'keywords'); } + my ($extension)=($url=~/\.(\w+)$/); + if (&Apache::loncommon::fileembstyle($extension) eq 'ssi' && + ($url) && ($fulltext)) { + $result.=&Apache::lonnet::ssi_body($url.'?symb='.&Apache::lonnet::escape($symb)); + } + $result=~s/\s+/ /gs; + my $applies = 0; + $applies = &checkwords($result,$applies,@allwords); +# Does this resource apply? + if ($applies) { + $r->print('
-LIMIT BY CREATION DATE RANGE:
-
-
-between:
-
-
-and:
-
-
+$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.=(< | |
+
-LASTREVISIONDATEEND +$title: $uctitle:".
- " \n";
+ if (! defined($Fields{'title'}) || $Fields{'title'} eq '') {
+ $Fields{'title'} = 'Untitled';
+ }
+ my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'},
+ $Fields{'id'},$checkbox_num++);
+ # Render the result into html
+ $output.= &$viewfunction($prefix,%Fields);
+ # Print them out as they come in.
+ $r->print($output);
+ $r->rflush();
+ }
+ if (@Results < 1) {
+ $r->print(&mt("There were no results matching your query"));
+ } else {
+ $r->print
+ ('
".
- '';
-}
-
-sub searchphrasefield {
- my ($title,$name,$value)=@_;
- my $instruction=<
".
- '';
-}
-
-sub selectbox {
- my ($title,$name,$value,%options)=@_;
- my $uctitle=uc($title);
- my $selout="\n
".'';
-}
-
-# --------------------------------------------------- Performing a basic search
-sub basicsearch {
- my ($r,$expression)=@_;
-
- my $query=$expression;
-# $query="select * from metadata where concat(title,':\@:',author) like '\%Sci\%'";
- $query="select * from metadata where concat(title,\" \",author) like '\%$expression\%'";
-# my $reply=&Apache::lonnet::reply("querysend:DESCRIBE metadata","msul3");
- my $reply=&Apache::lonnet::reply("querysend:$query",'msul3');
-# my $reply=&Apache::lonnet::reply('querysend:select * from metadata','msul3');
-
- my @results;
-
- my $replyfile='';
- $reply=~/^([\.\w]+)$/; # must do since 'use strict' checks for tainting
- $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1;
- $reply=~/(.*?)\_/;
- my $hostname=$1;
-
- {
- while (1) {
- last if -e $replyfile;
- sleep 1;
+=pod
+
+=item Search Status update functions
+
+Each of the following functions changes the values of one of the
+input fields used to display the search status to the user. The names
+should be explanatory.
+
+Inputs: Apache request handler ($r), text to display.
+
+Returns: Nothing.
+
+=over 4
+
+=item &update_count_status()
+
+=item &update_status()
+
+=item &update_seconds()
+
+=back
+
+=cut
+
+######################################################################
+######################################################################
+sub update_count_status {
+ my ($r,$text) = @_;
+ $text =~ s/\'/\\\'/g;
+ $r->print
+ ("\n");
+ $r->rflush();
+}
+
+sub update_status {
+ my ($r,$text) = @_;
+ $text =~ s/\'/\\\'/g;
+ $r->print
+ ("\n");
+ $r->rflush();
+}
+
+{
+ my $max_time = 300; # seconds for the search to complete
+ my $start_time = 0;
+ my $last_time = 0;
+
+sub reset_timing {
+ $start_time = 0;
+ $last_time = 0;
+}
+
+sub time_left {
+ if ($start_time == 0) {
+ $start_time = time;
+ }
+ my $time_left = $max_time - (time - $start_time);
+ $time_left = 0 if ($time_left < 0);
+ return $time_left;
+}
+
+sub update_seconds {
+ my ($r) = @_;
+ my $time = &time_left();
+ if (($last_time-$time) > 0) {
+ $r->print("\n");
+ $r->rflush();
+ }
+ $last_time = $time;
+}
+
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &revise_button()
+
+Inputs: None
+
+Returns: html string for a 'revise search' button.
+
+=cut
+
+######################################################################
+######################################################################
+sub revise_button {
+ my $revise_phase = 'disp_basic';
+ $revise_phase = 'disp_adv' if ($env{'form.searchmode'} eq 'advanced');
+ my $newloc = '/adm/searchcat'.
+ '?persistent_db_id='.$env{'form.persistent_db_id'}.
+ '&cleargroupsort=1'.
+ '&phase='.$revise_phase;
+ my $result = qq{ };
+ return $result;
+}
+
+######################################################################
+######################################################################
+
+=pod
+
+=item &run_search()
+
+Executes a search query by sending it the the other servers and putting the
+results into MySQL.
+
+=cut
+
+######################################################################
+######################################################################
+sub run_search {
+ my ($r,$query,$customquery,$customshow,$serverlist,$pretty_string) = @_;
+ my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
+ $bodytag.=&Apache::lonhtmlcommon::breadcrumbs
+ (undef,'Searching','Searching',undef,undef,
+ $env{'form.catalogmode'} ne 'groupsearch');
+ my $connection = $r->connection;
+ #
+ # Print run_search header
+ #
+ my $html = &Apache::lonxml::xmlbegin();
+ my $head = &Apache::loncommon::head('Search Status');
+ $r->print(<
+ $pretty_string =~ s:^\s*
::i;
+ $pretty_string =~ s:(
)*\s*$::im;
+ my @Lines = split("
",$pretty_string);
+ # I keep getting blank items at the end of the list, hence the following:
+ while ($Lines[-1] =~ /^\s*$/ && @Lines) {
+ pop(@Lines);
+ }
+ if (@Lines > 2) {
+ $pretty_string = join '
',(@Lines[0..2],'....
');
+ }
+ $r->print(&mt("Search: [_1]",$pretty_string));
+ $r->rflush();
+ #
+ # Determine the servers we need to contact.
+ my @Servers_to_contact;
+ if (defined($serverlist)) {
+ if (ref($serverlist) eq 'ARRAY') {
+ @Servers_to_contact = @$serverlist;
+ } else {
+ @Servers_to_contact = ($serverlist);
+ }
+ } else {
+ @Servers_to_contact = sort(keys(%Apache::lonnet::libserv));
+ }
+ my %Server_status;
+ #
+ # Check on the mysql table we will use to store results.
+ my $table =$env{'form.table'};
+ if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
+ $r->print("Unable to determine table id to store search results in.".
+ "The search has been aborted.".
+ &Apache::loncommon::end_page());
+ return;
+ }
+ my $table_status = &Apache::lonmysql::check_table($table);
+ if (! defined($table_status)) {
+ $r->print("Unable to determine status of table.".
+ &Apache::loncommon::end_page());
+ &Apache::lonnet::logthis("Bogus table id of $table for ".
+ "$env{'user.name'} @ $env{'user.domain'}");
+ &Apache::lonnet::logthis("lonmysql error = ".
+ &Apache::lonmysql::get_error());
+ return;
+ }
+ if (! $table_status) {
+ &Apache::lonnet::logthis("lonmysql error = ".
+ &Apache::lonmysql::get_error());
+ &Apache::lonnet::logthis("lonmysql debug = ".
+ &Apache::lonmysql::get_debug());
+ &Apache::lonnet::logthis('table status = "'.$table_status.'"');
+ $r->print("The table id,$table, we tried to use is invalid.".
+ "The search has been aborted.".
+ &Apache::loncommon::end_page());
+ return;
+ }
+ ##
+ ## Prepare for the big loop.
+ my $hitcountsum;
+ my $server;
+ my $status;
+ my $revise = &revise_button();
+ $r->print(<
+Status Total Matches Time Remaining
+
+
+
+END
+ $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);
+ $Fields{'hostname'} = $server;
+ #
+ # Skip if external and we did not want that
+ next if ((! $env{'form.inclext'}) && ($Fields{'url'}=~/^\/ext\//));
+ # Skip based on copyright
+ next if (! ©right_check(\%Fields));
+
+ #
+ # 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 [_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 'groupsearch') {
+ $r->print("");
+# }
+ 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
+ my $buttons = '';
+ $buttons .= ' 'x3;
+ $buttons .= 'connection;
+ $r->print(&search_results_header($importbutton,$closebutton));
+ ##
+ ## Set viewing function
+ ##
+ my $viewfunction = $Views{$env{'form.viewselect'}};
+ if (!defined($viewfunction)) {
+ $r->print("Internal Error - Bad view selected.\n");
+ $r->rflush();
+ return;
+ }
+ ##
+ ## $checkbox_num is a count of the number of checkboxes output on the
+ ## page this is used only during catalogmode=groupsearch.
+ my $checkbox_num = 0;
+ ##
+ ## Get the catalog controls setup
+ ##
+ my $action = "/adm/searchcat?phase=results";
+ ##
+ ## Deal with groupsearch by opening the groupsearch db file.
+ if ($env{'form.catalogmode'} eq 'groupsearch') {
+ if (! tie(%groupsearch_db,'GDBM_File',$diropendb,
+ &GDBM_WRCREAT(),0640)) {
+ $r->print('Unable to store import results.'.
+ &Apache::loncommon::end_page());
+ $r->rflush();
+ return;
+ }
+ }
+ ##
+ ## Prepare the table for querying
+ 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;
+ }
+ ##
+ ## Determine how many results we need to get
+ $env{'form.start'} = 1 if (! exists($env{'form.start'}));
+ $env{'form.show'} = 20 if (! exists($env{'form.show'}));
+ if (exists($env{'form.prev'})) {
+ $env{'form.start'} -= $env{'form.show'};
+ } elsif (exists($env{'form.next'})) {
+ $env{'form.start'} += $env{'form.show'};
+ }
+ $env{'form.start'} = 1 if ($env{'form.start'}<1);
+ $env{'form.start'} = $total_results if ($env{'form.start'}>$total_results);
+ my $min = $env{'form.start'};
+ my $max;
+ if ($env{'form.show'} eq 'all') {
+ $max = $total_results ;
+ } else {
+ $max = $min + $env{'form.show'} - 1;
+ $max = $total_results if ($max > $total_results);
+ }
+ ##
+ ## Output form elements
+ $r->print(&hidden_field('table').
+ &hidden_field('phase').
+ &hidden_field('persistent_db_id').
+ &hidden_field('start')
+ );
+ #
+ # Build sorting selector
+ my @fields =
+ (
+ {key=>'default' },
+ {key=>'title' },
+ {key =>'author' },
+ {key =>'subject'},
+ {key =>'url',desc=>'URL'},
+ {key =>'keywords'},
+ {key =>'language'},
+ {key =>'creationdate'},
+ {key =>'lastrevisiondate'},
+ {key =>'owner'},
+ {key =>'copyright'},
+ {key =>'authorspace'},
+ {key =>'lowestgradelevel'},
+ {key =>'highestgradelevel'},
+ {key =>'standards',desc=>'Standards'},
+ {key =>'count',desc=>'Number of accesses'},
+ {key =>'stdno',desc=>'Students Attempting'},
+ {key =>'avetries',desc=>'Average Number of Tries'},
+ {key =>'difficulty',desc=>'Mean Degree of Difficulty'},
+ {key =>'disc',desc=>'Mean Degree of Discrimination'},
+ {key =>'clear',desc=>'Evaluation: Clear'},
+ {key =>'technical',desc=>'Evaluation: Technically Correct'},
+ {key =>'correct',desc=>'Evaluation: Material is Correct'},
+ {key =>'helpful',desc=>'Evaluation: Material is Helpful'},
+ {key =>'depth',desc=>'Evaluation: Material has Depth'},
+ );
+ my %fieldnames = &Apache::lonmeta::fieldnames();
+ my @field_order;
+ foreach my $field_data (@fields) {
+ push(@field_order,$field_data->{'key'});
+ if (! exists($field_data->{'desc'})) {
+ $field_data->{'desc'}=$fieldnames{$field_data->{'key'}};
+ } else {
+ if (! defined($field_data->{'desc'})) {
+ $field_data->{'desc'} = ucfirst($field_data->{'key'});
+ }
+ $field_data->{'desc'} = &mt($field_data->{'desc'});
+ }
+ }
+ my %sort_fields = map {$_->{'key'},$_->{'desc'}} @fields;
+ $sort_fields{'select_form_order'} = \@field_order;
+ $env{'form.sortorder'} = 'desc' if (! exists($env{'form.sortorder'}));
+ $env{'form.sortfield'} = 'count' if (! exists($env{'form.sortfield'}));
+ if (! exists($env{'form.sortorder'})) {
+ if ($env{'form.sortfield'}=~/^(count|stdno|disc|clear|technical|correct|helpful)$/) {
+ $env{'form.sortorder'}='desc';
+ } else {
+ $env{'form.sortorder'}='asc';
}
- # QUESTION: how should I handle this error condition..
- # I'm sure there is syntax elsewhere I can use..
- my $fh=Apache::File->new($replyfile) or
- ($r->print('file cannot be opened') and return OK);
- @results=<$fh>;
}
+ my $sortform = &mt('Sort by [_1] [_2]',
+ &Apache::loncommon::select_form($env{'form.sortfield'},
+ 'sortfield',
+ %sort_fields),
+ &Apache::loncommon::select_form($env{'form.sortorder'},
+ 'sortorder',
+ (asc =>&mt('Ascending'),
+ desc=>&mt('Descending')
+ ))
+ );
+ ##
+ ## Output links (if necessary) for 'prev' and 'next' pages.
+ $r->print
+ ('
+
+
+ $revise
+
'
+ );
+ if ($total_results == 0) {
+ $r->print(''.
+ ''.
+ ' '.
+ &prev_next_buttons($min,$env{'form.show'},$total_results).
+ ' '.
+ &viewoptions().' '.&mt('There are currently no results').'.
'.
+ "".
+ &Apache::loncommon::end_page());
+ return;
+ } else {
+ $r->print('
\n";
+ }
+ }
+ my $customdata='';
+ my %customhash;
foreach my $result (@results) {
- my ($title,$author,$subject,$notes,$abstract,$mime,$lang,
- $creationdate,$lastrevisiondate,$owner,$copyright
- )=map {&Apache::lonnet::unescape($_)} (split(/\,/,$result));
- my $shortabstract=$abstract;
- $shortabstract=substr($abstract,0,200) if length($abstract)>200;
- $compiledresult=<
-Author(s): $author
-Subject: $subject
-Keyword(s): not available yet
-Notes: $notes
-Abstract: $shortabstract
-MIME Type: $mimetag{$mime}
-Language: $language{$lang}
-Creation Date: $creationdate
-Last Revision Date: $lastrevisiondate
-Publisher/Owner: $owner
-Copyright/Distribution: $copyright
-Repository Location: $hostname
+ if ($result=~/^(custom\=.*)$/) { # grab all custom metadata
+ my $tmp=$result;
+ $tmp=~s/^custom\=//;
+ my ($k,$v)=map {&Apache::lonnet::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 'groupsearch'.
+
+=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 $result =
+ &Apache::lonxml::xmlbegin().
+ &Apache::loncommon::headtag();
+
+ # 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') {
+ $result.=<
+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=<
+ENDSTATUS
+}
+
+sub results_link {
+ my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}.
+ "&persistent_db_id=".$env{'form.persistent_db_id'};
+ my $results_link = $basic_link."&phase=results".
+ "&pause=1"."&start=1";
+ return $results_link;
+}
+
+######################################################################
+######################################################################
+sub print_frames_interface {
+ my $r = shift;
+ my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}.
+ "&persistent_db_id=".$env{'form.persistent_db_id'};
+ my $run_search_link = $basic_link."&phase=run_search";
+ my $results_link = &results_link();
+ my $html = &Apache::lonxml::xmlbegin();
+ my $head = &Apache::loncommon::headtag('LON-CAPA Digital Library Search Results');
+ my $end_head = &Apache::loncommon::endheadtag();
+ my $result = <<"ENDFRAMES";
+$html
+$head
+
+$end_head
+
+