--- loncom/interface/lonsearchcat.pm 2002/06/24 16:45:22 1.128 +++ loncom/interface/lonsearchcat.pm 2006/02/07 19:46:30 1.252 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.128 2002/06/24 16:45:22 harris41 Exp $ +# $Id: lonsearchcat.pm,v 1.252 2006/02/07 19:46:30 albertel Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,14 +25,6 @@ # # http://www.lon-capa.org/ # -# YEAR=2001 -# 3/8, 3/12, 3/13, 3/14, 3/15, 3/19 Scott Harrison -# 3/20, 3/21, 3/22, 3/26, 3/27, 4/2, 8/15, 8/24, 8/25 Scott Harrison -# 10/12,10/14,10/15,10/16,11/28,11/29,12/10,12/12,12/16 Scott Harrison -# YEAR=2002 -# 1/17 Scott Harrison -# 6/17 Matthew Hall -# ############################################################################### ############################################################################### @@ -40,7 +32,7 @@ =head1 NAME -lonsearchcat +lonsearchcat - LONCAPA Search Interface =head1 SYNOPSIS @@ -56,7 +48,7 @@ described at http://www.lon-capa.org. lonsearchcat presents the user with an interface to search the LON-CAPA digital library. lonsearchcat also initiates the execution of a search by sending the search parameters to LON-CAPA servers. The progress of -search (on a server basis) is displayed to the user in a seperate window. +search (on a server basis) is displayed to the user in a separate window. =head1 Internals @@ -67,244 +59,752 @@ search (on a server basis) is displayed ############################################################################### ############################################################################### -############################################################################### -## ## -## ORGANIZATION OF THIS PERL MODULE ## -## ## -## 1. Modules used by this module ## -## 2. Variables used throughout the module ## -## 3. handler subroutine called via Apache and mod_perl ## -## 4. Other subroutines ## -## ## -############################################################################### - package Apache::lonsearchcat; -# ------------------------------------------------- modules used by this module use strict; -use Apache::Constants qw(:common); -use Apache::lonnet(); +use Apache::Constants qw(:common :http); +use Apache::lonnet; use Apache::File(); use CGI qw(:standard); use Text::Query; use GDBM_File; use Apache::loncommon(); - -# ---------------------------------------- variables used throughout the module +use Apache::lonmysql(); +use Apache::lonmeta; +use Apache::lonhtmlcommon; +use Apache::lonlocal; +use LONCAPA::lonmetadata(); +use HTML::Entities(); +use Parse::RecDescent; +use Apache::lonnavmaps; ###################################################################### ###################################################################### - -=pod - -=item Global variables - -=over 4 - -=item $closebutton - -button that closes the search window - -=item $importbutton - -button to take the selecte results and go to group sorting - -=item %hash - -The ubiquitous database hash - -=item $diropendb - -The full path to the (temporary) search database file. This is set and -used in &handler() and is also used in &output_results(). - -=back - -=cut - -###################################################################### -###################################################################### - -# -- 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 %hash; # database hash -my $diropendb = ""; # db file - -###################################################################### -###################################################################### - -=pod - -=item &handler() - main handler invoked by httpd child - -=item Variables - -=over 4 - -=item $hidden - -holds 'hidden' html forms - -=item $scrout - -string that holds portions of the screen output - -=back - -=cut +## +## 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, + "summary" => \&summary_view, + "fielded" => \&fielded_format_view, + "xml" => \&xml_sgml_view, + "compact" => \&compact_view); ###################################################################### ###################################################################### sub handler { my $r = shift; - untie %hash; +# &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 <body> tag. - $r->content_type('text/html'); + 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; - - my $domain = $r->dir_config('lonDefDomain'); - $diropendb= "/home/httpd/perl/tmp/".&Apache::lonnet::escape($domain). - "\_".&Apache::lonnet::escape($ENV{'user.name'})."_searchcat.db"; - + ## + ## 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', - 'reqinterface']); + ['catalogmode','launch','acts','mode','form','element','pause', + 'phase','persistent_db_id','table','start','show', + 'cleargroupsort','titleelement']); ## - ## Clear out old values from database + ## 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 ($ENV{'form.launch'} eq '1') { - if (tie(%hash,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { + 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 $html=&Apache::lonxml::xmlbegin(); + $r->print(<<END); +$html +<head> +<title>LON-CAPA Search Error</title></head> +$bodytag +We were unable to retrieve data describing your search. This is a serious +error and has been logged. Please alert your LON-CAPA administrator. +</body> +</html> +END + 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 'groupsearch'))) { + if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) { &start_fresh_session(); - untie %hash; + untie %groupsearch_db; + delete($env{'form.cleargroupsort'}); } else { - $r->print('<html><head></head><body>Unable to tie hash to db '. - 'file</body></html>'); + # This is a stupid error to give to the user. + # It really tells them nothing. + my $html=&Apache::lonxml::xmlbegin(); + $r->print($html.'<head></head>'.$bodytag. + 'Unable to tie hash to db file</body></html>'); return OK; } } ## - ## Produce some output, so people know it is working + ## Configure hidden fields ## - $r->print("\n"); - $r->rflush; + $hidden_fields = '<input type="hidden" name="persistent_db_id" value="'. + $env{'form.persistent_db_id'}.'" />'."\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 ## - my $hidden; # Holds 'hidden' html forms - if ($ENV{'form.catalogmode'} eq 'interactive') { - $hidden="<input type='hidden' name='catalogmode' value='interactive'>". - "\n"; - $closebutton="<input type='button' name='close' value='CLOSE' ". - "onClick='self.close()'>"."\n"; - } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { - $hidden=<<END; -<input type='hidden' name='catalogmode' value='groupsearch'> -END - $closebutton=<<END; -<input type='button' name='close' value='CLOSE' onClick='self.close()'> -END + if ($env{'form.catalogmode'} eq 'interactive') { + $closebutton="<input type='button' name='close' value='CLOSE' "; + if ($env{'form.phase'} =~ /(results|run_search)/) { + $closebutton .="onClick='parent.close()'"; + } else { + $closebutton .="onClick='self.close()'"; + } + $closebutton .=">\n"; + } elsif ($env{'form.catalogmode'} eq 'groupsearch') { + $closebutton="<input type='button' name='close' value='CLOSE' "; + if ($env{'form.phase'} =~ /(results|run_search)/) { + $closebutton .="onClick='parent.close()'"; + } else { + $closebutton .="onClick='self.close()'"; + } + $closebutton .= ">"; $importbutton=<<END; <input type='button' name='import' value='IMPORT' onClick='javascript:select_group()'> 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',}); } - $hidden .= <<END; -<input type='hidden' name='mode' value='$ENV{'form.mode'}'> -<input type='hidden' name='form' value='$ENV{'form.form'}'> -<input type='hidden' name='element' value='$ENV{'form.element'}'> -<input type='hidden' name='date' value='2'> -END ## - ## What are we doing? + ## Switch on the phase ## - if ($ENV{'form.basicsubmit'} eq 'SEARCH') { - # Perform basic search and give results - return &basicsearch($r,\%ENV,$hidden); - } elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') { - # Perform advanced search and give results - return &advancedsearch($r,\%ENV,$hidden); - } elsif ($ENV{'form.reqinterface'} eq 'advanced') { - # Output the advanced interface - $r->print(&advanced_search_form($closebutton,$hidden)); - return OK; - } else { - # Output normal search interface - $r->print(&basic_search_form($closebutton,$hidden)); + 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 $html=&Apache::lonxml::xmlbegin(); + $r->print(<<END); +$html +<head> +<title>Search Error</title></head> +$bodytag +Unable to create table in which to store search results. +The search has been aborted. +</body> +</html> +END + return OK; + } + delete($env{'form.launch'}); + if (! &make_form_data_persistent($r,$persistent_db_file)) { + my $html=&Apache::lonxml::xmlbegin(); + $r->print(<<END); +$html +<head> +<title>Search Error</title></head> +$bodytag +Unable to properly store search information. The search has been aborted. +</body> +</html> +END + 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 '<input type="hidden" name="'.$name.'" value="'.$value.'" />'.$/; +} + +###################################################################### +###################################################################### +## +## 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 $bodytag=&Apache::loncommon::bodytag('Course Search'); + my $pretty_search_string = '<b>'.$env{'form.courseexp'}.'</b>'; + 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").": <b>@New_Words</b>."; + } 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; + my $html=&Apache::lonxml::xmlbegin(); + $r->print($html.'<head><title>LON-CAPA Course Search</title></head>'. + $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr /><b>'.&mt('Course content').':</b><br />'); + $r->rflush(); +# ======================================================= Go through the course + my $c=$r->connection; + if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.".db", + &GDBM_READER(),0640)) { + foreach (sort(keys(%hash))) { + if ($c->aborted()) { last; } + if (($_=~/^src\_(.+)$/)) { + if ($hash{'randomout_'.$1} & !$env{'request.role.adv'}) { + next; + } + my $symb=&make_symb($1); + &checkonthis($r,$1,$hash{$_},0,&Apache::lonnet::gettitle($symb), + $fulltext,$symb,@allwords); + } + } + untie(%hash); + } + unless ($totalfound) { + $r->print('<p>'.&mt('No matches found in resources').'.</p>'); + } + +# Check discussions if requested + if ($discuss) { + my $totaldiscussions = 0; + $r->print('<br /><br /><b>'.&mt('Discussion postings').':</b><br />'); + my $navmap = Apache::lonnavmaps::navmap->new(); + my @allres=$navmap->retrieveResources(); + my %discussiontime = &Apache::lonnet::dump('discussiontimes', + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + foreach my $resource (@allres) { + my $result = ''; + my $applies = 0; + my $symb = $resource->symb(); + my $ressymb = $symb; + if ($symb =~ m#(___adm/\w+/\w+)/(\d+)/bulletinboard$#) { + $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard'; + unless ($ressymb =~ m#bulletin___\d+___adm/wrapper#) { + $ressymb=~s#(bulletin___\d+___)#$1adm/wrapper/#; + } + } + if (defined($discussiontime{$ressymb})) { + my %contrib = &Apache::lonnet::restore($ressymb,$env{'request.course.id'}, + $env{'course.'.$env{'request.course.id'}.'.domain'}, + $env{'course.'.$env{'request.course.id'}.'.num'}); + if ($contrib{'version'}) { + for (my $id=1;$id<=$contrib{'version'};$id++) { + unless (($contrib{'hidden'}=~/\.$id\./) || ($contrib{'deleted'}=~/\.$id\./)) { + if ($contrib{$id.':subject'}) { + $result .= $contrib{$id.':subject'}; + } + if ($contrib{$id.':message'}) { + $result .= $contrib{$id.':message'}; + } + if ($contrib{$id,':attachmenturl'}) { + if ($contrib{$id,':attachmenturl'} =~ m-/([^/]+)$-) { + $result .= $1; + } + } + $applies = &checkwords($result,$applies,@allwords); + } + } + } + } +# Does this discussion apply? + if ($applies) { + my ($map,$ind,$url)=&Apache::lonnet::decode_symb($ressymb); + my $disctype = &mt('resource'); + if ($url =~ m#/bulletinboard$#) { + if ($url =~m#^adm/wrapper/adm/.*/bulletinboard$#) { + $url =~s#^adm/wrapper##; + } + $disctype = &mt('bulletin board'); + } else { + $url = '/res/'.$url; + } + if ($url =~ /\?/) { + $url .= '&symb='; + } else { + $url .= '?symb='; + } + $url .= &Apache::lonnet::escape($resource->symb()); + my $title = $resource->compTitle(); + $r->print('<br /><a href="'.$url.'" target="cat">'. + ($title?$title:$url).'</a> - '.$disctype.'<br />'); + $totaldiscussions++; + } else { + $r->print(' .'); + } + } + unless ($totaldiscussions) { + $r->print('<p>'.&mt('No matches found in postings').'.</p>'); + } + } + +# =================================================== Done going through course + $r->print('</body></html>'); +} + +# =============================== 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('<br />'); + for (my $i=0;$i<=$level*5;$i++) { + $r->print(' '); + } + my $href=$url; + if ($hash{'encrypted_'.$id} && !$env{'request.role.adv'}) { + $href=&Apache::lonenc::encrypted($href) + .'?symb='.&Apache::lonenc::encrypted($symb); + } else { + $href.='?symb='.&Apache::lonnet::escape($symb); + } + $r->print('<a href="'.$href.'" target="cat">'.($title?$title:$url). + '</a><br />'); + $totalfound++; + } elsif ($fulltext) { + $r->print(' .'); + } + $r->rflush(); +# Check also the dependencies of this one + my $dependencies= + &Apache::lonnet::metadata($url,'dependencies'); + foreach (split(/\,/,$dependencies)) { + if (($_=~/^\/res\//) && (!$alreadyseen{$id})) { + &checkonthis($r,$id,$_,$level+1,'',$fulltext,undef,@allwords); + } + } +} + +sub checkwords { + my ($result,$applies,@allwords) = @_; + foreach (@allwords) { + if ($_=~/\w/) { + if ($result=~/$_/si) { + $applies++; + } + } + } + return $applies; +} + +sub untiehash { + if (tied(%hash)) { + untie(%hash); + } +} + +} # End of course search scoping + +sub search_html_header { + my $html=&Apache::lonxml::xmlbegin(); + my $Str = <<ENDHEADER; +$html +<head> +<title>The LearningOnline Network with CAPA</title> +</head> +ENDHEADER + return $Str; +} + ###################################################################### ###################################################################### =pod -=item &basic_search_form() +=item &print_basic_search_form() -Returns a scalar which holds html for the basic search form. +Prints the form for the basic search. Sorry the name is so cryptic. =cut ###################################################################### ###################################################################### - -sub basic_search_form{ - my ($closebutton,$hidden) = @_; - my $scrout=<<"ENDDOCUMENT"; -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -<script type="text/javascript"> - function openhelp(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=600,height=300'); - openhelpwin.focus(); - } -</script> -</head> -<body bgcolor="#FFFFFF"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> -<form method="post" action="/adm/searchcat"> -$hidden -<h3>Basic Search</h3> +sub print_basic_search_form { + my ($r,$closebutton,$hidden_fields) = @_; + my $result = ($env{'form.catalogmode'} ne 'groupsearch'); + my $bodytag=&Apache::loncommon::bodytag('Search'). + &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching','Search_Basic', + undef,undef, + $env{'form.catalogmode'} ne 'groupsearch'); + my $scrout = &search_html_header().$bodytag; + if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) { + # Define interface components + my $userelatedwords= '<label>'. + &mt('[_1] use related words', + &Apache::lonhtmlcommon::checkbox + ('related',$env{'form.related'},'related')).'</label>'; + my $onlysearchdomain='<label>'. + &mt('[_1] only search domain [_2]', + &Apache::lonhtmlcommon::checkbox('domains', + $env{'form.domains'}, + $r->dir_config('lonDefDomain') + ), + $r->dir_config('lonDefDomain') + ).'</label>'; + my $inclext= '<label>'. + &mt('[_1] include external resources', + &Apache::lonhtmlcommon::checkbox + ('inclext',$env{'form.inclext'})).'</label>'; + my $adv_search_link = + '<a href="/adm/searchcat?'. + 'phase=disp_adv&'. + 'catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}. + '">'.&mt('Advanced Search').'</a>'; + # + $scrout.='<form name="loncapa_search" method="post" '. + 'action="/adm/searchcat">'. + '<input type="hidden" name="phase" value="basic_search" />'. + $hidden_fields; + # + $scrout .= '<center>'.$/; + if ($env{'request.course.id'}) { + $scrout .= '<h1>'.&mt('LON-CAPA Catalog Search').'</h1>'; + } else { + # No need to tell them they are searching + $scrout.= ('<br />'x2); + } + $scrout.='<table>'. + '<tr><td align="center" valign="top">'. + &Apache::lonhtmlcommon::textbox + ('basicexp', + &HTML::Entities::encode($env{'form.basicexp'},'<>&"'),50 + ). + '<br />'. + '<font size="-1">'.&searchhelp().'</font>'.'</td>'. + '<td><font size="-1">'. + '<nobr>'.(' 'x3).$adv_search_link.'</nobr>'.'<br />'. + '<nobr>'.(' 'x1).$userelatedwords.'</nobr>'.'<br />'. + '<nobr>'.(' 'x1).$onlysearchdomain.'</nobr>'.'<br />'. + '<nobr>'.(' 'x1).$inclext.'</nobr>'.'<br />'. + '</font></td>'. + '</tr>'.$/; + # + $scrout .= '<tr><td align="center" colspan="2">'. + '<font size="-1">'. + '<input type="submit" name="basicsubmit" '. + 'value="'.&mt('Search').'" />'. + (' 'x2).$closebutton.(' 'x2). + &viewoptions(). + '</font>'. + '</td></tr>'.$/; + $scrout .= '</table>'.$/.'</center>'.'</form>'; + } + if ($env{'request.course.id'}) { + my %lt=&Apache::lonlocal::texthash('srch' => 'Search', + 'header' => 'Course Search', + 'note' => 'Enter terms or phrases, then press "Search" below', + 'use' => 'use related words', + 'full' =>'fulltext search (time consuming)', + 'disc' => 'search discussion postings (resources and bulletin boards)', + ); + $scrout.=(<<ENDCOURSESEARCH); +<form name="loncapa_search" method="post" action="/adm/searchcat"> +<center> +<hr /> +<h1>$lt{'header'}</h1> +<input type="hidden" name="phase" value="course_search" /> +$hidden_fields <p> -Enter terms or phrases separated by AND, OR, or NOT then press SEARCH below. +$lt{'note'}. </p> <p> <table> <tr><td> -ENDDOCUMENT - $scrout.=' '.&simpletextfield('basicexp',$ENV{'form.basicexp'},40). - ' '; -# $scrout.=&simplecheckbox('allversions',$ENV{'form.allversions'}); -# $scrout.='<font color="#800000">Search historic archives</font>'; - $scrout.=<<ENDDOCUMENT; -</td><td><a href="/adm/searchcat?reqinterface=advanced">Advanced Search</a></td></tr></table> -</p> -<p> - <input type="submit" name="basicsubmit" value='SEARCH' /> -$closebutton -<!-- basic view selection --> -<select name='basicviewselect'> -<option value='Detailed Citation View' selected="true"> -Detailed Citation View</option> -<option value='Summary View'>Summary View</option> -<option value='Fielded Format'>Fielded Format</option> -<option value='XML/SGML'>XML/SGML</option> -</select> -<!-- end of basic view selection --> -<input type="button" value="HELP" onClick="openhelp()" /> +ENDCOURSESEARCH + $scrout.=' '. + &Apache::lonhtmlcommon::textbox('courseexp', + $env{'form.courseexp'},40); + my $crscheckbox = + &Apache::lonhtmlcommon::checkbox('crsfulltext', + $env{'form.crsfulltext'}); + my $relcheckbox = + &Apache::lonhtmlcommon::checkbox('crsrelated', + $env{'form.crsrelated'}); + my $discheckbox = + &Apache::lonhtmlcommon::checkbox('crsdiscuss', + $env{'form.crsrelated'}); + $scrout.=(<<ENDENDCOURSE); +</td></tr> +<tr><td><label>$relcheckbox $lt{'use'}</label></td><td></td></tr> +<tr><td><label>$crscheckbox $lt{'full'}</label></td><td></td></tr> +<tr><td><label>$discheckbox $lt{'disc'}</label></td><td></td></tr> +</table><p> + <input type="submit" name="coursesubmit" value='$lt{'srch'}' /> </p> +</center> </form> +ENDENDCOURSE + } + $scrout.=(<<ENDDOCUMENT); </body> </html> ENDDOCUMENT - return $scrout; + $r->print($scrout); + return; } ###################################################################### ###################################################################### @@ -313,153 +813,229 @@ ENDDOCUMENT =item &advanced_search_form() -Returns a scalar which holds html for the advanced search form. +Prints the advanced search form. =cut ###################################################################### ###################################################################### - -sub advanced_search_form{ - my ($closebutton,$hidden) = @_; - my $scrout=<<"ENDHEADER"; -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -<script type="text/javascript"> - function openhelp(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=600,height=300'); - openhelpwin.focus(); - } -</script> -</head> -<body bgcolor="#FFFFFF"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> -<form method="post" action="/adm/searchcat"> -$hidden -<hr /> -<h3>Advanced Search</h3> -ENDHEADER - $scrout.=&searchphrasefield('Limit by title','title', - $ENV{'form.title'}); - $scrout.=&searchphrasefield('Limit by author','author', - $ENV{'form.author'}); - $scrout.=&searchphrasefield('Limit by subject','subject', - $ENV{'form.subject'}); - $scrout.=&searchphrasefield('Limit by keywords','keywords', - $ENV{'form.keywords'}); - $scrout.=&searchphrasefield('Limit by URL','url', - $ENV{'form.url'}); -# $scrout.=&searchphrasefield('Limit by version','version', -# $ENV{'form.version'}); - $scrout.=&searchphrasefield('Limit by notes','notes', - $ENV{'form.notes'}); - $scrout.=&searchphrasefield('Limit by abstract','abstract', - $ENV{'form.abstract'}); - $ENV{'form.mime'}='any' unless length($ENV{'form.mime'}); - $scrout.=&selectbox('Limit by MIME type','mime', - $ENV{'form.mime'}, - 'any','Any type', - \&{Apache::loncommon::filedescriptionex}, - (&Apache::loncommon::fileextensions)); - $ENV{'form.language'}='any' unless length($ENV{'form.language'}); - $scrout.=&selectbox('Limit by language','language', - $ENV{'form.language'},'any','Any Language', - \&{Apache::loncommon::languagedescription}, - (&Apache::loncommon::languageids), - ); -# ------------------------------------------------ Compute date selection boxes - $scrout.=<<CREATIONDATESTART; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY CREATION DATE RANGE:</b> -</font> -<br /> -between: -CREATIONDATESTART - $scrout.=&dateboxes('creationdatestart',1,1,1976, - $ENV{'form.creationdatestart_month'}, - $ENV{'form.creationdatestart_day'}, - $ENV{'form.creationdatestart_year'}, - ); - $scrout.="and:\n"; - $scrout.=&dateboxes('creationdateend',12,31,2051, - $ENV{'form.creationdateend_month'}, - $ENV{'form.creationdateend_day'}, - $ENV{'form.creationdateend_year'}, - ); - $scrout.="</p>"; - $scrout.=<<LASTREVISIONDATESTART; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY LAST REVISION DATE RANGE: -</b></font> -<br />between: -LASTREVISIONDATESTART - $scrout.=&dateboxes('lastrevisiondatestart',1,1,1976, - $ENV{'form.lastrevisiondatestart_month'}, - $ENV{'form.lastrevisiondatestart_day'}, - $ENV{'form.lastrevisiondatestart_year'}, - ); - $scrout.=<<LASTREVISIONDATEEND; -and: -LASTREVISIONDATEEND - $scrout.=&dateboxes('lastrevisiondateend',12,31,2051, - $ENV{'form.lastrevisiondateend_month'}, - $ENV{'form.lastrevisiondateend_day'}, - $ENV{'form.lastrevisiondateend_year'}, - ); - $scrout.='</p>'; - $scrout.=&searchphrasefield('Limit by publisher/owner','owner', - $ENV{'form.owner'}); - $ENV{'form.copyright'}='any' unless length($ENV{'form.copyright'}); - $scrout.=&selectbox('Limit by copyright/distribution','copyright', - $ENV{'form.copyright'}, - 'any','Any copyright/distribution', - \&{Apache::loncommon::copyrightdescription}, - (&Apache::loncommon::copyrightids), - ); -# ------------------------------------------- Compute customized metadata field - $scrout.=<<CUSTOMMETADATA; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY SPECIAL METADATA FIELDS:</b> -</font> -For resource-specific metadata, enter in an expression in the form of -<i>key</i>=<i>value</i> separated by operators such as AND, OR or NOT.<br /> -<b>Example:</b> grandmother=75 OR grandfather=85 -<br /> -CUSTOMMETADATA - $scrout.=&simpletextfield('custommetadata',$ENV{'form.custommetadata'}); - $scrout.=<<CUSTOMSHOW; +sub print_advanced_search_form{ + my ($r,$closebutton,$hidden_fields) = @_; + my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search'). + &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching', + 'Search_Advanced', + undef,undef, + $env{'form.catalogmode'} ne 'groupsearch'); + my %lt=&Apache::lonlocal::texthash('srch' => 'Search', + 'reset' => 'Reset', + 'help' => 'Help'); + my $advanced_buttons=<<"END"; +<input type="submit" name="advancedsubmit" value='$lt{"srch"}' /> +<input type="reset" name="reset" value='$lt{"reset"}' /> +$closebutton +END + my $scrout=&search_html_header(); + $scrout .= <<"ENDHEADER"; +$bodytag +<form method="post" action="/adm/searchcat" name="advsearch"> <p> -<font color="#800000" face="helvetica"><b>SHOW SPECIAL METADATA FIELDS:</b> -</font> -Enter in a space-separated list of special metadata fields to show -in a fielded listing for each record result. -<br /> -CUSTOMSHOW - $scrout.=&simpletextfield('customshow',$ENV{'form.customshow'}); +$advanced_buttons +ENDHEADER + $scrout.=(' 'x2).&viewoptions().'</p>'.$hidden_fields. + '<input type="hidden" name="phase" value="adv_search" />'; + my %fields=&Apache::lonmeta::fieldnames(); + # + $scrout .= '<h3>'.&mt('Standard Metadata').'</h3>'; + $scrout .= "<table>\n"; + $scrout .= '<tr><td> </td><td colspan="2"><font size="-1">'. + (' 'x2).&searchhelp()."</font></td></tr>\n"; + my %related_word_search = + ('title' => 1, + 'author' => 0, + 'owner' => 0, + 'authorspace' => 0, + 'modifyinguser'=> 0, + 'keywords' => 1, + 'notes' => 1, + 'abstract' => 1, + 'standards'=> 1, + 'mime' => 1, + 'subject' => 1, + ); + # + foreach my $field ('title','author','subject','owner','authorspace', + 'modifyinguser','keywords','notes','abstract', + 'standards','mime') { + $scrout.='<tr><td align="right">'.&titlefield($fields{$field}).'</td><td>'. + &Apache::lonmeta::prettyinput($field, + $env{'form.'.$field}, + $field, + 'advsearch', + $related_word_search{$field}, + '</td><td align="left">', + $env{'form.'.$field.'_related'}, + 50); + if ($related_word_search{$field}) { + $scrout .= 'related words'; + } else { + $scrout .= '</td><td> '; + } + $scrout .= '</td></tr>'.$/; + } + foreach my $field ('lowestgradelevel','highestgradelevel') { + $scrout.='<tr>'. + '<td align="right">'.&titlefield($fields{$field}).'</td>'. + '<td colspan="2">'. + &Apache::lonmeta::prettyinput($field, + $env{'form.'.$field}, + $field, + 'advsearch', + 0). + '</td></tr>'.$/; + } + $scrout.='<tr><td align="right">'. + &titlefield(&mt('MIME Type Category')).'</td><td colspan="2">'. + &Apache::loncommon::filecategoryselect('category', + $env{'form.category'}). + '</td></tr>'.$/; + $scrout.='<tr><td align="right" valign="top">'. + &titlefield(&mt('Domains')).'</td><td colspan="2">'. + &Apache::loncommon::domain_select('domains', + $env{'form.domains'},1). + '<br /><label>'. + &mt('[_1] include external resources', + &Apache::lonhtmlcommon::checkbox + ('inclext',$env{'form.inclext'})).'</label></td></tr>'.$/; + # + # Misc metadata + $scrout.='<tr><td align="right" valign="top">'. + &titlefield(&mt('Copyright/Distribution')).'</td><td colspan="2">'. + &Apache::lonmeta::selectbox('copyright', + $env{'form.copyright'}, + \&Apache::loncommon::copyrightdescription, + ( undef, + &Apache::loncommon::copyrightids) + ).'</td></tr>'.$/; + $scrout.='<tr><td align="right" valign="top">'. + &titlefield(&mt('Language')).'</td><td colspan="2">'. + &Apache::lonmeta::selectbox('language', + $env{'form.language'}, + \&Apache::loncommon::languagedescription, + ('any',&Apache::loncommon::languageids) + ).'</td></tr>'; + $scrout .= "</table>\n"; + # + # Dynamic metadata + $scrout .= '<h3>'.&mt('Problem Statistics').'</h3>'; + $scrout .= "<table>\n"; + $scrout .= '<tr><td> </td><td align="center">'.&mt('Minimum').'</td>'. + '<td align="center">'.&mt('Maximum').'</td></tr>'."\n"; + foreach my $statistic + ({ name=>'count', + description=>'Network-wide number of accesses (hits)',}, + { name=>'stdno', + description=> + 'Total number of students who have worked on this problem',}, + { name => 'avetries', + description=>'Average number of tries till solved',}, + { name => 'difficulty', + description=>'Degree of difficulty',}, + { name => 'disc', + description=>'Degree of discrimination'}) { + $scrout .= '<tr><td align="right">'. + &titlefield(&mt($statistic->{'description'})). + '</td><td align="center">'. + '<input type="text" name="'.$statistic->{'name'}.'_min" '. + 'value="" size="6" />'. + '</td><td align="center">'. + '<input type="text" name="'.$statistic->{'name'}.'_max" '. + 'value="" size="6" />'. + '</td></tr>'.$/; + } + $scrout .= "</table>\n"; + $scrout .= '<h3>'.&mt('Evaluation Data').'</h3>'; + $scrout .= "<table>\n"; + $scrout .= '<tr><td> </td><td align="center">'.&mt('Minimum').'</td>'. + '<td align="center">'.&mt('Maximum').'</td></tr>'."\n"; + foreach my $evaluation + ( { name => 'clear', + description => 'Material presented in clear way'}, + { name =>'depth', + description => 'Material covered with sufficient depth'}, + { name => 'helpful', + description => 'Material is helpful'}, + { name => 'correct', + description => 'Material appears to be correct'}, + { name => 'technical', + description => 'Resource is technically correct'}){ + $scrout .= '<tr><td align="right">'. + &titlefield(&mt($evaluation->{'description'})). + '</td><td align="center">'. + '<input type="text" name="'.$evaluation->{'name'}.'_min" '. + 'value="" size="6" />'. + '</td><td align="center">'. + '<input type="text" name="'.$evaluation->{'name'}.'_max" '. + 'value="" size="6" />'. + '</td></tr>'.$/; + } + $scrout .= "</table>\n"; + # + # Creation/Modification date limits + $scrout .= '<h3>'.&mt('Creation and Modification dates').'</h3>'; + $scrout .= "\n<table>\n"; + my $cafter = + &Apache::lonhtmlcommon::date_setter('advsearch', # formname + 'creationdate1', # fieldname + 0, # current value + '', # special + 1, # includeempty + '', # state + 1, # no_hh_mm_ss + ); + my $cbefore = + &Apache::lonhtmlcommon::date_setter('advsearch', # formname + 'creationdate2', # fieldname + 0, # current value + '', # special + 1, # includeempty + '', # state + 1, # no_hh_mm_ss + ); + $scrout .= &mt('<tr><td align="right">Created between</td>'. + '<td>[_1]</td></tr>'. + '<tr><td align="right">and </td>'. + '<td>[_2]</td></tr>',$cafter,$cbefore); + my $lafter = + &Apache::lonhtmlcommon::date_setter('advsearch', + 'revisiondate1', + 0, # current value + '', # special + 1, # includeempty + '', # state + 1, # no_hh_mm_ss + ); + my $lbefore = + &Apache::lonhtmlcommon::date_setter('advsearch', + 'revisiondate2', + 0, # current value + '', # special + 1, # includeempty + '', # state + 1, # no_hh_mm_ss + ); + $scrout .= &mt('<tr><td align="right">Last modified between </td>'. + '<td>[_1]</td></tr>'. + '<tr><td align="right">and</td>'. + '<td>[_2]</td></tr>',$lafter,$lbefore); + $scrout.="</table>\n"; $scrout.=<<ENDDOCUMENT; -<p> -<input type="submit" name="advancedsubmit" value='SEARCH' /> -<input type="reset" name="reset" value='RESET' /> -$closebutton -<!-- advance view select --> -<select name='advancedviewselect'> -<option value='Detailed Citation View' selected="true"> -Detailed Citation View</option> -<option value='Summary View'>Summary View</option> -<option value='Fielded Format'>Fielded Format</option> -<option value='XML/SGML'>XML/SGML</option> -</select> -<!-- end of advanced view select --> -<input type="button" value="HELP" onClick="openhelp()" /> -</p> +$advanced_buttons </form> </body> </html> ENDDOCUMENT - return $scrout; + $r->print($scrout); + return; } ###################################################################### @@ -467,167 +1043,258 @@ ENDDOCUMENT =pod -=item &make_persistent() +=item &titlefield() + +Inputs: title text -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. +Outputs: titletext with font wrapper =cut ###################################################################### ###################################################################### - -sub make_persistent { - my $persistent=''; - foreach (keys %ENV) { - if (/^form\./ && !/submit/) { - my $name=$_; - my $key=$name; - $ENV{$key}=~s/\'//g; # do not mess with html field syntax - $name=~s/^form\.//; - $persistent.=<<END; -<input type="hidden" name="$name" value="$ENV{$key}" /> -END - } - } - return $persistent; +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'); + return $desc{$code}; +} ###################################################################### ###################################################################### =pod -=item HTML form building functions +=item viewoptions() -=over 4 +Inputs: none -=item &simpletextfield() +Outputs: text for box with view options -Inputs: $name,$value,$size +=cut -Returns a text input field with the given name, value, and size. -If size is not specified, a value of 20 is used. +###################################################################### +###################################################################### +sub viewoptions { + my $scrout; + if (! defined($env{'form.viewselect'})) { + $env{'form.viewselect'}='detailed'; + } + $scrout.=&Apache::lonmeta::selectbox('viewselect', + $env{'form.viewselect'}, + \&viewoptiontext, + sort(keys(%Views))); + $scrout.= ' '; + my $countselect = &Apache::lonmeta::selectbox('show', + $env{'form.show'}, + undef, + (10,20,50,100,1000,10000)); + $scrout .= (' 'x2).&mt('[_1] Records per Page',$countselect). + '</nobr>'.$/; + return $scrout; +} -=item &simplecheckbox() +###################################################################### +###################################################################### -Inputs: $name,$value +=pod -Returns a simple check box with the given $name. -If $value eq 'on' the box is checked. +=item searchhelp() -=item &searchphrasefield() +Inputs: none -Inputs: $title,$name,$value +Outputs: return little blurb on how to enter searches -Returns html for a title line and an input field for entering search terms. -the instructions "Enter terms or phrases separated by search operators such -as AND, OR, or NOT." are given following the title. The entry field (which -is where the $name and $value are used) is an 80 column simpletextfield. +=cut -=item &dateboxes() +###################################################################### +###################################################################### +sub searchhelp { + return &mt('Enter words and quoted phrases'); +} -Returns html selection form elements for the specification of -the day, month, and year. +###################################################################### +###################################################################### + +=pod -=item &selectbox() +=item &get_persistent_form_data() -Returns html selection form. +Inputs: filename of database -=back +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 { + &Apache::lonnet::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; +} -sub simpletextfield { - my ($name,$value,$size)=@_; - $size = 20 if (! defined($size)); - return '<input type="text" name="'.$name. - '" size="'.$size.'" value="'.$value.'" />'; -} - -sub simplecheckbox { - my ($name,$value)=@_; - my $checked=''; - $checked="CHECKED" if $value eq 'on'; - return '<input type="checkbox" name="'.$name.'" '. $checked . ' />'; -} - -sub searchphrasefield { - my ($title,$name,$value)=@_; - my $instruction=<<END; -Enter terms or phrases separated by search operators such as AND, OR, or NOT. -END - my $uctitle=uc($title); - return "\n". - '<p><font color="#800000" face="helvetica"><b>'.$uctitle.':</b>'. - "</FONT> $instruction<br />".&simpletextfield($name,$value,80); +###################################################################### +###################################################################### + +=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; } -sub dateboxes { - my ($name,$defaultmonth,$defaultday,$defaultyear, - $currentmonth,$currentday,$currentyear)=@_; - ($defaultmonth,$defaultday,$defaultyear)=('','',''); - # - # Day - my $day=<<END; -<select name="${name}_day"> -<option value='$defaultday'> </option> -END - for (my $i = 1; $i<=31; $i++) { - $day.="<option value=\"$i\">$i</option>\n"; +###################################################################### +###################################################################### + +=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 { &Apache::lonnet::escape($_); } @values ); + $persistent_db{$name} = $store; } - $day.="</select>\n"; - $day=~s/(\"$currentday\")/$1 SELECTED/ if length($currentday); - # - # Month - my $month=<<END; -<select name="${name}_month"> -<option value='$defaultmonth'> </option> -END - my $i = 1; - foreach (qw/January February March April May June - July August September October November December /){ - $month .="<option value=\"$i\">$_</option>\n"; - $i++; - } - $month.="</select>\n"; - $month=~s/(\"$currentmonth\")/$1 SELECTED/ if length($currentmonth); - # - # Year (obviously) - my $year=<<END; -<select name="${name}_year"> -<option value='$defaultyear'> </option> -END - my $maxyear = 2051; - for (my $i = 1976; $i<=$maxyear; $i++) { - $year.="<option value=\"$i\">$i</option>\n"; - } - $year.="</select>\n"; - $year=~s/(\"$currentyear\")/$1 SELECTED/ if length($currentyear); - return "$month$day$year"; -} - -sub selectbox { - my ($title,$name,$value,$anyvalue,$anytag,$functionref,@idlist)=@_; - my $uctitle=uc($title); - my $selout="\n".'<p><font color="#800000" face="helvetica">'. - '<b>'.$uctitle.':</b></font><br /><select name="'.$name.'">'; - foreach ($anyvalue,@idlist) { - $selout.='<option value="'.$_.'"'; - if ($_ eq $value and !/^any$/) { - $selout.=' selected >'.&{$functionref}($_).'</option>'; - } - elsif ($_ eq $value and /^$anyvalue$/) { - $selout.=' selected >'.$anytag.'</option>'; - } - else {$selout.='>'.&{$functionref}($_).'</option>';} + 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 $selout.'</select>'; + return &make_persistent(\%save,$filename); } ###################################################################### @@ -635,129 +1302,299 @@ sub selectbox { =pod -=item &advancedsearch() +=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. -Parse advanced search results. +=item $libraries_to_query Reference to array of domains to search. + +=back =cut ###################################################################### ###################################################################### -sub advancedsearch { - my ($r,$envhash,$hidden)=@_; - my %ENV=%{$envhash}; +sub parse_advanced_search { + my ($r,$closebutton,$hidden_fields)=@_; + my @BasicFields = ('title','author','subject','keywords','url','version', + 'notes','abstract','extension','owner','authorspace', +# 'custommetadata','customshow', + 'modifyinguser','standards','mime'); + my @StatsFields = &statfields(); + my @EvalFields = &evalfields(); my $fillflag=0; + my $pretty_search_string = ""; # Clean up fields for safety - for my $field ('title','author','subject','keywords','url','version', - 'creationdatestart_month','creationdatestart_day', + for my $field (@BasicFields, + 'creationdatestart_month','creationdatestart_day', 'creationdatestart_year','creationdateend_month', 'creationdateend_day','creationdateend_year', 'lastrevisiondatestart_month','lastrevisiondatestart_day', 'lastrevisiondatestart_year','lastrevisiondateend_month', - 'lastrevisiondateend_day','lastrevisiondateend_year', - 'notes','abstract','mime','language','owner', - 'custommetadata','customshow') { - $ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g; + 'lastrevisiondateend_day','lastrevisiondateend_year') { + $env{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\']//g; } foreach ('mode','form','element') { # is this required? Hmmm. - next unless (exists($ENV{"form.$_"})); - $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"}); - $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; - } + next if (! exists($env{'form.'.$_})); + $env{'form.'.$_}=&Apache::lonnet::unescape($env{'form.'.$_}); + $env{'form.'.$_}=~s/[^\w\/\s\(\)\=\-\"\']//g; + } + # Preprocess the category form element. + $env{'form.category'} = 'any' if (! defined($env{'form.category'}) || + ref($env{'form.category'})); + # # Check to see if enough information was filled in - for my $field ('title','author','subject','keywords','url','version', - 'notes','abstract','mime','language','owner', - 'custommetadata') { - if (&filled($ENV{"form.$field"})) { + foreach my $field (@BasicFields) { + if (&filled($env{'form.'.$field})) { $fillflag++; } } - unless ($fillflag) { - &output_blank_field_error($r); - return OK; + foreach my $field (@StatsFields,@EvalFields) { + if (&filled($env{'form.'.$field.'_max'})) { + $fillflag++; + } + if (&filled($env{'form.'.$field.'_min'})) { + $fillflag++; + } + } + + for my $field ('lowestgradelevel','highestgradelevel') { + if ( $env{'form.'.$field} =~ /^\d+$/ && + $env{'form.'.$field} > 0) { + $fillflag++; + } + } + if (! $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 = '<font color="#800000" face="helvetica">'; # Evaluate logical expression AND/OR/NOT phrase fields. - foreach my $field ('title','author','subject','notes','abstract','url', - 'keywords','version','owner') { - if ($ENV{'form.'.$field}) { - push @queries,&build_SQL_query($field,$ENV{'form.'.$field}); - } + foreach my $field (@BasicFields) { + next if (!defined($env{'form.'.$field}) || $env{'form.'.$field} eq ''); + my ($error,$SQLQuery) = + &process_phrase_input($env{'form.'.$field}, + $env{'form.'.$field.'_related'},$field); + if (defined($error)) { + &output_unparsed_phrase_error($r,$closebutton,'phase=disp_adv', + $hidden_fields,$field); + return; + } else { + $pretty_search_string .= + $font.$field.'</font>: '.$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 .= '<br />'; + push (@queries,$SQLQuery); + } } - # Evaluate option lists - if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') { - push @queries,"(language like \"$ENV{'form.language'}\")"; + # + # Make the 'mime' from 'form.category' and 'form.extension' + # + my $searchphrase; + if (exists($env{'form.category'}) && + $env{'form.category'} !~ /^\s*$/ && + $env{'form.category'} ne 'any') { + my @extensions = &Apache::loncommon::filecategorytypes + ($env{'form.category'}); + if (scalar(@extensions) > 0) { + $searchphrase = join(' OR ',@extensions); + } } - if ($ENV{'form.mime'} and $ENV{'form.mime'} ne 'any') { - push @queries,"(mime like \"$ENV{'form.mime'}\")"; + if (defined($searchphrase)) { + my ($error,$SQLsearch) = &process_phrase_input($searchphrase,0,'mime'); + push @queries,$SQLsearch; + $pretty_search_string .=$font.'mime</font> contains <b>'. + $searchphrase.'</b><br />'; } - if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') { - push @queries,"(copyright like \"$ENV{'form.copyright'}\")"; + # + # Evaluate option lists + if ($env{'form.lowestgradelevel'} && + $env{'form.lowestgradelevel'} ne '0' && + $env{'form.lowestgradelevel'} =~ /^\d+$/) { + push(@queries, + '(lowestgradelevel>='.$env{'form.lowestgradelevel'}.')'); + $pretty_search_string.="lowestgradelevel>=". + $env{'form.lowestgradelevel'}."<br />\n"; + } + if ($env{'form.highestgradelevel'} && + $env{'form.highestgradelevel'} ne '0' && + $env{'form.highestgradelevel'} =~ /^\d+$/) { + push(@queries, + '(highestgradelevel<='.$env{'form.highestgradelevel'}.')'); + $pretty_search_string.="highestgradelevel<=". + $env{'form.highestgradelevel'}."<br />\n"; + } + if ($env{'form.language'} and $env{'form.language'} ne 'any') { + push @queries,"(language like \"$env{'form.language'}\")"; + $pretty_search_string.=$font."language</font>= ". + &Apache::loncommon::languagedescription($env{'form.language'}). + "<br />\n"; + } + if ($env{'form.copyright'} and $env{'form.copyright'} ne 'any') { + push @queries,"(copyright like \"$env{'form.copyright'}\")"; + $pretty_search_string.=$font."copyright</font> = ". + &Apache::loncommon::copyrightdescription($env{'form.copyright'}). + "<br />\n"; } - # Evaluate date windows - my $datequery=&build_date_queries( - $ENV{'form.creationdatestart_month'}, - $ENV{'form.creationdatestart_day'}, - $ENV{'form.creationdatestart_year'}, - $ENV{'form.creationdateend_month'}, - $ENV{'form.creationdateend_day'}, - $ENV{'form.creationdateend_year'}, - $ENV{'form.lastrevisiondatestart_month'}, - $ENV{'form.lastrevisiondatestart_day'}, - $ENV{'form.lastrevisiondatestart_year'}, - $ENV{'form.lastrevisiondateend_month'}, - $ENV{'form.lastrevisiondateend_day'}, - $ENV{'form.lastrevisiondateend_year'}, - ); - # Test to see if date windows are legitimate - if ($datequery=~/^Incorrect/) { - &output_date_error($r,$datequery); - return OK; + # + # Statistics + foreach my $field (@StatsFields,@EvalFields) { + my ($min,$max); + if (exists($env{'form.'.$field.'_min'}) && + $env{'form.'.$field.'_min'} ne '') { + $min = $env{'form.'.$field.'_min'}; + } + if (exists($env{'form.'.$field.'_max'}) && + $env{'form.'.$field.'_max'} ne '') { + $max = $env{'form.'.$field.'_max'}; + } + next if (! defined($max) && ! defined($min)); + if (defined($min) && defined($max)) { + ($min,$max) = sort {$a <=>$b} ($min,$max); + } + if (defined($min) && $min =~ /^(\d+\.\d+|\d+|\.\d+)$/) { + push(@queries,'('.$field.'>'.$min.')'); + $pretty_search_string.=$font.$field.'</font>>'.$min.'<br />'; + } + if (defined($max) && $max =~ /^(\d+\.\d+|\d+|\.\d+)$/) { + push(@queries,'('.$field.'<'.$max.')'); + $pretty_search_string.=$font.$field.'</font><'.$max.'<br />'; + } } - elsif ($datequery) { + # + # Evaluate date windows + my $cafter = + &Apache::lonhtmlcommon::get_date_from_form('creationdate1'); + my $cbefore = + &Apache::lonhtmlcommon::get_date_from_form('creationdate2'); + if ($cafter > $cbefore) { + my $tmp = $cafter; + $cafter = $cbefore; + $cbefore = $tmp; + } + my $mafter = + &Apache::lonhtmlcommon::get_date_from_form('revisiondate1'); + my $mbefore = + &Apache::lonhtmlcommon::get_date_from_form('revisiondate2'); + if ($mafter > $mbefore) { + my $tmp = $mafter; + $mafter = $mbefore; + $mbefore = $tmp; + } + my ($datequery,$error,$prettydate)=&build_date_queries($cafter,$cbefore, + $mafter,$mbefore); + if (defined($error)) { + &output_date_error($r,$error,$closebutton,$hidden_fields); + } elsif (defined($datequery)) { + # Here is where you would set up pretty_search_string to output + # date query information. + $pretty_search_string .= '<br />'.$prettydate.'<br />'; push @queries,$datequery; } + # # Process form information for custom metadata querying - my $customquery=''; - if ($ENV{'form.custommetadata'}) { - $customquery=&build_custommetadata_query('custommetadata', - $ENV{'form.custommetadata'}); + my $customquery=undef; + ## + ## The custom metadata search was removed q long time ago mostly + ## because I was unable to figureout exactly how it worked and could + ## not imagine people actually using it. MH + ## + # if ($env{'form.custommetadata'}) { + # $pretty_search_string .=$font."Custom Metadata Search</font>: <b>". + # $env{'form.custommetadata'}."</b><br />\n"; + # $customquery=&build_custommetadata_query('custommetadata', + # $env{'form.custommetadata'}); + # } + my $customshow=undef; + # if ($env{'form.customshow'}) { + # $pretty_search_string .=$font."Custom Metadata Display</font>: <b>". + # $env{'form.customshow'}."</b><br />\n"; + # $customshow=$env{'form.customshow'}; + # $customshow=~s/[^\w\s]//g; + # my @fields=split(/\s+/,$customshow); + # $customshow=join(" ",@fields); + # } + ## + ## Deal with restrictions to given domains + ## + my ($libraries_to_query,$pretty_domains_string,$domain_sql_restriction) = + &parse_domain_restrictions(); + if ((defined($domain_sql_restriction)) && ($domain_sql_restriction ne '')) { + push(@queries,$domain_sql_restriction); + $pretty_search_string .= $pretty_domains_string."<br />\n"; } - my $customshow=''; - if ($ENV{'form.customshow'}) { - $customshow=$ENV{'form.customshow'}; - $customshow=~s/[^\w\s]//g; - my @fields=split(/\s+/,$customshow); - $customshow=join(" ",@fields); - } - # Send query statements over the network to be processed by either the SQL - # database or a recursive scheme of 'grep'-like actions (for custom - # metadata). + # if (@queries) { - $query=join(" AND ",@queries); - $query="select * from metadata where $query"; - my $reply; # reply hash reference - unless ($customquery or $customshow) { - $reply=&Apache::lonnet::metadata_query($query); - } - else { - $reply=&Apache::lonnet::metadata_query($query, - $customquery,$customshow); - } - &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden); - return OK; + $query="SELECT * FROM metadata WHERE (".join(") AND (",@queries).')'; } elsif ($customquery) { - my $reply; # reply hash reference - $reply=&Apache::lonnet::metadata_query('', - $customquery,$customshow); - &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden); - return OK; + $query = ''; + } + #&Apache::lonnet::logthis('advanced query = '.$/.$query); + return ($query,$customquery,$customshow,$libraries_to_query, + $pretty_search_string); +} + +sub parse_domain_restrictions { + my $libraries_to_query = undef; + # $env{'form.domains'} can be either a scalar or an array reference. + # We need an array. + if (! exists($env{'form.domains'}) || $env{'form.domains'} eq '') { + return (undef,'',undef); } - # should not get to this point - return 'Error. Should not have gone to this point.'; + my @allowed_domains = &Apache::loncommon::get_env_multiple('form.domains'); + # + my %domain_hash = (); + my $pretty_domains_string; + my $domain_sql_restriction; + foreach (@allowed_domains) { + $domain_hash{$_}++; + } + if ($domain_hash{'any'}) { + $pretty_domains_string = "In all LON-CAPA domains."; + $domain_sql_restriction = undef; + } else { + if (@allowed_domains > 1) { + $pretty_domains_string = "In LON-CAPA domains:"; + } else { + $pretty_domains_string = "In LON-CAPA domain "; + } + $domain_sql_restriction = + '(domain="'.join('" OR domain="',@allowed_domains).'")'; + foreach (sort @allowed_domains) { + $pretty_domains_string .= "<b>".$_."</b> "; + } + foreach (keys(%Apache::lonnet::libserv)) { + if (exists($domain_hash{$Apache::lonnet::hostdom{$_}})) { + push @$libraries_to_query,$_; + } + } + } + return ($libraries_to_query, + $pretty_domains_string, + $domain_sql_restriction); } ###################################################################### @@ -765,80 +1602,277 @@ sub advancedsearch { =pod -=item &basicsearch() +=item &parse_basic_search() -Parse basic search form. +Parse the basic search form and return a scalar containing an sql query. =cut ###################################################################### ###################################################################### -sub basicsearch { - my ($r,$envhash,$hidden)=@_; - my %ENV=%{$envhash}; +sub parse_basic_search { + my ($r,$closebutton)=@_; + # # Clean up fields for safety for my $field ('basicexp') { - $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g; + $env{"form.$field"}=~s/[^\w\s\'\"\!\(\)\-]//g; } foreach ('mode','form','element') { # is this required? Hmmm. - next unless (exists($ENV{"form.$_"})); - $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"}); - $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; + next unless (exists($env{"form.$_"})); + $env{"form.$_"}=&Apache::lonnet::unescape($env{"form.$_"}); + $env{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; } - - # Check to see if enough is filled in - unless (&filled($ENV{'form.basicexp'})) { - &output_blank_field_error($r); + my ($libraries_to_query,$pretty_domains_string,$domain_sql_restriction) = + &parse_domain_restrictions(); + # + # Check to see if enough of a query is filled in + my $search_string = $env{'form.basicexp'}; + if (! &filled($search_string)) { + &output_blank_field_error($r,$closebutton,'phase=disp_basic'); return OK; } + my $pretty_search_string=$search_string; + my @Queries; + my $searchfield = 'concat_ws(" ",'.join(',', + ('title','author','subject', + 'notes','abstract','keywords') + ).')'; + my ($error,$SQLQuery) = &process_phrase_input($search_string, + $env{'form.related'}, + $searchfield); + if ($error) { + &output_unparsed_phrase_error($r,$closebutton,'phase=disp_basic', + '','basicexp'); + return; + } + push(@Queries,$SQLQuery); + if (defined($domain_sql_restriction) && $domain_sql_restriction ne '') { + push(@Queries,$domain_sql_restriction); + } + #foreach my $q (@Queries) { + # &Apache::lonnet::logthis(' '.$q); + #} + my $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries); + # + if (defined($pretty_domains_string) && $pretty_domains_string ne '') { + $pretty_search_string .= ' '.$pretty_domains_string; + } + $pretty_search_string .= "<br />\n"; + $pretty_search_string =~ s:^<br /> and ::; + #&Apache::lonnet::logthis('simple search final query = '.$/.$final_query); + return ($final_query,$pretty_search_string, + $libraries_to_query); +} - # Build SQL query string based on form page - my $query=''; - my $concatarg=join('," ",', - ('title', 'author', 'subject', 'notes', 'abstract', - 'keywords')); - $concatarg='title' if $ENV{'form.titleonly'}; - - $query=&build_SQL_query('concat('.$concatarg.')',$ENV{'form.'.'basicexp'}); - - # Get reply (either a hash reference to filehandles or bad connection) -# &Apache::lonnet::logthis("metadata query started:".time); - my $reply=&Apache::lonnet::metadata_query('select * from metadata where '.$query); -# &Apache::lonnet::logthis("metadata query finished:".time); - # Output search results - &output_results('Basic',$r,$envhash,$query,$reply,$hidden); +############################################################### +############################################################### - return OK; +my @Phrases; + +sub concat { + my ($item) = @_; + my $results = ''; + foreach (@$item) { + if (ref($_) eq 'ARRAY') { + $results .= join(' ',@$_); + } + } + return $results; } +sub process_phrase_input { + my ($phrase,$related,$field)=@_; + #&Apache::lonnet::logthis('phrase = :'.$phrase.':'); + my $grammar = <<'ENDGRAMMAR'; + searchphrase: + expression /^\Z/ { + # &Apache::lonsearchcat::print_item(\@item,0); + [@item]; + } + expression: + phrase(s) { + [@item]; + } + phrase: + orword { + [@item]; + } + | andword { + [@item]; + } + | minusword { + unshift(@::Phrases,$item[1]->[0]); + unshift(@::Phrases,$item[1]->[1]); + [@item]; + } + | word { + unshift(@::Phrases,$item[1]); + [@item]; + } + # + orword: + word 'OR' phrase { + unshift(@::Phrases,'OR'); + unshift(@::Phrases,$item[1]); + [@item]; + } + | word 'or' phrase { + unshift(@::Phrases,'OR'); + unshift(@::Phrases,$item[1]); + [@item]; + } + | minusword 'OR' phrase { + unshift(@::Phrases,'OR'); + unshift(@::Phrases,$item[1]->[0]); + unshift(@::Phrases,$item[1]->[1]); + [@item]; + } + | minusword 'or' phrase { + unshift(@::Phrases,'OR'); + unshift(@::Phrases,$item[1]->[0]); + unshift(@::Phrases,$item[1]->[1]); + [@item]; + } + andword: + word phrase { + unshift(@::Phrases,'AND'); + unshift(@::Phrases,$item[1]); + [@item]; + } + | minusword phrase { + unshift(@::Phrases,'AND'); + unshift(@::Phrases,$item[1]->[0]); + unshift(@::Phrases,$item[1]->[1]); + [@item]; + } + # + minusword: + '-' word { + [$item[2],'NOT']; + } + word: + "'" term(s) "'" { + &Apache::lonsearchcat::concat(\@item); + } + | '"' term(s) '"' { + &Apache::lonsearchcat::concat(\@item); + } + | term { + $item[1]; + } + term: + /[\w\Q:!@#$%^&*()+_=|{}<>,.;\\\/?\E]+/ { + $item[1]; + } +ENDGRAMMAR + # + # The end result of parsing the phrase with the grammar is an array + # @::Phrases. + # $phrase = "gene splicing" or cat -> "gene splicing","OR","cat" + # $phrase = "genetic engineering" -dna -> + # "genetic engineering","AND","NOT","dna" + # $phrase = cat or dog -poodle -> "cat","OR","dog","AND","NOT","poodle" + undef(@::Phrases); + my $p = new Parse::RecDescent($grammar); + if (! defined($p->searchphrase($phrase))) { + &Apache::lonnet::logthis('lonsearchcat:unable to process:'.$phrase); + return 'Unable to process phrase '.$phrase; + } + # + # Go through the phrases and make sense of them. + # Apply modifiers NOT OR and AND to the phrases. + my @NewPhrases; + while(@::Phrases) { + my $phrase = shift(@::Phrases); + # &Apache::lonnet::logthis('phrase = '.$phrase); + my $phrasedata; + if ($phrase =~ /^(NOT|OR|AND)$/) { + if ($phrase eq 'OR') { + $phrasedata->{'or'}++; + if (! @::Phrases) { $phrasedata = undef; last; } + $phrase = shift(@::Phrases); + } elsif ($phrase eq 'AND') { + $phrasedata->{'and'}++; + if (! @::Phrases) { $phrasedata = undef; last; } + $phrase = shift(@::Phrases); + } + if ($phrase eq 'NOT') { + $phrasedata->{'negate'}++; + if (! @::Phrases) { $phrasedata = undef; last; } + $phrase = shift(@::Phrases); + } + } + $phrasedata->{'phrase'} = $phrase; + if ($related) { + my @NewWords; + (undef,@NewWords) = &related_version($phrasedata->{'phrase'}); + $phrasedata->{'related_words'} = \@NewWords; + } + push(@NewPhrases,$phrasedata); + } + # + # Actually build the sql query from the phrases + my $SQLQuery; + foreach my $phrase (@NewPhrases) { + my $query; + if ($phrase->{'negate'}) { + $query .= $field.' NOT LIKE "%'.$phrase->{'phrase'}.'%"'; + } else { + $query .= $field.' LIKE "%'.$phrase->{'phrase'}.'%"'; + } + foreach my $related (@{$phrase->{'related_words'}}) { + if ($phrase->{'negate'}) { + $query .= ' AND '.$field.' NOT LIKE "%'.$related.'%"'; + } else { + $query .= ' OR '.$field.' LIKE "%'.$related.'%"'; + } + } + if ($SQLQuery) { + if ($phrase->{'or'}) { + $SQLQuery .= ' OR ('.$query.')'; + } else { + $SQLQuery .= ' AND ('.$query.')'; + } + } else { + $SQLQuery = '('.$query.')'; + } + } + # + # &Apache::lonnet::logthis("SQLQuery = $SQLQuery"); + # + return undef,$SQLQuery; +} ###################################################################### ###################################################################### =pod -=item &build_SQL_query() +=item &related_version() -Builds a SQL query string from a logical expression with AND/OR keywords -using Text::Query and &recursive_SQL_query_builder() +Modifies an input string to include related words. Words in the string +are replaced with parenthesized lists of 'OR'd words. For example +"torque" is replaced with "(torque OR word1 OR word2 OR ...)". + +Note: Using this twice on a string is probably silly. =cut ###################################################################### ###################################################################### -sub build_SQL_query { - my ($field_name,$logic_statement)=@_; - my $q=new Text::Query('abc', - -parse => 'Text::Query::ParseAdvanced', - -build => 'Text::Query::Build'); - $q->prepare($logic_statement); - my $matchexp=${$q}{'matchexp'}; chomp $matchexp; - my $sql_query=&recursive_SQL_query_build($field_name,$matchexp); - return $sql_query; +sub related_version { + my ($word) = @_; + return (undef) if (lc($word) =~ /\b(or|and|not)\b/); + my @Words = &Apache::loncommon::get_related_words($word); + # Only use 4 related words + @Words = ($#Words>4? @Words[0..4] : @Words); + my $result = join " OR ", ($word,@Words); + return $result,sort(@Words); } + ###################################################################### ###################################################################### @@ -872,349 +1906,1001 @@ sub build_custommetadata_query { return $matchexp; } + ###################################################################### ###################################################################### =pod -=item &recursive_SQL_query_build() +=item &build_date_queries() -Recursively constructs an SQL query. Takes as input $dkey and $pattern. +Builds a SQL logic query to check time/date entries. +Also reports errors (check for /^Incorrect/). =cut ###################################################################### ###################################################################### -sub recursive_SQL_query_build { - my ($dkey,$pattern)=@_; - my @matches=($pattern=~/(\[[^\]|\[]*\])/g); - return $pattern unless @matches; - foreach my $match (@matches) { - $match=~/\[ (\w+)\s(.*) \]/; - my ($key,$value)=($1,$2); - my $replacement=''; - if ($key eq 'literal') { - $replacement="($dkey like \"\%$value\%\")"; - } - elsif ($key eq 'not') { - $value=~s/like/not like/; -# $replacement="($dkey not like $value)"; - $replacement="$value"; - } - elsif ($key eq 'and') { - $value=~/(.*[\"|\)]) ([|\(|\^].*)/; - $replacement="($1 AND $2)"; - } - elsif ($key eq 'or') { - $value=~/(.*[\"|\)]) ([|\(|\^].*)/; - $replacement="($1 OR $2)"; - } - substr($pattern, - index($pattern,$match), - length($match), - $replacement - ); +sub build_date_queries { + my ($cafter,$cbefore,$mafter,$mbefore) = @_; + my ($result,$error,$pretty_string); + # + # Verify the input + if (! defined($cafter) && ! defined($cbefore) && + ! defined($mafter) && ! defined($mbefore)) { + # This is an okay situation, so return undef for the error + return (undef,undef,undef); + } + if ((defined($cafter) && ! defined($cbefore)) || + (defined($cbefore) && ! defined($cafter))) { + # This is bad, so let them know + $error = &mt('Incorrect entry for the creation date. '. + 'You must specify both the beginning and ending dates.'); + } + if (! defined($error) && + ((defined($mafter) && ! defined($mbefore)) || + (defined($mbefore) && ! defined($mafter)))) { + # This is also bad, so let them know + $error = &mt('Incorrect entry for the last revision date. '. + 'You must specify both the beginning and ending dates.'); + } + if (! defined($error)) { + # + # Build the queries + my @queries; + if (defined($cbefore) && defined($cafter)) { + my (undef,undef,undef,$caday,$camon,$cayear) = localtime($cafter); + my (undef,undef,undef,$cbday,$cbmon,$cbyear) = localtime($cbefore); + # Correct for year being relative to 1900 + $cayear+=1900; $cbyear+=1900; + my $cquery= + '(creationdate BETWEEN '. + "'".$cayear.'-'.$camon.'-'.$caday."'". + ' AND '. + "'".$cbyear.'-'.$cbmon.'-'.$cbday." 23:59:59')"; + $pretty_string .= '<br />' if (defined($pretty_string)); + $pretty_string .= + &mt('created between [_1] and [_2]', + &Apache::lonlocal::locallocaltime($cafter), + &Apache::lonlocal::locallocaltime($cbefore+24*60*60-1)); + push(@queries,$cquery); + $pretty_string =~ s/ 00:00:00//g; + } + if (defined($mbefore) && defined($mafter)) { + my (undef,undef,undef,$maday,$mamon,$mayear) = localtime($mafter); + my (undef,undef,undef,$mbday,$mbmon,$mbyear) = localtime($mbefore); + # Correct for year being relative to 1900 + $mayear+=1900; $mbyear+=1900; + my $mquery= + '(lastrevisiondate BETWEEN '. + "'".$mayear.'-'.$mamon.'-'.$maday."'". + ' AND '. + "'".$mbyear.'-'.$mbmon.'-'.$mbday." 23:59:59')"; + push(@queries,$mquery); + $pretty_string .= '<br />' if (defined($pretty_string)); + $pretty_string .= + &mt('last revised between [_1] and [_2]', + &Apache::lonlocal::locallocaltime($mafter), + &Apache::lonlocal::locallocaltime($mbefore+24*60*60-1)); + $pretty_string =~ s/ 00:00:00//g; + } + if (@queries) { + $result .= join(" AND ",@queries); + } } - &recursive_SQL_query_build($dkey,$pattern); + return ($result,$error,$pretty_string); } ###################################################################### ###################################################################### -=pod +=pod -=item &build_date_queries() +=item ©right_check() -Builds a SQL logic query to check time/date entries. -Also reports errors (check for /^Incorrect/). +Inputs: $Metadata, a hash pointer of metadata for a resource. + +Returns: 1 if the resource is available to the user making the query, + 0 otherwise. =cut ###################################################################### ###################################################################### -sub build_date_queries { - my ($cmonth1,$cday1,$cyear1,$cmonth2,$cday2,$cyear2, - $lmonth1,$lday1,$lyear1,$lmonth2,$lday2,$lyear2)=@_; - my @queries; - if ($cmonth1 or $cday1 or $cyear1 or $cmonth2 or $cday2 or $cyear2) { - unless ($cmonth1 and $cday1 and $cyear1 and - $cmonth2 and $cday2 and $cyear2) { - return "Incorrect entry for the creation date. You must specify ". - "a starting month, day, and year and an ending month, ". - "day, and year."; - } - my $cnumeric1=sprintf("%d%2d%2d",$cyear1,$cmonth1,$cday1); - $cnumeric1+=0; - my $cnumeric2=sprintf("%d%2d%2d",$cyear2,$cmonth2,$cday2); - $cnumeric2+=0; - if ($cnumeric1>$cnumeric2) { - return "Incorrect entry for the creation date. The starting ". - "date must occur before the ending date."; - } - my $cquery="(creationdate BETWEEN '$cyear1-$cmonth1-$cday1' AND '". - "$cyear2-$cmonth2-$cday2 23:59:59')"; - push @queries,$cquery; - } - if ($lmonth1 or $lday1 or $lyear1 or $lmonth2 or $lday2 or $lyear2) { - unless ($lmonth1 and $lday1 and $lyear1 and - $lmonth2 and $lday2 and $lyear2) { - return "Incorrect entry for the last revision date. You must ". - "specify a starting month, day, and year and an ending ". - "month, day, and year."; - } - my $lnumeric1=sprintf("%d%2d%2d",$lyear1,$lmonth1,$lday1); - $lnumeric1+=0; - my $lnumeric2=sprintf("%d%2d%2d",$lyear2,$lmonth2,$lday2); - $lnumeric2+=0; - if ($lnumeric1>$lnumeric2) { - return "Incorrect entry for the last revision date. The ". - "starting date must occur before the ending date."; - } - my $lquery="(lastrevisiondate BETWEEN '$lyear1-$lmonth1-$lday1' AND '". - "$lyear2-$lmonth2-$lday2 23:59:59')"; - push @queries,$lquery; +sub copyright_check { + my $Metadata = shift; + # Check copyright tags and skip results the user cannot use + my (undef,undef,$resdom,$resname) = split('/', + $Metadata->{'url'}); + # Check for priv + if (($Metadata->{'copyright'} eq 'priv') && + (($env{'user.name'} ne $resname) && + ($env{'user.domain'} ne $resdom))) { + return 0; } - if (@queries) { - return join(" AND ",@queries); + # Check for domain + if (($Metadata->{'copyright'} eq 'domain') && + ($env{'user.domain'} ne $resdom)) { + return 0; } - return ''; + return 1; } ###################################################################### ###################################################################### -=pod +=pod + +=item &ensure_db_and_table() -=item &output_results() +Ensure we can get lonmysql to connect to the database and the table we +need exists. -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.) +Inputs: $r, table id + +Returns: undef on error, 1 if the table exists. =cut ###################################################################### ###################################################################### -sub output_results { -# &Apache::lonnet::logthis("output_results:".time); - my $fnum; # search result counter - my ($mode,$r,$envhash,$query,$replyref,$hidden)=@_; - my %ENV=%{$envhash}; - my %rhash=%{$replyref}; - my $compiledresult=''; - my $timeremain=300; # (seconds) - my $elapsetime=0; - my $resultflag=0; - my $tflag=1; - # - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); - # spit out the generic header - $r->print(&search_results_header()); - $r->rflush(); - # begin showing the cataloged results - $r->print(<<CATALOGBEGIN); +sub ensure_db_and_table { + my ($r,$table) = @_; + ## + ## Sanity check the table id. + ## + if (! defined($table) || $table eq '' || $table =~ /\D/ ) { + $r->print("Unable to retrieve search results. ". + "Unable to determine the table results were stored in. ". + "</body></html>"); + 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 stored. </body></html>"); + &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.</form></body></html>"); + &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 $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1). + &Apache::lonhtmlcommon::breadcrumbs + (undef,'Searching','Searching',undef,undef, + $env{'form.catalogmode'} ne 'groupsearch'); + + ## + 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.</form></body></html>"); + &Apache::lonnet::logthis("lonmysql was unable to determine the number". + " of rows in table ".$table); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + return; + } + my $result; + my $html=&Apache::lonxml::xmlbegin(); + $result.=<<END; +$html +<head> +<script> + function change_sort() { + var newloc = "/adm/searchcat?phase=results"; + newloc += "&persistent_db_id=$env{'form.persistent_db_id'}"; + newloc += "&sortby="; + newloc += document.forms.statusform.elements.sortby.value; + parent.resultsframe.location= newloc; + } +</script> +<title>Results</title> </head> -<body bgcolor="#ffffff"> -<img align=right src=/adm/lonIcons/lonlogos.gif> -<h1>Search Catalog</h1> -CATALOGBEGIN - $r->print(<<CATALOGCONTROLS); -<form name='results' method="post" action="/adm/searchcat"> -$hidden -<input type='hidden' name='acts' value='' /> -<input type='button' value='Revise search request' -onClick='this.form.submit();' /> -$importbutton -$closebutton -$persistent -<hr /> -<h3>Search Query</h3> -CATALOGCONTROLS +$bodytag +<form name="statusform" action="" method="post"> +<input type="hidden" name="Queue" value="" /> +END + +#<h2>Sort Results</h2> +#Sort by: <select size="1" name="sortby" onchange="javascript:change_sort();"> +# $env{'form.sortby'} = 'id' if (! defined($env{'form.sortby'})); +# foreach (keys(%SortableFields)) { +# $result.="<option name=\"$_\""; +# if ($_ eq $env{'form.sortby'}) { +# $result.=" selected "; +# } +# $result.=" >$SortableFields{$_}</option>\n"; +# } +# $result.="</select>\n"; + my $revise = &revise_button(); + $result.=<<END; +<p> +There are $total_results matches to your query. $revise +</p><p> +Search:$pretty_query_string +</p> +</form> +</body> +</html> +END + $r->print($result); + return; +} + +##################################################################### +##################################################################### + +=pod + +=item MySQL Table Description + +MySQL table creation requires a precise description of the data to be +stored. The use of the correct types to hold data is vital to efficient +storage and quick retrieval of records. The columns must be described in +the following format: + +=cut + +##################################################################### +##################################################################### +# +# These should probably be scoped but I don't have time right now... +# +my @Datatypes; +my @Fullindicies; + +###################################################################### +###################################################################### + +=pod + +=item &create_results_table() + +Creates the table of search results by calling lonmysql. Stores the +table id in $env{'form.table'} + +Inputs: none. + +Returns: the identifier of the table on success, undef on error. + +=cut + +###################################################################### +###################################################################### +sub set_up_table_structure { + my ($datatypes,$fullindicies) = + &LONCAPA::lonmetadata::describe_metadata_storage(); + # Copy the table description before modifying it... + @Datatypes = @{$datatypes}; + unshift(@Datatypes,{name => 'id', + type => 'MEDIUMINT', + restrictions => 'UNSIGNED NOT NULL', + primary_key => 'yes', + auto_inc => 'yes' }); + @Fullindicies = @{$fullindicies}; + return; +} + +sub create_results_table { + &set_up_table_structure(); + my $table = &Apache::lonmysql::create_table + ( { columns => \@Datatypes, + FULLTEXT => [{'columns' => \@Fullindicies},], + } ); + if (defined($table)) { + $env{'form.table'} = $table; + return $table; + } + return undef; # Error... +} + +###################################################################### +###################################################################### + +=pod + +=item Search Status update functions + +Each of the following functions changes the values of one of the +input fields used to display the search status to the user. The names +should be explanatory. + +Inputs: Apache request handler ($r), text to display. + +Returns: Nothing. + +=over 4 + +=item &update_count_status() + +=item &update_status() + +=item &update_seconds() + +=back + +=cut + +###################################################################### +###################################################################### +sub update_count_status { + my ($r,$text) = @_; + $text =~ s/\'/\\\'/g; + $r->print + ("<script>document.statusform.count.value = ' $text'</script>\n"); + $r->rflush(); +} + +sub update_status { + my ($r,$text) = @_; + $text =~ s/\'/\\\'/g; + $r->print + ("<script>document.statusform.status.value = ' $text'</script>\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("<script>". + "document.statusform.seconds.value = '$time'". + "</script>\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{<input type="button" value="Revise search" name="revise"} . + qq{ onClick="parent.location='$newloc';" /> }; + 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; # - # Remind them what they searched for + # Print run_search header # - if ($mode eq 'Basic') { - $r->print('<p><b>Basic search:</b> '.$ENV{'form.basicexp'}.'</p>'); - } elsif ($mode eq 'Advanced') { - $r->print('<p><b>Advanced search</b> '.$query.'</p>'); + my $html=&Apache::lonxml::xmlbegin(); + $r->print(<<END); +$html +<head> +<title>Search Status</title></head> +$bodytag +<form name="statusform" action="" method="post"> +<input type="hidden" name="Queue" value="" /> +END + # Remove leading and trailing <br /> + $pretty_string =~ s:^\s*<br />::i; + $pretty_string =~ s:(<br />)*\s*$::im; + my @Lines = split("<br />",$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 '<br />',(@Lines[0..2],'....<br />'); } - $r->print('<h3>Search Results</h3>'); + $r->print(&mt("Search: [_1]",$pretty_string)); $r->rflush(); # - # make the pop-up window for status + # 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; # - $r->print(&make_popwin(%rhash)); - $r->rflush(); - ## - ## Prepare for the main loop below + # 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.</body></html>"); + return; + } + my $table_status = &Apache::lonmysql::check_table($table); + if (! defined($table_status)) { + $r->print("Unable to determine status of table.</body></html>"); + &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.</body></html>"); + return; + } ## - 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 %hash if (keys %hash); + ## Prepare for the big loop. + my $hitcountsum; + my $server; + my $status; + my $revise = &revise_button(); + $r->print(<<END); +<table> +<tr><th>Status</th><th>Total Matches</th><th>Time Remaining</th><th></th></tr> +<tr> +<td><input type="text" name="status" value="" size="50" /></td> +<td><input type="text" name="count" value="" size="10" /></td> +<td><input type="text" name="seconds" value="" size="8" /></td> +<td>$revise</td> +</tr> +</table> +</form> +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); # - if (! tie(%hash,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { - $r->print('<html><head></head><body>Unable to tie hash to db '. - 'file</body></html>'); + # 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 { - 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); - $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; - } + # 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; } - if ($compiledresult or $servercount!=$servernum) { - $compiledresult.="<hr align='left' width='200' noshade />"; + 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; } - $compiledresult.="\n<p>\n"; - if ($ENV{'form.catalogmode'} eq 'interactive') { - my $titleesc=$Fields{'title'}; - $titleesc=~s/\'/\\'/; # ' - $compiledresult.=<<END if ($ENV{'form.catalogmode'} eq 'interactive'); + # 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("</body></html>"); +# if ($env{'form.catalogmode'} ne 'groupsearch') { + $r->print("<script>". + "window.location='/adm/searchcat?". + "phase=sort&". + "persistent_db_id=$env{'form.persistent_db_id'}';". + "</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 + my $buttons = '<input type="submit" name="prev" value="'.&mt('Prev').'" '; + $buttons .= '/>'; + $buttons .= ' 'x3; + $buttons .= '<input type="submit" name="reload" '. + 'value="'.&mt('Reload').'" />'; + $buttons .= ' 'x3; + $buttons .= '<input type="submit" name="next" value="'.&mt('Next').'" '; + $buttons .= '/>'; + return $buttons; +} + +###################################################################### +###################################################################### + +=pod + +=item &display_results() + +Prints the results out for selection and perusal. + +=cut + +###################################################################### +###################################################################### +sub display_results { + my ($r,$importbutton,$closebutton,$diropendb) = @_; + my $connection = $r->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.</form></body></html>'); + $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.</form></body></html>"); + &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'; + } + } + 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 + ('<table width="100%"><tr><td width="25%" align="right">'. + '<nobr>'.$sortform.'</nobr>'. + '</td><td width="25%" align="right">'. + &prev_next_buttons($min,$env{'form.show'},$total_results). + '</td><td align="right">'. + &viewoptions().'</td></tr></table>' + ); + if ($total_results == 0) { + $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="2">'. + '<h3>'.&mt('There are currently no results').'.</h3>'. + "</form></body></html>"); + return; + } else { + $r->print('<center>'. + mt('Results [_1] to [_2] out of [_3]', + $min,$max,$total_results). + "</center>\n"); + } + ## + ## Get results from MySQL table + my $sort_command = 'id>='.$min.' AND id<='.$max; + my $order; + if (exists($env{'form.sortorder'})) { + if ($env{'form.sortorder'} eq 'asc') { + $order = 'ASC'; + } elsif ($env{'form.sortorder'} eq 'desc') { + $order = 'DESC'; + } else { + $order = ''; + } + } else { + $order = ''; + } + if ($env{'form.sortfield'} ne 'default' && + exists($sort_fields{$env{'form.sortfield'}})) { + $sort_command = $env{'form.sortfield'}.' IS NOT NULL '. + 'ORDER BY '.$env{'form.sortfield'}.' '.$order. + ' LIMIT '.($min-1).','.($max-$min+1); + } + my @Results = &Apache::lonmysql::get_rows($table,$sort_command); + ## + ## Loop through the results and output them. + foreach my $row (@Results) { + if ($connection->aborted()) { + &cleanup(); + return; + } + my %Fields = %{&parse_row(@$row)}; + my $output="<p>\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 + ('<center>'. + &prev_next_buttons($min,$env{'form.show'},$total_results, + "table=".$env{'form.table'}. + "&phase=results". + "&persistent_db_id=". + $env{'form.persistent_db_id'}) + ."</center>\n" + ); + } + $r->print("</form></body></html>"); + $r->rflush(); + untie %groupsearch_db if (tied(%groupsearch_db)); + return; +} + +###################################################################### +###################################################################### + +=pod + +=item &catalogmode_output($title,$url,$fnum,$checkbox_num) + +Returns html needed for the various catalog modes. Gets inputs from +$env{'form.catalogmode'}. Stores data in %groupsearch_db. + +=cut + +###################################################################### +###################################################################### +sub catalogmode_output { + my $output = ''; + my ($title,$url,$fnum,$checkbox_num) = @_; + if ($env{'form.catalogmode'} eq 'interactive') { + $title=~ s/\'/\\\'/g; + if ($env{'form.catalogmode'} eq 'interactive') { + $output.=<<END <font size='-1'><INPUT TYPE="button" NAME="returnvalues" VALUE="SELECT" -onClick="javascript:select_data('$titleesc','$Fields{'url'}')"> +onClick="javascript:select_data('$title','$url')"> </font> -<br /> END - } - if ($ENV{'form.catalogmode'} eq 'groupsearch') { - $fnum+=0; - $hash{"pre_${fnum}_link"}=$Fields{'url'}; - $hash{"pre_${fnum}_title"}=$Fields{'title'}; - $compiledresult.=<<END; + } + } elsif ($env{'form.catalogmode'} eq 'groupsearch') { + $groupsearch_db{"pre_${fnum}_link"}=$url; + $groupsearch_db{"pre_${fnum}_title"}=$title; + $output.=<<END; <font size='-1'> <input type="checkbox" name="returnvalues" value="SELECT" -onClick="javascript:queue($fnum)" /> +onClick="javascript:queue($checkbox_num,$fnum)" /> </font> -<br /> END -# <input type="hidden" name="title$fnum" value="$title" /> -# <input type="hidden" name="url$fnum" value="$url" /> - $fnum++; - } - my $viewselect; - if ($mode eq 'Basic') { - $viewselect=$ENV{'form.basicviewselect'}; - } - elsif ($mode eq 'Advanced') { - $viewselect=$ENV{'form.advancedviewselect'}; - } - if ($viewselect eq 'Detailed Citation View') { - $compiledresult.=&detailed_citation_view - (%Fields, hostname => $rkey ); - } - elsif ($viewselect eq 'Summary View') { - $compiledresult.=&summary_view - (%Fields, hostname => $rkey ); - } - elsif ($viewselect eq 'Fielded Format') { - $compiledresult.=&fielded_format_view - (%Fields, hostname => $rkey ); - } - elsif ($viewselect eq 'XML/SGML') { - $compiledresult.=&xml_sgml_view - (%Fields, hostname => $rkey ); - } - } - untie %hash; - } - if ($compiledresult) { - $resultflag=1; - $r->print($compiledresult); - } - my $percent=sprintf('%3.0f',($servercount/$servernum*100)); - } # End of foreach loop over servers remaining - } # End of big loop - while($serversleft && $timeremain) - unless ($resultflag) { - $r->print("\nThere were no results that matched your query\n"); } -# $r->print('<script type="text/javascript">'.'popwin.close()</script>'."\n"); $r->rflush(); - $r->print("</body>\n</html>\n"); - return; + return $output; +} +###################################################################### +###################################################################### + +=pod + +=item &parse_row() + +Parse a row returned from the database. + +=cut + +###################################################################### +###################################################################### +sub parse_row { + my @Row = @_; + my %Fields; + if (! scalar(@Datatypes)) { + &set_up_table_structure(); + } + for (my $i=0;$i<=$#Row;$i++) { + $Fields{$Datatypes[$i]->{'name'}}=&Apache::lonnet::unescape($Row[$i]); + } + $Fields{'language'} = + &Apache::loncommon::languagedescription($Fields{'language'}); + $Fields{'copyrighttag'} = + &Apache::loncommon::copyrightdescription($Fields{'copyright'}); + $Fields{'mimetag'} = + &Apache::loncommon::filedescription($Fields{'mime'}); + return \%Fields; } ########################################################### @@ -1225,13 +2911,10 @@ END =item &parse_raw_result() Takes a line from the file of results and parse it. Returns a hash -with keys for the following fields: -'title', 'author', 'subject', 'url', 'keywords', 'version', 'notes', -'abstract', 'mime', 'lang', 'owner', 'copyright', 'creationdate', -'lastrevisiondate'. +with keys according to column labels In addition, the following tags are set by calling the appropriate -lonnet function: 'language', 'cprtag', 'mimetag'. +lonnet function: 'language', 'copyrighttag', 'mimetag'. The 'title' field is set to "Untitled" if the title field is blank. @@ -1243,59 +2926,11 @@ The 'title' field is set to "Untitled" i ########################################################### sub parse_raw_result { my ($result,$hostname) = @_; - # Check for a comma - if it is there then we do not need to unescape the - # string. There seems to be some kind of problem with some items in - # the database - the entire string gets sent out unescaped...? - unless ($result =~ /,/) { - $result = &Apache::lonnet::unescape($result); - } - my @fields=map { - &Apache::lonnet::unescape($_); - } (split(/\,/,$result)); - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$abstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright)=@fields; - my %Fields = - ( title => &Apache::lonnet::unescape($title), - author => &Apache::lonnet::unescape($author), - subject => &Apache::lonnet::unescape($subject), - url => &Apache::lonnet::unescape($url), - keywords => &Apache::lonnet::unescape($keywords), - version => &Apache::lonnet::unescape($version), - notes => &Apache::lonnet::unescape($notes), - abstract => &Apache::lonnet::unescape($abstract), - mime => &Apache::lonnet::unescape($mime), - lang => &Apache::lonnet::unescape($lang), - owner => &Apache::lonnet::unescape($owner), - copyright => &Apache::lonnet::unescape($copyright), - creationdate => &Apache::lonnet::unescape($creationdate), - lastrevisiondate => &Apache::lonnet::unescape($lastrevisiondate) - ); - $Fields{'language'} = - &Apache::loncommon::languagedescription($Fields{'lang'}); - $Fields{'copyrighttag'} = - &Apache::loncommon::copyrightdescription($Fields{'copyright'}); - $Fields{'mimetag'} = - &Apache::loncommon::filedescription($Fields{'mime'}); - # Put spaces in the keyword list, if needed. - $Fields{'keywords'}=~ s/,([A-z])/, $1/g; - if ($Fields{'title'}=~ /^\s*$/ ) { - $Fields{'title'}='Untitled'; - } - unless ($ENV{'user.adv'}) { - $Fields{'keywords'} = '- not displayed -'; - $Fields{'notes'} = '- not displayed -'; - $Fields{'abstract'} = '- not displayed -'; - $Fields{'subject'} = '- not displayed -'; - } - if (length($Fields{'abstract'})>200) { - $Fields{'abstract'} = - substr($Fields{'abstract'},0,200).'...'; - } - if (length($Fields{'keywords'})>200) { - $Fields{'keywords'} = - substr($Fields{'keywords'},0,200).'...'; - } + # conclude from self to others regarding fields + my %Fields=&LONCAPA::lonmetadata::metadata_col_to_hash + (map { + &Apache::lonnet::unescape($_); + } (split(/\,/,$result)) ); return %Fields; } @@ -1315,8 +2950,8 @@ sub handle_custom_fields { my $customshow=''; my $extrashow=''; my @customfields; - if ($ENV{'form.customshow'}) { - $customshow=$ENV{'form.customshow'}; + if ($env{'form.customshow'}) { + $customshow=$env{'form.customshow'}; $customshow=~s/[^\w\s]//g; my @fields=map { "<font color=\"#008000\">$_:</font><!-- $_ -->"; @@ -1345,12 +2980,20 @@ sub handle_custom_fields { =pod -=item &search_results_header +=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. -Output the proper javascript code to deal with different calling modes. +=over 4 + +=item $mode is either (at this writing) 'Basic' or 'Advanced' + +=back -Takes inputs directly from from %ENV. The following environment variables -are checked: +The following environment variables are checked: =over 4 @@ -1364,8 +3007,16 @@ 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 @@ -1373,50 +3024,64 @@ Checked for existance & 'edit' mode. ###################################################################### ###################################################################### sub search_results_header { + my ($importbutton,$closebutton) = @_; + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); my $result = ''; # output beginning of search page - $result.=<<BEGINNING; -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -BEGINNING # 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') { + if ($env{'form.catalogmode'} eq 'interactive'){ + if (! exists($env{'form.mode'}) || $env{'form.mode'} ne 'edit') { $result.=<<SCRIPT; <script type="text/javascript"> function select_data(title,url) { changeTitle(title); changeURL(url); - self.close(); + parent.close(); } function changeTitle(val) { - if (opener.inf.document.forms.resinfo.elements.t) { - opener.inf.document.forms.resinfo.elements.t.value=val; + if (parent.opener.inf.document.forms.resinfo.elements.t) { + parent.opener.inf.document.forms.resinfo.elements.t.value=val; } } function changeURL(val) { - if (opener.inf.document.forms.resinfo.elements.u) { - opener.inf.document.forms.resinfo.elements.u.value=val; + if (parent.opener.inf.document.forms.resinfo.elements.u) { + parent.opener.inf.document.forms.resinfo.elements.u.value=val; } } </script> SCRIPT - } elsif ($ENV{'form.mode'} eq 'edit') { - my $form = $ENV{'form.form'}; - my $element = $ENV{'form.element'}; + } 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=<<END; +function changeTitle(val) { + if (parent.targetwin.document) { + parent.targetwin.document.forms["$form"].elements["$titleelement"].value=val; + } else { + var url = 'forms[\"$form\"].elements[\"$titleelement\"].value'; + alert("Unable to transfer data to "+url); + } +} +END + } + $result.=<<SCRIPT; <script type="text/javascript"> function select_data(title,url) { changeURL(url); - self.close(); -} -function changeTitle(val) { + changeTitle(title); + parent.close(); } +$changetitle function changeURL(val) { - if (window.opener.document) { - window.opener.document.forms["$form"].elements["$element"].value=val; + if (parent.targetwin.document) { + parent.targetwin.document.forms["$form"].elements["$element"].value=val; } else { var url = 'forms[\"$form\"].elements[\"$element\"].value'; alert("Unable to transfer data to "+url); @@ -1426,138 +3091,126 @@ function changeURL(val) { SCRIPT } } - $result.=<<SCRIPT if $ENV{'form.catalogmode'} eq 'groupsearch'; + $result.=<<SCRIPT if $env{'form.catalogmode'} eq 'groupsearch'; <script type="text/javascript"> - function select_data(title,url) { -// alert('DEBUG: Should be storing '+title+' and '+url); - } - function queue(val) { - if (eval("document.forms.results.returnvalues["+val+"].checked")) { - document.forms.results.acts.value+='1a'+val+'b'; - } - else { - document.forms.results.acts.value+='0a'+val+'b'; - } + function queue(checkbox_num,val) { + if (document.forms.results.returnvalues.length != "undefined" && + typeof(document.forms.results.returnvalues.length) == "number") { + if (document.forms.results.returnvalues[checkbox_num].checked) { + parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b'; + } else { + parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b'; + } + } else { + if (document.forms.results.returnvalues.checked) { + parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b'; + } else { + parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b'; + } + } } function select_group() { - window.location= - "/adm/groupsort?mode=$ENV{'form.mode'}&catalogmode=groupsearch&acts="+ - document.forms.results.acts.value; - } -</script> -SCRIPT - $result.=<<SCRIPT; -<script type="text/javascript"> - 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(); + parent.window.location= + "/adm/groupsort?mode=$env{'form.mode'}&catalogmode=groupsearch&acts="+ + parent.statusframe.document.forms.statusform.elements.Queue.value; } </script> SCRIPT + $result.=<<END; +</head> +$bodytag +<form name="results" method="post" action="/adm/searchcat" > +<input type="hidden" name="Queue" value="" /> +$importbutton +END return $result; } ###################################################################### ###################################################################### +sub search_status_header { + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + my $html=&Apache::lonxml::xmlbegin(); + return <<ENDSTATUS; +$html +<head> +<title>Search Status</title></head> +$bodytag +<h3>Search Status</h3> +Sending search request to LON-CAPA servers.<br /> +ENDSTATUS +} -=pod - -=item &make_popwin() +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; +} -Returns html with javascript in it to open up the status window. +###################################################################### +###################################################################### +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 $result = <<"ENDFRAMES"; +$html +<head> +<script> +var targetwin = opener; +var queue = ''; +</script> +<title>LON-CAPA Digital Library Search Results</title> +</head> +<frameset rows="150,*"> + <frame name="statusframe" src="$run_search_link"> + <frame name="resultsframe" src="$results_link"> +</frameset> +</html> +ENDFRAMES -=cut + $r->print($result); + return; +} ###################################################################### ###################################################################### -sub make_popwin { - my %rhash = @_; - my $servernum=(keys %rhash); - my $hcinit; - my $grid="'<br />'+\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) { - # '<a href=" - $grid.="'<a href=\""; - # javascript:displayinfo('+ - $grid.="javascript:opener.displayinfo('+"; - # "'"+'key - $grid.="\"'\"+'"; - $grid.=$sk; - my $hc; - if ($rhash{$sk} eq 'con_lost') { - $hc="BAD CONNECTION, CONTACT SYSTEM ADMINISTRATOR "; - } - else { - $hc="'+\"'\"+\"+hc['$sk']+\"+\"'\"+'"; - $hcinit.="hc[\"$sk\"]=\"not yet connected...\";"; - } - $grid.=" hitcount=".$hc; - $grid.=" domain=".$Apache::lonnet::hostdom{$sk}; - $grid.=" IP=".$Apache::lonnet::hostip{$sk}; - # '+"'"+'">'+ - $grid.="'+\"'\"+')\">'+"; - $grid.="\n"; - $grid.="'<img border=\"0\" name=\"img_".$Apache::lonnet::hostdom{$sk}. - '_'.$sk."\" src=\"/adm/lonIcons/srvnull.gif\" alt=\"".$sk. - "\" /></a>'+\n"; - $grid.="'<br />'+\n" unless $sn%10; - $sn++; + +sub has_stat_data { + my ($values) = @_; + if ( (defined($values->{'count'}) && $values->{'count'} ne '') || + (defined($values->{'stdno'}) && $values->{'stdno'} ne '') || + (defined($values->{'disc'}) && $values->{'disc'} ne '') || + (defined($values->{'avetries'}) && $values->{'avetries'} ne '') || + (defined($values->{'difficulty'}) && $values->{'difficulty'} ne '')) { + return 1; } - my $result.=<<ENDPOP; -<script type="text/javascript"> - 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"/><i>PLEASE BE PATIENT</i>'+ - '<'+'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(); -</script> -ENDPOP - return $result; + return 0; +} + +sub statfields { + return ('count','stdno','disc','avetries','difficulty'); +} + +sub has_eval_data { + my ($values) = @_; + if ( (defined($values->{'clear'}) && $values->{'clear'} ne '') || + (defined($values->{'technical'}) && $values->{'technical'} ne '') || + (defined($values->{'correct'}) && $values->{'correct'} ne '') || + (defined($values->{'helpful'}) && $values->{'helpful'} ne '') || + (defined($values->{'depth'}) && $values->{'depth'} ne '')) { + return 1; + } + return 0; +} + +sub evalfields { + return ('clear','technical','correct','helpful','depth'); } ###################################################################### @@ -1568,6 +3221,7 @@ ENDPOP =item Metadata Viewing Functions Output is a HTML-ified string. + Input arguments are title, author, subject, url, keywords, version, notes, short abstract, mime, language, creation date, last revision date, owner, copyright, hostname, and @@ -1582,38 +3236,111 @@ extra custom metadata to show. ###################################################################### ###################################################################### sub detailed_citation_view { - my %values = @_; - my $result=<<END; -<i>$values{'owner'}</i>, last revised $values{'lastrevisiondate'} -<h3><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" - target='search_preview'>$values{'title'}</a></h3> -<h3>$values{'author'}</h3> -</p> -<p> -<b>Subject:</b> $values{'subject'}<br /> -<b>Keyword(s):</b> $values{'keywords'}<br /> -<b>Notes:</b> $values{'notes'}<br /> -<b>MIME Type:</b> -END - $result.=&Apache::loncommon::filedescription($values{'mime'}); - $result.=<<END; -<br /> -<b>Language:</b> -END - $result.=&Apache::loncommon::languagedescription($values{'lang'}); - $result.=<<END; -<br /> -<b>Copyright/Distribution:</b> -END - $result.=&Apache::loncommon::copyrightdescription($values{'copyright'}); - $result.=<<END; -<br /> -</p> -$values{'extrashow'} -<p> -$values{'shortabstract'} -</p> -END + my ($prefix,%values) = @_; + my $result; + my $jumpurl=$values{'url'}; + $jumpurl=~s/^\/ext\//http\:\/\//; + $result .= '<b>'.$prefix. + '<img src="'.&Apache::loncommon::icon($values{'url'}).' " />'.' '. + '<a href="'.$jumpurl.'" '. + 'target="search_preview">'.$values{'title'}."</a></b>\n"; + $result .= "<p>\n"; + $result .= '<b>'.$values{'author'}.'</b>,'. + ' <i>'.$values{'owner'}.'</i><br />'; + foreach my $field + ( + { name=>'url', + translate => '<b>URL:</b> [_1]', + special => 'url link',}, + { name=>'subject', + translate => '<b>Subject:</b> [_1]',}, + { name=>'keywords', + translate => '<b>Keywords:</b> [_1]',}, + { name=>'notes', + translate => '<b>Notes:</b> [_1]',}, + { name=>'mimetag', + translate => '<b>MIME Type:</b> [_1]',}, + { name=>'standards', + translate => '<b>Standards:</b>[_1]',}, + { name=>'copyrighttag', + translate => '<b>Copyright/Distribution:</b> [_1]',}, + { name=>'count', + format => "%d", + translate => '<b>Access Count:</b> [_1]',}, + { name=>'stdno', + format => "%d", + translate => '<b>Number of Students:</b> [_1]',}, + { name=>'avetries', + format => "%.2f", + translate => '<b>Average Tries:</b> [_1]',}, + { name=>'disc', + format => "%.2f", + translate => '<b>Degree of Discrimination:</b> [_1]',}, + { name=>'difficulty', + format => "%.2f", + translate => '<b>Degree of Difficulty:</b> [_1]',}, + { name=>'clear', + format => "%.2f", + translate => '<b>Clear:</b> [_1]',}, + { name=>'depth', + format => "%.2f", + translate => '<b>Depth:</b> [_1]',}, + { name=>'helpful', + format => "%.2f", + translate => '<b>Helpful:</b> [_1]',}, + { name=>'correct', + format => "%.2f", + translate => '<b>Correct:</b> [_1]',}, + { name=>'technical', + format => "%.2f", + translate => '<b>Technical:</b> [_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 .= '<b>'.&mt($field->{'translate'}).'</b><ul>'; + foreach my $item (split(',',$values{$field->{'name'}})){ + $result .= '<li>'. + '<a target="search_preview" '. + 'href="'.$jumpurl.'">'.$item.'</a></li>'; + } + $result .= '</ul>'; + } elsif (exists($field->{'format'}) && $field->{'format'} ne ''){ + $result.= &mt($field->{'translate'}, + sprintf($field->{'format'}, + $values{$field->{'name'}}))."<br />\n"; + } else { + if ($field->{'special'} eq 'url link') { + $result.= + &mt($field->{'translate'}, + '<a href="'.$jumpurl.'" '. + 'target="search_preview">'. + $values{$field->{'name'}}. + '</a>'); + } else { + $result.= &mt($field->{'translate'}, + $values{$field->{'name'}}); + } + $result .= "<br />\n"; + } + } + $result .= "</p>"; + if (exists($values{'extrashow'}) && $values{'extrashow'} ne '') { + $result .= '<p>'.$values{'extrashow'}.'</p>'; + } + if (exists($values{'shortabstract'}) && $values{'shortabstract'} ne '') { + $result .= '<p>'.$values{'shortabstract'}.'</p>'; + } + $result .= '<hr align="left" width="200" noshade />'."\n"; return $result; } @@ -1625,19 +3352,35 @@ END =item &summary_view() =cut - ###################################################################### ###################################################################### sub summary_view { - my %values = @_; - my $result=<<END; -<a href="http://$ENV{'HTTP_HOST'}$values{'url'}" - target='search_preview'>$values{'author'}</a><br /> -$values{'title'}<br /> -$values{'owner'} -- $values{'lastrevisiondate'}<br /> + my ($prefix,%values) = @_; + my $icon=&Apache::loncommon::icon($values{'url'}); + my $result=qq{$prefix<img src="$icon" />}; + 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\:\/\//; + + $result.=<<END; +<a href="$jumpurl" + target='search_preview'>$values{'title'}</a><br /> +$values{'author'}, $values{'owner'} -- $values{'lastrevisiondate'}<br /> $values{'copyrighttag'}<br /> $values{'extrashow'} </p> +<hr align='left' width='200' noshade /> END return $result; } @@ -1647,6 +3390,37 @@ END =pod +=item &compact_view() + +=cut + +###################################################################### +###################################################################### +sub compact_view { + my ($prefix,%values) = @_; + my $jumpurl=$values{'url'}; + $jumpurl=~s/^\/ext\//http\:\/\//; + + my $result = + $prefix.'<img src="'.&Apache::loncommon::icon($values{'url'}).'">'; + 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.' '; + } + $result.=' <a href="'.$jumpurl.'" target="search_preview">'. + $values{'title'}.'</a>'.(' 'x2). + '<b>'.$values{'author'}.'</b> ('.$values{'domain'}.')<br />'; + return $result; +} + + +###################################################################### +###################################################################### + +=pod + =item &fielded_format_view() =cut @@ -1654,27 +3428,40 @@ END ###################################################################### ###################################################################### sub fielded_format_view { - my %values = @_; + 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=<<END; -<b>URL: </b> <a href="http://$ENV{'HTTP_HOST'}$values{'url'}" - target='search_preview'>$values{'url'}</a> -<br /> -<b>Title:</b> $values{'title'}<br /> -<b>Author(s):</b> $values{'author'}<br /> -<b>Subject:</b> $values{'subject'}<br /> -<b>Keyword(s):</b> $values{'keywords'}<br /> -<b>Notes:</b> $values{'notes'}<br /> -<b>MIME Type:</b> $values{'mimetag'}<br /> -<b>Language:</b> $values{'language'}<br /> -<b>Creation Date:</b> $values{'creationdate'}<br /> -<b>Last Revision Date:</b> $values{'lastrevisiondate'}<br /> -<b>Publisher/Owner:</b> $values{'owner'}<br /> -<b>Copyright/Distribution:</b> $values{'copyrighttag'}<br /> -<b>Repository Location:</b> $values{'hostname'}<br /> -<b>Abstract:</b> $values{'shortabstract'}<br /> -$values{'extrashow'} -</p> +$prefix <img src="$icon" /> +<dl> +<dt>URL:</dt> + <dd><a href="$jumpurl" + target='search_preview'>$values{'url'}</a></dd> END + foreach my $field ('title','author','domain','subject','keywords','notes', + 'mimetag','language','creationdate','lastrevisiondate', + 'owner','copyrighttag','hostname','abstract') { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + if (&has_stat_data(\%values)) { + foreach my $field (&statfields()) { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + } + if (&has_eval_data(\%values)) { + foreach my $field (&evalfields()) { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + } + $result .= "</dl>\n"; + $result .= $values{'extrashow'}; + $result .= '<hr align="left" width="200" noshade />'."\n"; return $result; } @@ -1692,36 +3479,63 @@ END ###################################################################### ###################################################################### sub xml_sgml_view { - my %values = @_; + my ($prefix,%values) = @_; + my $xml = '<LonCapaResource>'."\n"; + # The usual suspects + foreach my $field ('url','title','author','subject','keywords','notes','domain') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + # + $xml .= "<mimeInfo>\n"; + foreach my $field ('mime','mimetag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</mimeInfo>\n"; + # + $xml .= "<languageInfo>\n"; + foreach my $field ('language','languagetag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</languageInfo>\n"; + # + foreach my $field ('creationdate','lastrevisiondate','owner') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + # + $xml .= "<copyrightInfo>\n"; + foreach my $field ('copyright','copyrighttag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</copyrightInfo>\n"; + $xml .= qq{<repositoryLocation>$values{'hostname'}</repositoryLocation>}. + "\n"; + $xml .= qq{<shortabstract>$values{'shortabstract'}</shortabstract>}."\n"; + # + if (&has_stat_data(\%values)){ + $xml .= "<problemstatistics>\n"; + foreach my $field (&statfields()) { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</problemstatistics>\n"; + } + # + if (&has_eval_data(\%values)) { + $xml .= "<evaluation>\n"; + foreach my $field (&evalfields) { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</evaluation>\n"; + } + # + $xml .= "</LonCapaResource>\n"; + $xml = &HTML::Entities::encode($xml,'<>&'); my $result=<<END; +$prefix <pre> -<LonCapaResource> -<url>$values{'url'}</url> -<title>$values{'title'}</title> -<author>$values{'author'}</author> -<subject>$values{'subject'}</subject> -<keywords>$values{'keywords'}</keywords> -<notes>$values{'notes'}</notes> -<mimeInfo> -<mime>$values{'mime'}</mime> -<mimetag>$values{'mimetag'}</mimetag> -</mimeInfo> -<languageInfo> -<language>$values{'lang'}</language> -<languagetag>$values{'language'}</languagetag> -</languageInfo> -<creationdate>$values{'creationdate'}</creationdate> -<lastrevisiondate>$values{'lastrevisiondate'}</lastrevisiondate> -<owner>$values{'owner'}</owner> -<copyrightInfo> -<copyright>$values{'copyright'}</copyright> -<copyrighttag>$values{'copyrighttag'}</copyrighttag> -</copyrightInfo> -<repositoryLocation>$values{'hostname'}</repositoryLocation> -<shortabstract>$values{'shortabstract'}</shortabstract> -</LonCapaResource> +$xml </pre> $values{'extrashow'} +<hr align='left' width='200' noshade /> END return $result; } @@ -1740,11 +3554,56 @@ END sub filled { my ($field)=@_; if ($field=~/\S/ && $field ne 'any') { - return 1; + return 1; + } else { + return 0; } - else { - return 0; +} + +###################################################################### +###################################################################### + +=pod + +=item &output_unparsed_phrase_error() + +=cut + +###################################################################### +###################################################################### +sub output_unparsed_phrase_error { + my ($r,$closebutton,$parms,$hidden_fields,$field)=@_; + my $errorstring; + if ($field eq 'basicexp') { + $errorstring = &mt('Unable to understand the search phrase <i>[_1]</i>. Please modify your search.',$env{'form.basicexp'}); + } else { + $errorstring = &mt('Unable to understand the search phrase <b>[_1]</b>:<i>[_2]</i>.',$field,$env{'form.'.$field}); } + my $bodytag = &Apache::loncommon::bodytag('Search'); + my $heading = &mt('Unparsed Field'); + my $revise = &mt('Revise search request'); + # make query information persistent to allow for subsequent revision + my $html=&Apache::lonxml::xmlbegin(); + $r->print(<<ENDPAGE); +$html +<head> +<title>The LearningOnline Network with CAPA</title> +</head> +$bodytag +<form method="post" action="/adm/searchcat"> +$hidden_fields +$closebutton +<hr /> +<h2>$heading</h2> +<p> +$errorstring +</p> +<p> +<a href="/adm/searchcat?$parms&persistent_db_id=$env{'form.persistent_db_id'}">$revise</a> +</p> +</body> +</html> +ENDPAGE } ###################################################################### @@ -1754,41 +3613,47 @@ sub filled { =item &output_blank_field_error() +Output a complete page that indicates the user has not filled in enough +information to do a search. + +Inputs: $r (Apache request handle), $closebutton, $parms. + +Returns: nothing + +$parms is extra information to include in the 'Revise search request' link. + =cut ###################################################################### ###################################################################### sub output_blank_field_error { - my ($r)=@_; - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); - - $r->print(<<BEGINNING); -<html> + my ($r,$closebutton,$parms,$hidden_fields)=@_; + my $bodytag=&Apache::loncommon::bodytag('Search'); + my $errormsg = &mt('You did not fill in enough information for the search to be started. You need to fill in relevant fields on the search page in order for a query to be processed.'); + my $revise = &mt('Revise Search Request'); + my $heading = &mt('Unactionable Search Queary'); + my $html=&Apache::lonxml::xmlbegin(); + $r->print(<<ENDPAGE); +$html <head> <title>The LearningOnline Network with CAPA</title> -BEGINNING - $r->print(<<RESULTS); </head> -<body bgcolor="#ffffff"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> +$bodytag <form method="post" action="/adm/searchcat"> -$persistent -<input type='button' value='Revise search request' -onClick='this.form.submit();' /> +$hidden_fields $closebutton <hr /> -<h3>Helpful Message</h3> +<h2>$heading</h2> <p> -Incorrect search query due to blank entry fields. -You need to fill in the relevant -fields on the search page in order for a query to be -processed. +$errormsg +</p> +<p> +<a href="/adm/searchcat?$parms&persistent_db_id=$env{'form.persistent_db_id'}">$revise</a> </p> </body> </html> -RESULTS +ENDPAGE + return; } ###################################################################### @@ -1800,30 +3665,36 @@ 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,$hidden_fields)=@_; # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); - + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + my $html=&Apache::lonxml::xmlbegin(); $r->print(<<RESULTS); -<html> +$html <head> <title>The LearningOnline Network with CAPA</title> </head> -<body bgcolor="#ffffff"> +$bodytag <img align='right' src='/adm/lonIcons/lonlogos.gif' /> <h1>Search Catalog</h1> <form method="post" action="/adm/searchcat"> -$persistent +$hidden_fields <input type='button' value='Revise search request' onClick='this.form.submit();' /> $closebutton <hr /> -<h3>Helpful Message</h3> +<h3>Error</h3> <p> $message </p> @@ -1839,7 +3710,7 @@ RESULTS =item &start_fresh_session() -Cleans the global %hash by removing all fields which begin with +Cleans the global %groupsearch_db by removing all fields which begin with 'pre_' or 'store'. =cut @@ -1847,64 +3718,30 @@ Cleans the global %hash by removing all ###################################################################### ###################################################################### sub start_fresh_session { - delete $hash{'mode_catalog'}; - foreach (keys %hash) { + delete $groupsearch_db{'mode_catalog'}; + foreach (keys %groupsearch_db) { if ($_ =~ /^pre_/) { - delete $hash{$_}; + delete $groupsearch_db{$_}; } if ($_ =~ /^store/) { - delete $hash{$_}; + delete $groupsearch_db{$_}; } } } -###################################################################### -###################################################################### - -=pod - -=item &popwin_js() send javascript to popwin - -=cut +1; -###################################################################### -###################################################################### -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"); -<script type="text/javascript"> - if (! popwin.closed) { - $text +sub cleanup { + if (tied(%groupsearch_db)) { + unless (untie(%groupsearch_db)) { + &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db'); + } } -</script> -END - $r->rflush(); + &untiehash(); + &Apache::lonmysql::disconnect_from_db(); + return OK; } -###################################################################### -###################################################################### - -=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__ =pod