--- loncom/interface/lonsearchcat.pm 2002/07/16 15:02:06 1.143 +++ loncom/interface/lonsearchcat.pm 2002/07/29 21:53:57 1.146 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.143 2002/07/16 15:02:06 matthew Exp $ +# $Id: lonsearchcat.pm,v 1.146 2002/07/29 21:53:57 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -87,8 +87,10 @@ use Apache::lonnet(); use Apache::File(); use CGI qw(:standard); use Text::Query; +use DBI; use GDBM_File; use Apache::loncommon(); +use Apache::lonmysql(); # ---------------------------------------- variables used throughout the module @@ -101,10 +103,6 @@ use Apache::loncommon(); =over 4 -=item $closebutton - -button that closes the search window - =item $importbutton button to take the select results and go to group sorting @@ -125,10 +123,6 @@ that produces it. Adding a new view typ adding a line to the definition of this hash and making sure the function takes the proper parameters. -=item $results_db - -The name of the database results from searches are put in. - =back =cut @@ -137,20 +131,18 @@ The name of the database results from se ###################################################################### # -- dynamically rendered interface components -my $closebutton; # button that closes the search window my $importbutton; # button to take the selected results and go to group sorting # -- miscellaneous variables my %groupsearch_db; # database hash my $diropendb = ""; # db file - -my $results_db = ""; # View Description Function Pointer my %Views = ("Detailed Citation View" => \&detailed_citation_view, "Summary View" => \&summary_view, "Fielded Format" => \&fielded_format_view, "XML/SGML" => \&xml_sgml_view ); - +my %persistent_db; +my $hidden_fields; ###################################################################### ###################################################################### @@ -178,25 +170,53 @@ string that holds portions of the screen ###################################################################### sub handler { my $r = shift; - untie %groupsearch_db; - + # + my $closebutton; # button that closes the search window + # This button is different for the RAT compared to + # normal invocation. + # $r->content_type('text/html'); $r->send_http_header; return OK if $r->header_only; + ## + ## 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']); + ## + ## 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(5); + delete($ENV{'form.pause'}); + } ## ## Initialize global variables ## my $domain = $r->dir_config('lonDefDomain'); $diropendb= "/home/httpd/perl/tmp/".&Apache::lonnet::escape($domain). "\_".&Apache::lonnet::escape($ENV{'user.name'})."_searchcat.db"; - $results_db = "/home/httpd/perl/tmp/".&Apache::lonnet::escape($domain). - '_'.&Apache::lonnet::escape($ENV{'user.name'})."_searchresults.db"; - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, - ['catalogmode','launch','acts','mode','form','element', - 'reqinterface']); + # + # 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.persistent_db_id'} = time; + } + 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'; + ## + &get_persistent_form_data($r,$persistent_db_file); ## ## Clear out old values from groupsearch database ## + untie %groupsearch_db if (tied(%groupsearch_db)); if ($ENV{'form.launch'} eq '1') { if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { &start_fresh_session(); @@ -208,23 +228,15 @@ sub handler { } } ## - ## 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 + $hidden_fields = ''; + ## if ($ENV{'form.catalogmode'} eq 'interactive') { - $hidden="". - "\n"; $closebutton=""."\n"; } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { - $hidden=< -END $closebutton=< END @@ -232,55 +244,77 @@ END END + } else { + $closebutton = ''; + $importbutton = ''; } - $hidden .= &make_persistent({ "form.mode" => $ENV{'form.mode'}, - "form.form" => $ENV{'form.form'}, - "form.element" => $ENV{'form.element'}, - "form.date" => 2 }); - ## - ## What are we doing? - ## - my $searchtype; - $searchtype = 'Basic' if ($ENV{'form.basicsubmit'} eq 'SEARCH'); - $searchtype = 'Advanced' if ($ENV{'form.advancedsubmit'} eq 'SEARCH'); - if ($searchtype) { + ## + ## Sanity checks on form elements + ## + if (!defined($ENV{'form.viewselect'})) { + $ENV{'form.viewselect'} ="Detailed Citation View"; + } + $ENV{'form.phase'} = 'displaybasic' if (! exists($ENV{'form.phase'})); + ## + ## Switch on the phase + ## + if ($ENV{'form.phase'} eq 'disp_basic') { + &print_basic_search_form($r,$closebutton); + } elsif ($ENV{'form.phase'} eq 'disp_adv') { + &print_advanced_search_form($r,$closebutton); + } elsif ($ENV{'form.phase'} eq 'results') { + &display_results($r,$importbutton,$closebutton); + } elsif($ENV{'form.phase'} eq 'run_search') { + my ($query,$customquery,$customshow,$libraries,$pretty_string) = + &get_persistent_data($persistent_db_file, + ['query','customquery','customshow', + 'libraries','pretty_string']); + &write_status($r,"query = $query"); + &write_status($r,"customquery = $customquery"); + &write_status($r,"customshow = $customshow"); + &write_status($r,"libraries = $libraries"); + &write_status($r,"pretty_string = $pretty_string"); + &run_search($r,$query,$customquery,$customshow, + $libraries,$pretty_string); + } elsif(($ENV{'form.phase'} eq 'basic_search') || + ($ENV{'form.phase'} eq 'adv_search')) { + # Set up table + if (! defined(&create_results_table())) { + # Unable to make table to store results in. + # Definately abort search. + } + if (! &make_form_data_persistent($r,$persistent_db_file)) { + # Unable to store persistent data. + # Probably should bail out. + } + # # We are running a search my ($query,$customquery,$customshow,$libraries) = (undef,undef,undef,undef); my $pretty_string; - if ($searchtype eq 'Basic') { - ($query,$pretty_string) = &parse_basic_search($r); - } elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') { + if ($ENV{'form.phase'} eq 'basic_search') { + ($query,$pretty_string) = &parse_basic_search($r,$closebutton); + } else { # Advanced search ($query,$customquery,$customshow,$libraries,$pretty_string) - = &parse_advanced_search($r); + = &parse_advanced_search($r,$closebutton); return OK if (! defined($query)); } - # Output some information to the user. - $r->print(&search_results_header($searchtype,$pretty_string)); - $r->print("Sending search request to LON-CAPA servers.
\n"); - $r->rflush(); - # 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). - my $reply=&Apache::lonnet::metadata_query($query,$customquery, - $customshow,$libraries); - $r->rflush(); - &output_results($searchtype,$r,$reply,$hidden); - } else { - # - # We need to get information to search on - # - # Set the default view if it is not already set. - if (!defined($ENV{'form.viewselect'})) { - $ENV{'form.viewselect'} ="Detailed Citation View"; - } - # Output the advanced interface - if ($ENV{'form.reqinterface'} eq 'advanced') { - $r->print(&advanced_search_form($closebutton,$hidden)); - } else { - # Output normal search interface - $r->print(&basic_search_form($closebutton,$hidden)); - } + &write_status($r,"query = $query"); + &write_status($r,"customquery = $customquery"); + &write_status($r,"customshow = $customshow"); + &write_status($r,"libraries = $libraries"); + &write_status($r,"pretty_string = $pretty_string"); + &make_persistent($r, + { query => $query, + customquery => $customquery, + customshow => $customshow, + libraries => $libraries, + pretty_string => $pretty_string }, + $persistent_db_file); + ## + ## Print out the frames interface + ## + &print_frames_interface($r); } return OK; } @@ -290,7 +324,7 @@ END =pod -=item &basic_search_form() +=item &print_basic_search_form() Returns a scalar which holds html for the basic search form. @@ -299,8 +333,8 @@ Returns a scalar which holds html for th ###################################################################### ###################################################################### -sub basic_search_form{ - my ($closebutton,$hidden) = @_; +sub print_basic_search_form{ + my ($r,$closebutton) = @_; my $scrout=<<"ENDDOCUMENT"; @@ -317,7 +351,8 @@ sub basic_search_form{

Search Catalog

-$hidden + +$hidden_fields

Basic Search

Enter terms or phrases separated by AND, OR, or NOT @@ -333,7 +368,7 @@ ENDDOCUMENT # $scrout.='Search historic archives'; my $checkbox = &simplecheckbox('related',$ENV{'form.related'}); $scrout.=<Advanced Search +Advanced Search $checkbox use related words

@@ -352,7 +387,8 @@ END ENDDOCUMENT - return $scrout; + $r->print($scrout); + return; } ###################################################################### ###################################################################### @@ -368,8 +404,8 @@ Returns a scalar which holds html for th ###################################################################### ###################################################################### -sub advanced_search_form{ - my ($closebutton,$hidden) = @_; +sub print_advanced_search_form{ + my ($r,$closebutton) = @_; my $advanced_buttons = <<"END";

@@ -401,7 +437,8 @@ Enter terms or phrases separated by sear such as AND, OR, or NOT.
$advanced_buttons -$hidden +$hidden_fields +
VIEW: @@ -453,22 +490,17 @@ ENDHEADER # adjust the size of the select box my $size = 4; my $size = (scalar @domains < ($size - 1) ? scalar @domains + 1 : $size); - # standalone machines do not get to choose a domain to search. - if ((scalar @domains) == 1) { - $scrout .=''."\n"; - } else { - $scrout.="\n".''. - 'DOMAINS
'. - ''."\n". + ''."\n"; - foreach my $dom (sort @domains) { - $scrout.="\n"; - } - $scrout.="\n"; + foreach my $dom (sort @domains) { + $scrout.="\n"; } + $scrout.="\n"; #---------------------------------------------------------------- $scrout.=&selectbox('Limit by language','language', $ENV{'form.language'},'any','Any Language', @@ -548,7 +580,8 @@ $advanced_buttons ENDDOCUMENT - return $scrout; + $r->print($scrout); + return; } ###################################################################### @@ -556,34 +589,166 @@ ENDDOCUMENT =pod -=item &make_persistent() +=item &get_persistent_form_data -Returns a scalar which holds the current ENV{'form.*'} values in -a 'hidden' html input tag. This allows search interface information -to be somewhat persistent. +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 $r = shift; + my $filename = shift; + return undef 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.category'=>1,'form.domains'=>1); + # + # Loop through the keys, looking for 'form.' + foreach my $name (keys(%persistent_db)) { + next if ($name !~ /^form./); + my @values = map { + &Apache::lonnet::unescape($_); + } split(',',$persistent_db{$name}); + next if (@values <1); + if (exists($ENV{$name})) { + if (ref($ENV{$name}) eq 'ARRAY') { + # If it is an array, tack @values on the end of it. + $ENV{$name} = [@$ENV{$name},@values]; + } elsif (! ref($ENV{$name}) && $arrays_allowed{$name}) { + # if arrays are allowed, turn it into one and add @values + $ENV{$name} = [$ENV{$name},@values]; + } # otherwise, assume the value in $ENV{$name} is better than ours. + } else { + if ($arrays_allowed{$name}) { + $ENV{$name} = [@values]; + } else { + $ENV{$name} = $values[0] if ($values[0]); + } + } + &write_status($r,"Reconstructed $name = $ENV{$name}"); + } + 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 { + &Apache::lonnet::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 seperated string. + +=cut +###################################################################### +###################################################################### sub make_persistent { + my $r = shift; my %save = %{shift()}; - my $persistent=''; - foreach my $name (keys %save) { - if ($name =~ /^form\./ && $name !~ /submit/) { - my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); - $name=~s/^form\.//; - foreach (@values) { - s/\"/\'/g; # do not mess with html field syntax - next if (! $_ ); - $persistent.=< -END - } - } + my $filename = shift; + return undef if (! tie(%persistent_db,'GDBM_File', + $filename,&GDBM_WRCREAT,0640)); + foreach my $name (keys(%save)) { + next if (! exists($save{$name})); + next if (! defined($save{$name}) || $save{$name} eq ''); + my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name})); + # We handle array references, but not recursively. + my $store = join(',', map { &Apache::lonnet::escape($_); } @values ); + $persistent_db{$name} = $store; + &write_status($r,"Stored $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 seperated 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 $persistent; + return &make_persistent($r,\%save,$filename); } ###################################################################### @@ -893,7 +1058,7 @@ Parse advanced search form and return th ###################################################################### ###################################################################### sub parse_advanced_search { - my ($r)=@_; + my ($r,$closebutton)=@_; my $fillflag=0; my $pretty_search_string = "
\n"; # Clean up fields for safety @@ -929,7 +1094,7 @@ sub parse_advanced_search { } } unless ($fillflag) { - &output_blank_field_error($r); + &output_blank_field_error($r,$closebutton); return ; } # Turn the form input into a SQL-based query @@ -992,7 +1157,7 @@ sub parse_advanced_search { ); # Test to see if date windows are legitimate if ($datequery=~/^Incorrect/) { - &output_date_error($r,$datequery); + &output_date_error($r,$datequery,$closebutton); return ; } elsif ($datequery) { # Here is where you would set up pretty_search_string to output @@ -1072,7 +1237,7 @@ Parse the basic search form and return a ###################################################################### ###################################################################### sub parse_basic_search { - my ($r)=@_; + my ($r,$closebutton)=@_; # Clean up fields for safety for my $field ('basicexp') { $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g; @@ -1086,7 +1251,7 @@ sub parse_basic_search { # Check to see if enough is filled in unless (&filled($ENV{'form.basicexp'})) { - &output_blank_field_error($r); + &output_blank_field_error($r,$closebutton); return OK; } my $pretty_search_string = ''.$ENV{'form.basicexp'}.''; @@ -1100,7 +1265,6 @@ sub parse_basic_search { $pretty_search_string .= " with no related words."; } } - &Apache::lonnet::logthis("Search String: $search_string"); # Build SQL query string based on form page my $query=''; my $concatarg=join('," ",', @@ -1315,231 +1479,527 @@ sub build_date_queries { ###################################################################### ###################################################################### -=pod - -=item &output_results() +=pod -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.) +=item ©right_check() =cut ###################################################################### ###################################################################### -sub output_results { -# &Apache::lonnet::logthis("output_results:".time); - my $fnum; # search result counter - my ($mode,$r,$replyref,$hidden)=@_; - my %rhash=%{$replyref}; - my $compiledresult=''; - my $timeremain=300; # (seconds) - my $elapsetime=0; - my $resultflag=0; - my $tflag=1; - ## - ## Set viewing function - ## - my $viewfunction = $Views{$ENV{'form.viewselect'}}; - if (!defined($viewfunction)) { - $r->print("Internal Error - Bad view selected.\n"); - $r->rflush(); - return; + +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') && + (($ENV{'user.name'} ne $resname) && + ($ENV{'user.domain'} ne $resdom))) { + return 0; } + # Check for domain + if (($Metadata->{'copyright'} eq 'domain') && + ($ENV{'user.domain'} ne $resdom)) { + return 0; + } + return 1; +} + +##################################################################### +##################################################################### + +=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 + +## +## Restrictions: +## columns of type 'text' and 'blob' cannot have defaults. +## columns of type 'enum' cannot be used for FULLTEXT. +## +my @DataOrder = qw/id title author subject url keywords version notes + abstract mime lang owner copyright creationdate lastrevisiondate hostname + idx_title idx_author idx_subject idx_abstract idx_mime idx_language + idx_owner idx_copyright/; + +my %Datatypes = + ( id =>{ type => 'INT', + restrictions => 'NOT NULL', + primary_key => 'yes', + auto_inc => 'yes' + }, + title =>{ type=>'TEXT'}, + author =>{ type=>'TEXT'}, + subject =>{ type=>'TEXT'}, + url =>{ type=>'TEXT', + restrictions => 'NOT NULL' }, + keywords =>{ type=>'TEXT'}, + version =>{ type=>'TEXT'}, + notes =>{ type=>'TEXT'}, + abstract =>{ type=>'TEXT'}, + mime =>{ type=>'TEXT'}, + lang =>{ type=>'TEXT'}, + owner =>{ type=>'TEXT'}, + copyright =>{ type=>'TEXT'}, + hostname =>{ type=>'TEXT'}, + #-------------------------------------------------- + creationdate =>{ type=>'DATETIME'}, + lastrevisiondate =>{ type=>'DATETIME'}, + #-------------------------------------------------- + idx_title =>{ type=>'FULLTEXT', target=>'title'}, + idx_author =>{ type=>'FULLTEXT', target=>'author'}, + idx_subject =>{ type=>'FULLTEXT', target=>'subject'}, + idx_abstract =>{ type=>'FULLTEXT', target=>'abstract'}, + idx_mime =>{ type=>'FULLTEXT', target=>'mime'}, + idx_language =>{ type=>'FULLTEXT', target=>'lang'}, + idx_owner =>{ type=>'FULLTEXT', target=>'owner'}, + idx_copyright =>{ type=>'FULLTEXT', target=>'copyright'}, + ); + +###################################################################### +###################################################################### + +=pod + +=item &create_results_table() + +Creates the table of search results by calling lonmysql. Stores the +table id in $ENV{'form.table'} + +Inputs: none. + +Returns: the identifier of the table on success, undef on error. + +=cut + +###################################################################### +###################################################################### +sub create_results_table { + my $table = &Apache::lonmysql::create_table + ( { columns => \%Datatypes, + column_order => \@DataOrder, + } ); + if (defined($table)) { + $ENV{'form.table'} = $table; + return $table; + } + return undef; # Error... +} +###################################################################### +###################################################################### + +=pod + +=item &write_status() + +=cut + +###################################################################### +###################################################################### +sub write_status { + my ($r,$string) = @_; + $string =~ s/(\')/\$1/g; + $string =~ s/\n//sg; +# $r->print("\n"); +# $r->rflush(); + return; +} + +###################################################################### +###################################################################### + +=pod + +=item &run_search + +=cut + +###################################################################### +###################################################################### +sub run_search { + my ($r,$query,$customquery,$customshow,$serverlist,$pretty_string) = @_; # - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(\%ENV); + # Timing variables # - # Begin producing output - $r->rflush(); + my $starttime = time; + my $max_time = 120; # seconds for the search to complete # - # begin showing the cataloged results - my $action = "/adm/searchcat"; - if ($mode eq 'Basic') { - $action .= "?reqinterface=basic"; - } elsif ($mode eq 'Advanced') { - $action .= "?reqinterface=advanced"; - } - $r->print(< -$hidden - - -$importbutton -$closebutton -$persistent -
-CATALOGCONTROLS + # Print run_search header # - # make the pop-up window for status - $r->print(&make_popwin(%rhash)); + $r->print("Search Status"); + $r->print("Search: ".$pretty_string."
\n"); $r->rflush(); + # + # Determine the servers we need to contact. + # + my @Servers_to_contact; + if (defined($serverlist)) { + @Servers_to_contact = @$serverlist; + } else { + @Servers_to_contact = sort(keys(%Apache::lonnet::libserv)); + } + my %Server_status; + my $table =$ENV{'form.table'}; + if (! defined($table)) { + # What do I do now? Print out an error page. + &Apache::lonnet::logthis("lonmysql attempted to create a table ". + "and this was the result:". + &Apache::lonmysql::get_error()); + $r->print("An internal error occured with the database.
". + "The error has been logged, but you should probably alert". + " your system administrator."); + return; + } ## - ## Prepare for the main loop below + ## Prepare for the big loop. ## - my $servercount=0; - my $hitcountsum=0; - my $servernum=(keys %rhash); - my $serversleft=$servernum; - ## - ## Run until we run out of time or we run out of servers - ## - while($serversleft && $timeremain) { - ## - ## %rhash has servers deleted from it as results come in - ## (within the foreach loop below). - ## - foreach my $rkey (sort keys %rhash) { -# &Apache::lonnet::logthis("Server $rkey:".time); - $servercount++; - $compiledresult=''; - my $reply=$rhash{$rkey}; - my @results; - if ($reply eq 'con_lost') { - &popwin_imgupdate($r,$rkey,"srvbad.gif"); - $serversleft--; - delete $rhash{$rkey}; - } else { - # must do since 'use strict' checks for tainting - $reply=~/^([\.\w]+)$/; - my $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1; - $reply=~/(.*?)\_/; - for (my $counter=0;$counter<2;$counter++) { - if (-e $replyfile && ! -e "$replyfile.end") { - &popwin_imgupdate($r,$rkey,"srvhalf.gif"); - &popwin_js($r,'popwin.hc["'.$rkey.'"]='. - '"still transferring..."'.';'); - } - # Are we finished transferring data? - if (-e "$replyfile.end") { - $serversleft--; - delete $rhash{$rkey}; - if (-s $replyfile) { - &popwin_imgupdate($r,$rkey,"srvgood.gif"); - my $fh; - unless ($fh=Apache::File->new($replyfile)){ - # Is it really appropriate to die on this error? - $r->print('ERROR: file '. - $replyfile.' cannot be opened'); - return OK; - } - @results=<$fh> if $fh; - my $hits =@results; - &popwin_js($r,'popwin.hc["'.$rkey.'"]='. - $hits.';'); - $hitcountsum+=$hits; - &popwin_js($r,'popwin.document.forms.popremain.'. - 'numhits.value='.$hitcountsum.';'); - } else { - &popwin_imgupdate($r,$rkey,"srvempty.gif"); - &popwin_js($r,'popwin.hc["'.$rkey.'"]=0;'); - } - last; - } # end of if ( -e "$replyfile.end") - last unless $timeremain; - sleep 1; # wait for daemons to write files? - $timeremain--; - $elapsetime++; - &popwin_js($r,"popwin.document.popremain.". - "elapsetime.value=$elapsetime;"); - } - &popwin_js($r,'popwin.document.whirly.'. - 'src="/adm/lonIcons/lonanimend.gif";'); - } # end of if ($reply eq 'con_lost') else statement - my %Fields = undef; # Holds the data to be sent to the various - # *_view routines. - my ($extrashow,$customfields,$customhash) = - &handle_custom_fields(\@results); - my @customfields = @$customfields; - my %customhash = %$customhash; - untie %groupsearch_db if (tied %groupsearch_db); - # - if (! tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { - $r->print('Unable to tie hash to db '. - 'file'); + my $hitcountsum; + my $server; + my $status; + while ((time - $starttime < $max_time) && + ((@Servers_to_contact) || keys(%Server_status))) { + # Send out a search request if it needs to be done. + if (@Servers_to_contact) { + # Contact one server + my $server = shift(@Servers_to_contact); + my $reply=&Apache::lonnet::metadata_query($query,$customquery, + $customshow,[$server]); + ($server) = keys(%$reply); + $Server_status{$server} = $reply->{$server}; + # $r->print("Contacted:$server:reply:$Server_status{$server}"); + if ($max_time - (time - $starttime) < 20) { + # If there are less than 20 seconds to go in the search, + # give the newly contacted servers 20 more seconds to + # respond.... + $max_time += 20; + } } else { - if ($ENV{'form.launch'} eq '1') { - &start_fresh_session(); - } - foreach my $result (@results) { - next if $result=~/^custom\=/; - chomp $result; - next unless $result; - %Fields = &parse_raw_result($result,$rkey); - # - # Check copyright tags and skip results the user cannot use - my (undef,undef,$resdom,$resname) = split('/',$Fields{'url'}); - # Check for priv - if (($Fields{'copyright'} eq 'priv') && - (($ENV{'user.name'} ne $resname) && - ($ENV{'user.domain'} ne $resdom))) { + sleep(1); # wait a sec. to give time for files to be written + } + while (my ($server,$status) = each(%Server_status)) { + if ($status eq 'con_lost') { + delete ($Server_status{$server}); + # $r->print("server $server is not responding."); + next; + } + $status=~/^([\.\w]+)$/; + my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$1; + if (-e $datafile && ! -e "$datafile.end") { + # Let the user know we are receiving data from the server + # $r->print("$server:Receiving file"); + next; + } + if (-e "$datafile.end") { + if (-z "$datafile") { + delete($Server_status{$server}); next; } - # Check for domain - if (($Fields{'copyright'} eq 'domain') && - ($ENV{'user.domain'} ne $resdom)) { + my $fh; + if (!($fh=Apache::File->new($datafile))) { + # Error opening file... + # Tell the user and exit...? + # Should I give up on opening it? + $r->print("Unable to open search results file for ". + "server $server. Omitting from search"); next; } - # - $Fields{'extrashow'}=$extrashow; - if ($extrashow) { - foreach my $field (@customfields) { - my $value=''; - $value = $1 if ($customhash{$Fields{'url'}}=~/\<{$field}[^\>]*\>(.*?)\<\/{$field}[^\>]*\>/s); - $Fields{'extrashow'}=~s/\<\!\-\- $field \-\-\>/ $value/g; + # Read in the whole file. + while (my $result = <$fh>) { + # handle custom fields? Someday we will! + chomp($result); + next unless $result; + # Parse the result. + my %Fields = &parse_raw_result($result,$server); + $Fields{'hostname'} = $server; + 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()); } - } - $compiledresult.="\n

\n"; - if ($ENV{'form.catalogmode'} eq 'interactive') { - my $titleesc=$Fields{'title'}; - $titleesc=~s/\'/\\'/; # ' - $compiledresult.=<print(&Apache::lonmysql::get_debug()); + $hitcountsum ++; + } # End of foreach (@results) + $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}); + # $r->print("Received $new_count more results from ". + # $server."."); + } + } + # Finished looping through the servers + } + &Apache::lonmysql::disconnect_from_db(); + # Let the user know + # + # We have run out of time or run out of servers to talk to and + # results to get. + $r->print("

Search completed.

"); + if ($hitcountsum) { + $r->print($hitcountsum." successful matches to your query.
"); + } else { + $r->print("There were no successful matches to your query.
"); + } + $r->print(""); + return; +} + +###################################################################### +###################################################################### +=pod + +=item &prev_next_buttons + +=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. + my $links; + ## + ## Prev + my $prev_min = $current_min - $show; + $prev_min = 0 if $prev_min < 0; + if ($prev_min < $current_min) { + $links .= qq{ +prev +}; + } else { + $links .= 'prev'; + } + ## + ## Pages.... Someday. + ## + $links .= qq{   +reload +}; + ## + ## Next + my $next_min = $current_min + $show; + $next_min = $current_min if ($next_min > $total); + if ($next_min != $current_min) { + $links .= qq{   +next +}; + } else { + $links .= ' next'; + } + return $links; +} +###################################################################### +###################################################################### + +=pod + +=item &display_results + +=cut + +###################################################################### +###################################################################### +sub display_results { + my ($r,$mode,$importbutton,$closebutton) = @_; + $r->print(&search_results_header()); + ## + ## Set viewing function + ## + my $viewfunction = $Views{$ENV{'form.viewselect'}}; + if (!defined($viewfunction)) { + $r->print("Internal Error - Bad view selected.\n"); + $r->rflush(); + return; + } + ## + ## Get the catalog controls setup + ## + my $action = "/adm/searchcat?phase=results"; + ## + ## + ## + if ($ENV{'form.catalogmode'} eq 'groupsearch') { + if (! tie(%groupsearch_db,'GDBM_File',$diropendb, + &GDBM_WRCREAT,0640)) { + $r->print('Unable to tie hash to db file'); + $r->rflush(); + return; + } + } + ## + ## Prepare the table for querying + ## + my $table = $ENV{'form.table'}; + my $connection_result = &Apache::lonmysql::connect_to_db(); + if (!defined($connection_result)) { + $r->print(&Apache::lonmysql::get_error()); + } + my $table_check = &Apache::lonmysql::check_table($table); + if (! defined($table_check)) { + $r->print("A MySQL error has occurred."); + &Apache::lonnet::logthis("lonmysql was unable to determine the status". + " of table ".$table); + return; + } 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; + } + ## + ## 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::lonnet::logthis("lonmysql was unable to determine the number". + " of rows in table ".$table); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + &Apache::lonnet::logthis(&Apache::lonmysql::get_debug()); + return; + } + if ($total_results == 0) { + $r->print("There were no results matching your query.\n". + ""); + return; + } + ## + ## Determine how many results we need to get + ## + $ENV{'form.show'} = 20; + $ENV{'form.start'} = 0 if (! exists($ENV{'form.start'})); + $ENV{'form.show'} = 'all' if (! exists($ENV{'form.show'})); + my $min = $ENV{'form.start'}; + my $max; + if ($ENV{'form.show'} eq 'all') { + $max = $total_results ; + } else { + $max = $min + $ENV{'form.show'}; + $max = $total_results if ($max > $total_results); + } + ## + ## Output links (if necessary) for 'prev' and 'next' pages. + ## + $r->print("
Results $min to $max out of $total_results
\n"); + $r->print + ('
'. + &prev_next_buttons($min,$ENV{'form.show'},$total_results, + "table=".$ENV{'form.table'}. + "&phase=results". + "&persistent_db_id=".$ENV{'form.persistent_db_id'}) + ."

\n" + ); + ## + ## Get results from MySQL table + ## + my @Results = &Apache::lonmysql::get_rows($table, + 'id>'.$min.' AND id<='.$max); + ## + ## Loop through the results and output them. + ## + foreach my $row (@Results) { + my %Fields = %{&parse_row(@$row)}; + my $output="

\n"; + $output.=&catalogmode_output($Fields{'title'},$Fields{'url'}); + # Render the result into html + $output.= &$viewfunction(%Fields); + $output.="

\n
"; + # Print them out as they come in. + $r->print($output); + $r->rflush(); + } + if (@Results < 1) { + $r->print("There were no results matching your query"); + } + $r->print(""); + $r->rflush(); + untie %groupsearch_db; + return; +} + +###################################################################### +###################################################################### + +=pod + +=item &catalogmode_output($title,$url) + +Returns html needed for the various catalog modes. Gets inputs from +$ENV{'form.catalogmode'}. Stores data in %groupsearch_db and $fnum +(local variable). + +=cut + +###################################################################### +###################################################################### +{ +my $fnum; + +sub catalogmode_output { + my $output = ''; + my ($title,$url) = @_; + if ($ENV{'form.catalogmode'} eq 'interactive') { + $title=~ s/\'/\\'/g; # ' Escape single quotes. + if ($ENV{'form.catalogmode'} eq 'interactive') { + $output.=< +onClick="javascript:select_data('$title','$url')"> -
END - } - if ($ENV{'form.catalogmode'} eq 'groupsearch') { - $fnum+=0; - $groupsearch_db{"pre_${fnum}_link"}=$Fields{'url'}; - $groupsearch_db{"pre_${fnum}_title"}=$Fields{'title'}; - $compiledresult.=< -
END -# -# - $fnum++; - } - # Render the result into html - $compiledresult.= &$viewfunction(%Fields, hostname => $rkey ); - if ($compiledresult or $servercount!=$servernum) { - $compiledresult.="
"; - } - } - untie %groupsearch_db; - } - if ($compiledresult) { - $resultflag=1; - $r->print($compiledresult); - } - } # 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->print("\n\n"); - $r->rflush(); - return; + $fnum++; + } + return $output; +} + +} +###################################################################### +###################################################################### + +=pod + +=item &parse_row + +Parse a row returned from the database. + +=cut + +###################################################################### +###################################################################### +sub parse_row { + my @Row = @_; + my %Fields; + for (my $i=0;$i<=$#Row;$i++) { + $Fields{$DataOrder[$i]}=&Apache::lonnet::unescape($Row[$i]); + } + $Fields{'language'} = + &Apache::loncommon::languagedescription($Fields{'lang'}); + $Fields{'copyrighttag'} = + &Apache::loncommon::copyrightdescription($Fields{'copyright'}); + $Fields{'mimetag'} = + &Apache::loncommon::filedescription($Fields{'mime'}); + return \%Fields; } ########################################################### @@ -1611,6 +2071,7 @@ sub parse_raw_result { $Fields{'title'}='Untitled'; } unless ($ENV{'user.adv'}) { + # What is this anyway? $Fields{'keywords'} = '- not displayed -'; $Fields{'notes'} = '- not displayed -'; $Fields{'abstract'} = '- not displayed -'; @@ -1709,21 +2170,8 @@ Checked for existance & 'edit' mode. ###################################################################### ###################################################################### sub search_results_header { - my ($mode,$pretty_query) = @_; - $mode = lc($mode); - my $title; - if ($mode eq 'advanced') { - $title = "Advanced Search Results"; - } elsif ($mode eq 'basic') { - $title = "Basic Search Results"; - } my $result = ''; # output beginning of search page - $result.=< - -$title -BEGINNING # conditional output of script functions dependent on the mode in # which the search was invoked if ($ENV{'form.catalogmode'} eq 'interactive'){ @@ -1790,124 +2238,46 @@ SCRIPT } SCRIPT - $result.=< - function displayinfo(val) { - popwin.document.forms.popremain.sdetails.value=val; - } - function openhelp(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=400,height=300'); - openhelpwin.focus(); - } - function abortsearch(val) { - popwin.close(); - } - -SCRIPT $result.=< - - -

$title

END - if ($pretty_query) { - $result .= "

Search query: $pretty_query

"; - } return $result; } ###################################################################### ###################################################################### - -=pod - -=item &make_popwin() - -Returns html with javascript in it to open up the status window. - -=cut +sub search_status_header { + return <Search Status + +

Search Status

+Sending search request to LON-CAPA servers.
+ENDSTATUS +} ###################################################################### ###################################################################### -sub make_popwin { - my %rhash = @_; - my $servernum=(keys %rhash); - my $hcinit; - my $grid="'
'+\n"; - # $sn is the server number, used ONLY to make sure we have - # rows of 10 each. No longer used to index images. - my $sn=1; - foreach my $sk (sort keys %rhash) { - $grid.="''+ - $grid.="'+\"'\"+')\">'+"; - $grid.="\n"; - $grid.="'\"".$sk.'+\n"; - $grid.="'
'+\n" unless $sn%10; - $sn++; - } - my $result.=< - popwin=open('','popwin','scrollbars=1,width=400,height=220'); - popwin.focus(); - popwin.document.writeln('<'+'html>'); - popwin.document.writeln('<'+'head>'); - popwin.document.writeln('<'+'script>'); - popwin.document.writeln('hc=new Array();$hcinit'); - popwin.document.writeln('<'+'/script>'); - popwin.document.writeln('<'+'/head>'+ - '<'+'body bgcolor="#FFFFFF">'+ - '<'+'image name="whirly" align="right" src="/adm/lonIcons/'+ - 'lonanim.gif" '+ - 'alt="animated logo" />'+ - '<'+'h3>Search Results Progress<'+'/h3>'+ - '<'+'form name="popremain">'+ - '<'+'tt>'+ - '<'+'br clear="all"/>PLEASE BE PATIENT'+ - '<'+'br />SCANNING $servernum SERVERS'+ - '<'+'br clear="all" />Number of record hits found '+ - '<'+'input type="text" size="10" name="numhits"'+ - ' value="0" />'+ - '<'+'br clear="all" />Time elapsed '+ - '<'+'input type="text" size="10" name="elapsetime"'+ - ' value="0" />'+ - '<'+'br />'+ - 'SERVER GRID (click on any cell for details)'+ - $grid - '<'+'br />'+ - 'Server details '+ - '<'+'input type="text" size="35" name="sdetails"'+ - ' value="" />'+ - '<'+'br />'+ - ' <'+'input type="button" name="button"'+ - ' value="close this window" '+ - ' onClick="javascript:opener.abortsearch()" />'+ - ' <'+'input type="button" name="button"'+ - ' value="help" onClick="javascript:opener.openhelp()" />'+ - '<'+'/tt>'+ - '<'+'/form>'+ - '<'+'/body><'+'/html>'); - popwin.document.close(); - -ENDPOP - return $result; +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 = $basic_link."&phase=results". + "&pause=10"."&start=0"."&show=20"; + my $result = <<"ENDFRAMES"; + + +LON-CAPA Digital Library Search Results + + + + + + +ENDFRAMES + + $r->print($result); + return; } ###################################################################### @@ -1962,7 +2332,6 @@ END =item &summary_view() =cut - ###################################################################### ###################################################################### sub summary_view { @@ -2096,10 +2465,8 @@ sub filled { ###################################################################### ###################################################################### sub output_blank_field_error { - my ($r)=@_; + my ($r,$closebutton)=@_; # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(\%ENV); - $r->print(< @@ -2111,9 +2478,9 @@ BEGINNING

Search Catalog

-$persistent - +$hidden_fields +Revise search request  $closebutton

Helpful Message

@@ -2137,15 +2504,19 @@ RESULTS Output a full html page with an error message. +Inputs: + + $r, the request pointer. + $message, the error message for the user. + $closebutton, the specialized close button needed for groupsearch. + =cut ###################################################################### ###################################################################### sub output_date_error { - my ($r,$message)=@_; + my ($r,$message,$closebutton)=@_; # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(\%ENV); - $r->print(< @@ -2155,7 +2526,7 @@ sub output_date_error {

Search Catalog

-$persistent +$hidden_fields $closebutton @@ -2195,51 +2566,6 @@ sub start_fresh_session { } } -###################################################################### -###################################################################### - -=pod - -=item &popwin_js() send javascript to popwin - -=cut - -###################################################################### -###################################################################### -sub popwin_js { - # Print javascript out to popwin, but make sure we dont generate - # any javascript errors in doing so. - my ($r,$text) = @_; - $r->print(<<"END"); - -END - $r->rflush(); -} - -###################################################################### -###################################################################### - -=pod - -=item &popwin_imgupdate() - -Send a given image (and its location) out to the browser. Takes as -input $r, loncapa server id, and an icon URL. - -=cut - -###################################################################### -###################################################################### -sub popwin_imgupdate { - my ($r,$server,$icon) = @_; - &popwin_js($r,'popwin.document.img_'.$Apache::lonnet::hostdom{$server}. - '_'.$server.'.'.'src="/adm/lonIcons/'.$icon.'";'); -} - 1; __END__