--- loncom/interface/lonsearchcat.pm 2003/06/19 13:07:49 1.183 +++ loncom/interface/lonsearchcat.pm 2004/06/28 15:55:09 1.230 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.183 2003/06/19 13:07:49 matthew Exp $ +# $Id: lonsearchcat.pm,v 1.230 2004/06/28 15:55:09 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -48,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 @@ -59,20 +59,8 @@ 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 :http); use Apache::lonnet(); @@ -82,111 +70,60 @@ use Text::Query; use GDBM_File; use Apache::loncommon(); use Apache::lonmysql(); - -# ---------------------------------------- variables used throughout the module +use Apache::lonmeta; +use Apache::lonhtmlcommon; +use Apache::lonlocal; +use LONCAPA::lonmetadata(); +use HTML::Entities(); +use Parse::RecDescent; ###################################################################### ###################################################################### - -=pod - -=item Global variables - -=over 4 - -=item $importbutton - -button to take the select results and go to group sorting - -=item %groupsearch_db - -Database hash used to save values for the groupsearch RAT interface. - -=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(). - -=item %Views - -Hash which associates an output view description with the function -that produces it. Adding a new view type should be as easy as -adding a line to the definition of this hash and making sure the function -takes the proper parameters. - -=item $bodytag - -LON-CAPA standard body tag, gotten from &Apache::lonnet::bodytag. -No title, no table, just a <body> tag. - -=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 -# -- dynamically rendered interface components -my $importbutton; # button to take the selected results and go to group sorting +# The different view modes and associated functions -# -- miscellaneous variables -my %groupsearch_db; # database hash -my $diropendb = ""; # db file -# View Description Function Pointer -my %Views = ("Detailed Citation View" => \&detailed_citation_view, - "Summary View" => \&summary_view, - "Fielded Format" => \&fielded_format_view, - "XML/SGML" => \&xml_sgml_view, - "Compact View" => \&compact_view); -my %persistent_db; -my $hidden_fields; -my $bodytag; - -# -# For course search -# -my %alreadyseen; -my $hashtied; -my %hash; -my $totalfound; - -###################################################################### -###################################################################### - -=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 +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; +# &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. 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. # - $r->content_type('text/html'); + &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK if $r->header_only; ## @@ -199,7 +136,7 @@ sub handler { &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['catalogmode','launch','acts','mode','form','element','pause', 'phase','persistent_db_id','table','start','show', - 'cleargroupsort']); + 'cleargroupsort','titleelement']); ## ## The following is a trick - we wait a few seconds if asked to so ## the daemon running the search can get ahead of the daemon @@ -214,7 +151,8 @@ sub handler { ## Initialize global variables ## my $domain = $r->dir_config('lonDefDomain'); - $diropendb= "/home/httpd/perl/tmp/$ENV{'user.domain'}_$ENV{'user.name'}_searchcat.db"; + $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. @@ -229,11 +167,34 @@ sub handler { '_'.&Apache::lonnet::escape($ENV{'user.name'}). '_'.$ENV{'form.persistent_db_id'}.'_persistent_search.db'; ## - 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"); - $r->print(<<END); + &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); + $r->print(<<END); <html> <head><title>LON-CAPA Search Error</title></head> $bodytag @@ -242,8 +203,11 @@ error and has been logged. Please alert </body> </html> END - return OK; + return OK; + } } + } else { + &clean_up_environment(); } ## ## Clear out old values from groupsearch database @@ -270,20 +234,19 @@ END $hidden_fields = '<input type="hidden" name="persistent_db_id" value="'. $ENV{'form.persistent_db_id'}.'" />'."\n"; if (exists($ENV{'form.catalogmode'})) { - $hidden_fields .= '<input type="hidden" name="catalogmode" value="'. - $ENV{'form.catalogmode'}.'" />'."\n"; + $hidden_fields .= &hidden_field('catalogmode'); } if (exists($ENV{'form.form'})) { - $hidden_fields .= '<input type="hidden" name="form" value="'. - $ENV{'form.form'}.'" />'."\n"; + $hidden_fields .= &hidden_field('form'); } if (exists($ENV{'form.element'})) { - $hidden_fields .= '<input type="hidden" name="element" value="'. - $ENV{'form.element'}.'" />'."\n"; + $hidden_fields .= &hidden_field('element'); + } + if (exists($ENV{'form.titleelement'})) { + $hidden_fields .= &hidden_field('titleelement'); } if (exists($ENV{'form.mode'})) { - $hidden_fields .= '<input type="hidden" name="mode" value="'. - $ENV{'form.mode'}.'" />'."\n"; + $hidden_fields .= &hidden_field('mode'); } ## ## Configure dynamic components of interface @@ -325,15 +288,41 @@ END } $ENV{'form.phase'} = 'disp_basic' if (! exists($ENV{'form.phase'})); $ENV{'form.show'} = 20 if (! exists($ENV{'form.show'})); + # + $ENV{'form.searchmode'} = 'basic' if (! exists($ENV{'form.searchmode'})); + if ($ENV{'form.phase'} eq 'adv_search' || + $ENV{'form.phase'} eq 'disp_adv') { + $ENV{'form.searchmode'} = 'advanced'; + } elsif ($ENV{'form.phase'} eq 'course_search') { + $ENV{'form.searchmode'} = 'course_search'; + } + # + if ($ENV{'form.searchmode'} eq 'advanced') { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?phase=disp_adv&'. + 'catalogmode='.$ENV{'form.catalogmode'}. + '&launch='.$ENV{'form.launch'}. + '&mode='.$ENV{'form.mode'}, + text=>"Advanced Search", + bug=>'Searching',}); + } elsif ($ENV{'form.searchmode'} eq 'course search') { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?phase=disp_adv&'. + 'catalogmode='.$ENV{'form.catalogmode'}. + '&launch='.$ENV{'form.launch'}. + '&mode='.$ENV{'form.mode'}, + text=>"Course Search", + bug=>'Searching',}); + } ## ## Switch on the phase ## if ($ENV{'form.phase'} eq 'disp_basic') { - &print_basic_search_form($r,$closebutton); + &print_basic_search_form($r,$closebutton,$hidden_fields); } elsif ($ENV{'form.phase'} eq 'disp_adv') { - &print_advanced_search_form($r,$closebutton); + &print_advanced_search_form($r,$closebutton,$hidden_fields); } elsif ($ENV{'form.phase'} eq 'results') { - &display_results($r,$importbutton,$closebutton); + &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, @@ -349,12 +338,35 @@ END &course_search($r); } elsif(($ENV{'form.phase'} eq 'basic_search') || ($ENV{'form.phase'} eq 'adv_search')) { - $ENV{'form.searchmode'} = 'basic'; - if ($ENV{'form.phase'} eq 'adv_search') { - $ENV{'form.searchmode'} = 'advanced'; + # + # 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); $r->print(<<END); <html><head><title>Search Error</title></head> $bodytag @@ -376,35 +388,65 @@ Unable to properly store search informat END return OK; } - # - # We are running a search - 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); - } else { # Advanced search - ($query,$customquery,$customshow,$libraries,$pretty_string) - = &parse_advanced_search($r,$closebutton); - return OK if (! defined($query)); - } - &make_persistent({ query => $query, - customquery => $customquery, - customshow => $customshow, - libraries => $libraries, - pretty_string => $pretty_string }, - $persistent_db_file); ## ## Print out the frames interface ## - &print_frames_interface($r); + 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 course_search { my $r=shift; @@ -415,54 +457,39 @@ sub course_search { if ($ENV{'form.crsrelated'}) { ($search_string,@New_Words) = &related_version($ENV{'form.courseexp'}); if (@New_Words) { - $pretty_search_string .= " with related words: <b>@New_Words</b>."; + $pretty_search_string .= ' '.&mt("with related words").": <b>@New_Words</b>."; } else { - $pretty_search_string .= " with no related words."; + $pretty_search_string .= ' '.&mt('with no related words')."."; } } my $fulltext=$ENV{'form.crsfulltext'}; my @allwords=($search_string,@New_Words); $totalfound=0; $r->print('<html><head><title>LON-CAPA Course Search</title></head>'. - $bodytag.$pretty_search_string); + $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr />'); $r->rflush(); # ======================================================= Go through the course - $hashtied=0; - undef %alreadyseen; - %alreadyseen=(); + undef %alreadyseen; + %alreadyseen=(); my $c=$r->connection; - &tiehash(); - foreach (keys %hash) { - if ($c->aborted()) { last; } - if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) { - &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext,@allwords); - } - } - &untiehash(); + if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db", + &GDBM_READER(),0640)) { + foreach (keys %hash) { + if ($c->aborted()) { last; } + if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) { + &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext, + @allwords); + } + } + untie(%hash); + } unless ($totalfound) { - $r->print('<p>No resources found.</p>'); + $r->print('<p>'.&mt('No resources found').'.</p>'); } # =================================================== Done going through course $r->print('</body></html>'); } -# ---------------------------------------------------------------- tie the hash - -sub tiehash { - $hashtied=0; - if ($ENV{'request.course.fn'}) { - if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db", - &GDBM_READER(),0640)) { - $hashtied=1; - } - } -} - -sub untiehash { - if ($hashtied) { untie %hash; } - $hashtied=0; -} - # =============================== This pulls up a resource and its dependencies sub checkonthis { @@ -508,6 +535,24 @@ sub checkonthis { } } +sub untiehash { + if (tied(%hash)) { + untie(%hash); + } +} + +} # End of course search scoping + +sub search_html_header { + my $Str = <<ENDHEADER; +<html> +<head> +<title>The LearningOnline Network with CAPA</title> +</head> +ENDHEADER + return $Str; +} + ###################################################################### ###################################################################### @@ -515,102 +560,118 @@ sub checkonthis { =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 print_basic_search_form{ - my ($r,$closebutton) = @_; - my $bodytag=&Apache::loncommon::bodytag('Search'); - 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> -$bodytag -ENDDOCUMENT -if (&Apache::lonnet::allowed('bre',$ENV{'request.role.domain'})) { - $scrout.=(<<ENDDOCUMENT); -<h1>Catalog Search</h1> -<form method="post" action="/adm/searchcat"> -<input type="hidden" name="phase" value="basic_search" /> -$hidden_fields -<p> -Enter terms or phrases separated by AND, OR, or NOT -then press SEARCH below. -</p> -<p> -<table> -<tr><td> -ENDDOCUMENT - $scrout.=' '.&simpletextfield('basicexp',$ENV{'form.basicexp'},40). - ' '; - my $relatedcheckbox = &simplecheckbox('related',$ENV{'form.related'}); - my $domain = $r->dir_config('lonDefDomain'); - my $domaincheckbox = &simplecheckbox('domains',$domain,'checked'); - $scrout.=<<END; -</td><td><a -href="/adm/searchcat?phase=disp_adv&catalogmode=$ENV{'form.catalogmode'}&launch=$ENV{'form.launch'}" ->Advanced Search</a></td></tr> -<tr><td>$relatedcheckbox use related words</td> - <td>$domaincheckbox only search domain <b>$domain</b></td></tr> -</table> -</p> -<p> - <input type="submit" name="basicsubmit" value='SEARCH' /> -$closebutton -END - $scrout.=&selectbox(undef,'viewselect', - $ENV{'form.viewselect'}, - undef,undef,undef, - sort(keys(%Views))); - $scrout.=&selectbox(undef,'show', - $ENV{'form.show'}, - undef,undef,undef, - (10,20,50,100)); - $scrout.=<<ENDDOCUMENT; -per page. -</p> -</form> -ENDDOCUMENT +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= + &mt('[_1] use related words', + &Apache::lonhtmlcommon::checkbox + ('related',$ENV{'form.related'},'related')); + my $onlysearchdomain= + &mt('[_1] only search domain [_2]', + &Apache::lonhtmlcommon::checkbox('domains', + $ENV{'form.domains'}, + $r->dir_config('lonDefDomain') + ), + $r->dir_config('lonDefDomain') + ); + 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 />'. + '</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)' + ); $scrout.=(<<ENDCOURSESEARCH); +<form name="loncapa_search" method="post" action="/adm/searchcat"> +<center> <hr /> -<h1>Course Search</h1> -<form method="post" action="/adm/searchcat"> +<h1>$lt{'header'}</h1> <input type="hidden" name="phase" value="course_search" /> $hidden_fields <p> -Enter terms or phrases, then press SEARCH below. +$lt{'note'}. </p> <p> <table> <tr><td> ENDCOURSESEARCH $scrout.=' '. - &simpletextfield('courseexp',$ENV{'form.courseexp'},40); + &Apache::lonhtmlcommon::textbox('courseexp', + $ENV{'form.courseexp'},40); my $crscheckbox = - &simplecheckbox('crsfulltext',$ENV{'form.crsfulltext'}); + &Apache::lonhtmlcommon::checkbox('crsfulltext', + $ENV{'form.crsfulltext'}); my $relcheckbox = - &simplecheckbox('crsrelated',$ENV{'form.crsrelated'}); + &Apache::lonhtmlcommon::checkbox('crsrelated', + $ENV{'form.crsrelated'}); $scrout.=(<<ENDENDCOURSE); </td></tr> -<tr><td>$relcheckbox use related words</td><td></td></tr> -<tr><td>$crscheckbox fulltext search (time consuming)</td><td></td></tr> +<tr><td>$relcheckbox $lt{'use'}</td><td></td></tr> +<tr><td>$crscheckbox $lt{'full'}</td><td></td></tr> </table><p> - <input type="submit" name="coursesubmit" value='SEARCH' /> + <input type="submit" name="coursesubmit" value='$lt{'srch'}' /> </p> +</center> +</form> ENDENDCOURSE } $scrout.=(<<ENDDOCUMENT); @@ -627,188 +688,216 @@ ENDDOCUMENT =item &advanced_search_form() -Returns a scalar which holds html for the advanced search form. +Prints the advanced search form. =cut ###################################################################### ###################################################################### - sub print_advanced_search_form{ - my ($r,$closebutton) = @_; - my $advanced_buttons = <<"END"; -<p> -<input type="submit" name="advancedsubmit" value='SEARCH' /> -<input type="reset" name="reset" value='RESET' /> + 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 -<input type="button" value="HELP" onClick="openhelp()" /> -</p> END - if (!defined($ENV{'form.viewselect'})) { - $ENV{'form.viewselect'} ="Detailed Citation View"; - } - my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search'); - 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> + my $scrout=&search_html_header(); + $scrout .= <<"ENDHEADER"; $bodytag -Enter terms or phrases separated by search operators -such as AND, OR, or NOT.<br /> -<form method="post" action="/adm/searchcat"> +<form method="post" action="/adm/searchcat" name="advsearch"> +<p> $advanced_buttons -$hidden_fields -<input type="hidden" name="phase" value="adv_search" /> -<table> -<tr><td><font color="#800000" face="helvetica"><b>VIEW:</b></font></td> -<td> ENDHEADER - $scrout.=&selectbox(undef,'viewselect', - $ENV{'form.viewselect'}, - undef,undef,undef, - sort(keys(%Views))); - $scrout.=' '; - $scrout.=&selectbox(undef,'show', - $ENV{'form.show'}, - undef,undef,undef, - (10,20,50,100)); - $scrout.=' '. - '<font color="#800000" face="helvetica">Per Page</font>'; - $scrout.="</td><td>Related<br />Words</td></tr>\n"; - $scrout.=&searchphrasefield_with_related('title', 'title' , - $ENV{'form.title'}); - $scrout.=&searchphrasefield('author', 'author' ,$ENV{'form.author'}); - $scrout.=&searchphrasefield_with_related('subject', 'subject' , - $ENV{'form.subject'}); - $scrout.=&searchphrasefield_with_related('keywords','keywords', - $ENV{'form.keywords'}); - $scrout.=&searchphrasefield('URL', 'url' ,$ENV{'form.url'}); - $scrout.=&searchphrasefield_with_related('notes', 'notes' , - $ENV{'form.notes'}); - $scrout.=&searchphrasefield_with_related('abstract','abstract', - $ENV{'form.abstract'}); - # Hack - an empty table row. - $scrout.="<tr><td> </td><td> </td><td> </td></tr>\n"; - $scrout.=&searchphrasefield('file<br />extension','extension', - $ENV{'form.extension'}); - $scrout.="<tr><td> </td><td> </td><td> </td></tr>\n"; - $scrout.=&searchphrasefield('publisher<br />owner','owner', - $ENV{'form.owner'}); - $scrout.="</table>\n"; - $ENV{'form.category'}='any' if (! defined($ENV{'form.category'})); - $scrout.=&selectbox('File Category','category', - $ENV{'form.category'}, - 'any','Any category', - undef, - (&Apache::loncommon::filecategories())); - $ENV{'form.language'}='any' unless length($ENV{'form.language'}); - #---------------------------------------------------------------- - # Allow restriction to multiple domains. - # I make the crazy assumption that there will never be a domain 'any'. - # - $ENV{'form.domains'} = 'any' if (! exists($ENV{'form.domains'})); - my @allowed_domains = (ref($ENV{'form.domains'}) ? @{$ENV{'form.domains'}} - : ($ENV{'form.domains'}) ); - my %domain_hash = (); - foreach (@allowed_domains) { - $domain_hash{$_}++; + $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, + ); + # + foreach my $field ('title','author','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>'.$/; } - my @domains =&Apache::loncommon::get_domains(); - # adjust the size of the select box - my $size = 4; - $size = (scalar @domains < ($size - 1) ? scalar @domains + 1 : $size); - $scrout.="\n".'<font color="#800000" face="helvetica"><b>'. - 'DOMAINS TO BE SEARCHED</b></font><br />'. - '<select name="domains" size="'.$size.'" multiple>'."\n". - '<option name="any" value="any" '. - ($domain_hash{'any'}? 'selected ' :''). - '>all domains</option>'."\n"; - foreach my $dom (sort @domains) { - $scrout.="<option name=\"$dom\" ". - ($domain_hash{$dom} ? 'selected ' :'').">$dom</option>\n"; - } - $scrout.="</select>\n"; - #---------------------------------------------------------------- - $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>'; - $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; -#<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'}); + 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). + '</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; $advanced_buttons </form> @@ -824,7 +913,106 @@ ENDDOCUMENT =pod -=item &get_persistent_form_data +=item &titlefield() + +Inputs: title text + +Outputs: titletext with font wrapper + +=cut + +###################################################################### +###################################################################### +sub titlefield { + my $title=shift; + return $title; +} + +###################################################################### +###################################################################### + +=pod + +=item viewoptiontext() + +Inputs: codename for view option + +Outputs: displayed text + +=cut + +###################################################################### +###################################################################### +sub viewoptiontext { + my $code=shift; + my %desc=&Apache::lonlocal::texthash + ('detailed' => "Detailed Citation View", + 'xml' => 'XML/SGML', + 'compact' => 'Compact View', + 'fielded' => 'Fielded Format', + 'summary' => 'Summary View'); + return $desc{$code}; +} + +###################################################################### +###################################################################### + +=pod + +=item viewoptions() + +Inputs: none + +Outputs: text for box with view options + +=cut + +###################################################################### +###################################################################### +sub viewoptions { + my $scrout; + if (! defined($ENV{'form.viewselect'})) { + $ENV{'form.viewselect'}='detailed'; + } + $scrout.=&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; +} + +###################################################################### +###################################################################### + +=pod + +=item searchhelp() + +Inputs: none + +Outputs: return little blurb on how to enter searches + +=cut + +###################################################################### +###################################################################### +sub searchhelp { + return &mt('Enter words and quoted phrases'); +} + +###################################################################### +###################################################################### + +=pod + +=item &get_persistent_form_data() Inputs: filename of database @@ -879,7 +1067,7 @@ sub get_persistent_form_data { =pod -=item &get_persistent_data +=item &get_persistent_data() Inputs: filename of database, ref to array of values to recover. @@ -930,7 +1118,7 @@ Inputs: Hash of values to save, filename Store variables away to the %persistent_db. Values will be escaped. Values that are array pointers will have their -elements escaped and concatenated in a comma seperated string. +elements escaped and concatenated in a comma separated string. =cut @@ -962,7 +1150,7 @@ Inputs: filename of persistent database. Store most form variables away to the %persistent_db. Values will be escaped. Values that are array pointers will have their -elements escaped and concatenated in a comma seperated string. +elements escaped and concatenated in a comma separated string. =cut @@ -980,287 +1168,6 @@ sub make_form_data_persistent { } ###################################################################### -# HTML form building functions # -###################################################################### - -=pod - -=item HTML form building functions - -=over 4 - -=cut - -############################################### -############################################### - -=pod - -=item &simpletextfield() - -Inputs: $name,$value,$size - -Returns a text input field with the given name, value, and size. -If size is not specified, a value of 20 is used. - -=cut - -############################################### -############################################### - -sub simpletextfield { - my ($name,$value,$size)=@_; - $size = 20 if (! defined($size)); - return '<input type="text" name="'.$name. - '" size="'.$size.'" value="'.$value.'" />'; -} - -############################################### -############################################### - -=pod - -=item &simplecheckbox() - -Inputs: $name,$value - -Returns a simple check box with the given $name. -If $value eq 'on' the box is checked. - -=cut - -############################################### -############################################### - -sub simplecheckbox{ - my ($name,$value,$checked)=@_; - $checked="checked" if ($value eq 'on'); - return '<input type="checkbox" name="'.$name.'" value="'.$value.'" '. - $checked.' />'; -} - -############################################### -############################################### - -=pod - -=item &fieldtitle() - -Input: $title - -Returns a scalar with html which will display $title as a search -field heading. - -=cut - -############################################### -############################################### - -sub fieldtitle { - my $title = uc(shift()); - return '<font color="#800000" face="helvetica"><b>'.$title. - ': </b></font>'; -} - -############################################### -############################################### - -=pod - -=item &searchphrasefield() - -Inputs: $title,$name,$value - -Returns html for a title line and an input field for entering search terms. -The entry field (which is where the $name and $value are used) is a 50 column -simpletextfield. The html returned is for a row in a three column table. - -=cut - -############################################### -############################################### - -sub searchphrasefield { - my ($title,$name,$value)=@_; - return '<tr><td>'.&fieldtitle($title).'</td><td>'. - &simpletextfield($name,$value,50)."</td><td> </td></tr>\n"; -} - -############################################### -############################################### - -=pod - -=item &searchphrasefield_with_related() - -Inputs: $title,$name,$value - -Returns html for a title line and an input field for entering search terms -and a check box for 'related words'. The entry field (which is where the -$name and $value are used) is a 50 column simpletextfield. The name of -the related words checkbox is "$name_related". - -=cut - -############################################### -############################################### - -sub searchphrasefield_with_related { - my ($title,$name,$value)=@_; - return '<tr><td>'.&fieldtitle($title).'</td><td>'. - &simpletextfield($name,$value,50).'</td><td align="center"> '. - &simplecheckbox($name.'_related',$ENV{'form.'.$name.'_related'}). - " </td></tr>\n"; -} - -############################################### -############################################### - -=pod - -=item &dateboxes() - -Returns html selection form elements for the specification of -the day, month, and year. - -=cut - -############################################### -############################################### - -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"; - } - $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"; -} - -############################################### -############################################### - -=pod - -=item &selectbox() - -Returns a scalar containing an html <select> form. - -Inputs: - -=over 4 - -=item $title - -Printed above the select box, in uppercase. If undefined, only a select -box will be returned, with no additional html. - -=item $name - -The name element of the <select> tag. - -=item $default - -The default value of the form. Can be $anyvalue, or in @idlist. - -=item $anyvalue - -The <option value="..."> used to indicate a default of -none of the values. Can be undef. - -=item $anytag - -The text associate with $anyvalue above. - -=item $functionref - -Each element in @idlist will be passed as a parameter -to the function referenced here. The return value of the function should -be a scalar description of the items. If this value is undefined the -description of each item in @idlist will be the item name. - -=item @idlist - -The items to be selected from. One of these or $anyvalue will be the -value returned by the form element, $ENV{form.$name}. - -=back - -=cut - -############################################### - -sub selectbox { - my ($title,$name,$default,$anyvalue,$anytag,$functionref,@idlist)=@_; - if (! defined($functionref)) { $functionref = sub { $_[0]}; } - my $selout=''; - if (defined($title)) { - my $uctitle=uc($title); - $selout="\n".'<p><font color="#800000" face="helvetica">'. - '<b>'.$uctitle.': </b></font>'; - } - $selout .= '<select name="'.$name.'">'; - unshift @idlist,$anyvalue if (defined($anyvalue)); - foreach (@idlist) { - $selout.='<option value="'.$_.'"'; - if ($_ eq $default and !/^any$/) { - $selout.=' selected >'.&{$functionref}($_).'</option>'; - } - elsif ($_ eq $default and /^$anyvalue$/) { - $selout.=' selected >'.$anytag.'</option>'; - } - else {$selout.='>'.&{$functionref}($_).'</option>';} - } - return $selout.'</select>'.(defined($title)?'</p>':' '); -} - -###################################################################### -# End of HTML form building functions # -###################################################################### - -=pod - -=back - -=cut - - -###################################################################### ###################################################################### =pod @@ -1286,41 +1193,59 @@ Parse advanced search form and return th ###################################################################### ###################################################################### sub parse_advanced_search { - my ($r,$closebutton)=@_; + 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 = "<br />\n"; + 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','extension','language','owner', - 'custommetadata','customshow','category') { - $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','category','extension','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,$closebutton,'phase=disp_adv'); + 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 @@ -1328,24 +1253,31 @@ sub parse_advanced_search { 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}) { - my $searchphrase = $ENV{'form.'.$field}; - $pretty_search_string .= $font."$field</font> contains <b>". - $searchphrase."</b>"; + 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 @New_Words; - ($searchphrase,@New_Words) = &related_version($searchphrase); - if (@New_Words) { - $pretty_search_string .= " with related words: ". - "<b>@New_Words</b>."; + 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 no related words."; + $pretty_search_string.= ' with related words.'; } } - $pretty_search_string .= "<br />\n"; - push @queries,&build_SQL_query($field,$searchphrase); + $pretty_search_string .= '<br />'; + push (@queries,$SQLQuery); } } # @@ -1361,18 +1293,30 @@ sub parse_advanced_search { $searchphrase = join(' OR ',@extensions); } } - if (exists($ENV{'form.extension'}) && $ENV{'form.extension'} !~ /^\s*$/) { - $searchphrase .= ' OR ' if (defined($searchphrase)); - my @extensions = split(/,/,$ENV{'form.extension'}); - $searchphrase .= join(' OR ',@extensions); - } if (defined($searchphrase)) { - push @queries,&build_SQL_query('mime',$searchphrase); + my ($error,$SQLsearch) = &process_phrase_input($searchphrase,0,'mime'); + push @queries,$SQLsearch; $pretty_search_string .=$font.'mime</font> contains <b>'. $searchphrase.'</b><br />'; } - ##### + # # 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>= ". @@ -1383,51 +1327,87 @@ sub parse_advanced_search { push @queries,"(copyright like \"$ENV{'form.copyright'}\")"; $pretty_search_string.=$font."copyright</font> = ". &Apache::loncommon::copyrightdescription($ENV{'form.copyright'}). - "<br \>\n"; + "<br />\n"; + } + # + # 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 />'; + } } # # 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,$closebutton); - return ; - } elsif ($datequery) { + 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=undef; -# 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'}); -# } + ## + ## 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); -# } - ## --------------------------------------------------------------- + # 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) = @@ -1435,11 +1415,11 @@ sub parse_advanced_search { $pretty_search_string .= $pretty_domains_string."<br />\n"; # if (@queries) { - $query=join(" AND ",@queries); - $query="select * from metadata where $query"; + $query="SELECT * FROM metadata WHERE (".join(") AND (",@queries).')'; } elsif ($customquery) { $query = ''; } + # &Apache::lonnet::logthis('query = '.$/.$query); return ($query,$customquery,$customshow,$libraries_to_query, $pretty_search_string); } @@ -1448,7 +1428,7 @@ 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'})) { + if (! exists($ENV{'form.domains'}) || $ENV{'form.domains'} eq '') { return (undef,''); } my @allowed_domains; @@ -1457,6 +1437,7 @@ sub parse_domain_restrictions { } else { @allowed_domains = ($ENV{'form.domains'}); } + # my %domain_hash = (); my $pretty_domains_string; foreach (@allowed_domains) { @@ -1497,9 +1478,10 @@ Parse the basic search form and return a ###################################################################### 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. @@ -1509,46 +1491,229 @@ sub parse_basic_search { } my ($libraries_to_query,$pretty_domains_string) = &parse_domain_restrictions(); - # Check to see if enough is filled in - unless (&filled($ENV{'form.basicexp'})) { + # + # 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 = '<b>'.$ENV{'form.basicexp'}.'</b>'; - my $search_string = $ENV{'form.basicexp'}; - if ($ENV{'form.related'}) { - my @New_Words; - ($search_string,@New_Words) = &related_version($ENV{'form.basicexp'}); - if (@New_Words) { - $pretty_search_string .= " with related words: <b>@New_Words</b>."; - } else { - $pretty_search_string .= " with no related words."; - } + 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; } - # 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_ws(" ",'.$concatarg.')',$search_string); + push(@Queries,$SQLQuery); + #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"; - my $final_query = 'SELECT * FROM metadata WHERE '.$query; -# &Apache::lonnet::logthis($final_query); + $pretty_search_string =~ s:^<br /> and ::; + # &Apache::lonnet::logthis($final_query); return ($final_query,$pretty_search_string, $libraries_to_query); } +############################################################### +############################################################### + +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 &related_version +=item &related_version() Modifies an input string to include related words. Words in the string are replaced with parenthesized lists of 'OR'd words. For example @@ -1561,45 +1726,15 @@ Note: Using this twice on a string is pr ###################################################################### ###################################################################### sub related_version { - my $search_string = shift; - my $result = $search_string; - my %New_Words = (); - while ($search_string =~ /(\w+)/cg) { - my $word = $1; - next if (lc($word) =~ /\b(or|and|not)\b/); - my @Words = &Apache::loncommon::get_related_words($word); - @Words = ($#Words>4? @Words[0..4] : @Words); - foreach (@Words) { $New_Words{$_}++;} - my $replacement = join " OR ", ($word,@Words); - $result =~ s/(\b)$word(\b)/$1($replacement)$2/g; - } - return $result,sort(keys(%New_Words)); + 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); } -###################################################################### -###################################################################### - -=pod - -=item &build_SQL_query() - -Builds a SQL query string from a logical expression with AND/OR keywords -using Text::Query and &recursive_SQL_query_builder() - -=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; -} ###################################################################### ###################################################################### @@ -1634,47 +1769,6 @@ sub build_custommetadata_query { return $matchexp; } -###################################################################### -###################################################################### - -=pod - -=item &recursive_SQL_query_build() - -Recursively constructs an SQL query. Takes as input $dkey and $pattern. - -=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 (lc($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); - } - &recursive_SQL_query_build($dkey,$pattern); -} ###################################################################### ###################################################################### @@ -1691,51 +1785,73 @@ Also reports errors (check for /^Incorre ###################################################################### ###################################################################### 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; + 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 (@queries) { - return join(" AND ",@queries); + 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); + } } - return ''; + return ($result,$error,$pretty_string); } ###################################################################### @@ -1745,6 +1861,11 @@ sub build_date_queries { =item ©right_check() +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 ###################################################################### @@ -1768,13 +1889,12 @@ sub copyright_check { return 1; } - ###################################################################### ###################################################################### =pod -=item &ensure_db_and_table +=item &ensure_db_and_table() Ensure we can get lonmysql to connect to the database and the table we need exists. @@ -1830,7 +1950,10 @@ sub ensure_db_and_table { =pod -=item &print_sort_form +=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 @@ -1838,9 +1961,14 @@ sub ensure_db_and_table { ###################################################################### 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 = - (id => 'Default', + my %SortableFields=&Apache::lonlocal::texthash( + id => 'Default', title => 'Title', author => 'Author', subject => 'Subject', @@ -1852,7 +1980,7 @@ sub print_sort_form { copyright => 'Copyright', hostname => 'Host', creationdate => 'Creation Date', - lastrevisiondate => 'Revision Date', + lastrevisiondate => 'Revision Date' ); ## my $table = $ENV{'form.table'}; @@ -1930,35 +2058,11 @@ the following format: ##################################################################### ##################################################################### - -my @Datatypes = - ( { name => 'id', - type => 'MEDIUMINT', - restrictions => 'UNSIGNED NOT NULL', - primary_key => 'yes', - auto_inc => 'yes' }, - { name => 'title', type=>'TEXT'}, - { name => 'author', type=>'TEXT'}, - { name => 'subject', type=>'TEXT'}, - { name => 'url', type=>'TEXT', restrictions => 'NOT NULL' }, - { name => 'keywords', type=>'TEXT'}, - { name => 'version', type=>'TEXT'}, - { name => 'notes', type=>'TEXT'}, - { name => 'abstract', type=>'TEXT'}, - { name => 'mime', type=>'TEXT'}, - { name => 'lang', type=>'TEXT'}, - { name => 'owner', type=>'TEXT'}, - { name => 'copyright', type=>'TEXT'}, - { name => 'hostname', type=>'TEXT'}, - #-------------------------------------------------- - { name => 'creationdate', type=>'DATETIME'}, - { name => 'lastrevisiondate', type=>'DATETIME'}, - #-------------------------------------------------- - ); - -my @Fullindicies = - qw/title/; -# qw/title author subject abstract mime language owner copyright/; +# +# These should probably be scoped but I don't have time right now... +# +my @Datatypes; +my @Fullindicies; ###################################################################### ###################################################################### @@ -1978,7 +2082,22 @@ Returns: the identifier of the table on ###################################################################### ###################################################################### +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},], @@ -2035,12 +2154,37 @@ sub update_status { $r->rflush(); } +{ + my $max_time = 40; # 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,$text) = @_; - $text =~ s/\'/\\\'/g; - $r->print - ("<script>document.statusform.seconds.value = ' $text'</script>\n"); - $r->rflush(); + 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; +} + } ###################################################################### @@ -2048,7 +2192,7 @@ sub update_seconds { =pod -=item &revise_button +=item &revise_button() Inputs: None @@ -2075,7 +2219,10 @@ sub revise_button { =pod -=item &run_search +=item &run_search() + +Executes a search query by sending it the the other servers and putting the +results into MySQL. =cut @@ -2083,13 +2230,12 @@ sub revise_button { ###################################################################### 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; # - # Timing variables - # - my $starttime = time; - my $max_time = 30; # seconds for the search to complete - # # Print run_search header # $r->print(<<END); @@ -2099,18 +2245,21 @@ $bodytag <form name="statusform" action="" method="post"> <input type="hidden" name="Queue" value="" /> END - # Check to see if $pretty_string has more than one carriage return. - # Assume \n s are following <br /> s and truncate the value. - # (there is probably a better way)... - my @Lines = split /<br \/>/,$pretty_string; + # 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 />'); + $pretty_string = join '<br />',(@Lines[0..2],'....<br />'); } - $r->print("Search: ".$pretty_string); + $r->print(&mt("Search: [_1]",$pretty_string)); $r->rflush(); # # Determine the servers we need to contact. - # my @Servers_to_contact; if (defined($serverlist)) { if (ref($serverlist) eq 'ARRAY') { @@ -2122,6 +2271,8 @@ END @Servers_to_contact = sort(keys(%Apache::lonnet::libserv)); } my %Server_status; + # + # Check on the mysql table we will use to store results. my $table =$ENV{'form.table'}; if (! defined($table) || $table eq '' || $table =~ /\D/ ) { $r->print("Unable to determine table id to store search results in.". @@ -2138,13 +2289,17 @@ END 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; } ## ## Prepare for the big loop. - ## my $hitcountsum; my $server; my $status; @@ -2162,17 +2317,18 @@ END </form> END $r->rflush(); - my $time_remaining = $max_time - (time - $starttime) ; - my $last_time = $time_remaining; - &update_seconds($r,$time_remaining); - &update_status($r,'contacting '.$Servers_to_contact[0]); - while (($time_remaining > 0) && + &reset_timing(); + &update_seconds($r); + &update_status($r,&mt('contacting [_1]',$Servers_to_contact[0])); + while (&time_left() && ((@Servers_to_contact) || keys(%Server_status))) { - # Send out a search request if it needs to be done. + &update_seconds($r); + # + # Send out a search request if (@Servers_to_contact) { # Contact one server my $server = shift(@Servers_to_contact); - &update_status($r,'contacting '.$server); + &update_status($r,&mt('contacting [_1]',$server)); my $reply=&Apache::lonnet::metadata_query($query,$customquery, $customshow,[$server]); ($server) = keys(%$reply); @@ -2184,16 +2340,17 @@ END # left to contact. if (scalar (keys(%Server_status))) { &update_status($r, - 'waiting on '.(join(' ',keys(%Server_status)))); + &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. - while (my ($server,$status) = each(%Server_status)) { + 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; @@ -2201,12 +2358,12 @@ END $status=~/^([\.\w]+)$/; my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$1; if (-e $datafile && ! -e "$datafile.end") { - &update_status($r,'Receiving results from '.$server); + &update_status($r,&mt('Receiving results from [_1]',$server)); next; } last if ($connection->aborted()); if (-e "$datafile.end") { - &update_status($r,'Reading results from '.$server); + &update_status($r,&mt('Reading results from [_1]',$server)); if (-z "$datafile") { delete($Server_status{$server}); next; @@ -2221,29 +2378,30 @@ END # Read in the whole file. while (my $result = <$fh>) { last if ($connection->aborted()); - # handle custom fields? Someday we will! + # + # Records are stored one per line chomp($result); - next unless $result; + next if (! $result); + # # Parse the result. my %Fields = &parse_raw_result($result,$server); $Fields{'hostname'} = $server; + # + # 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()); } - # $r->print(&Apache::lonmysql::get_debug()); + # $hitcountsum ++; - $time_remaining = $max_time - (time - $starttime) ; - if ($last_time - $time_remaining > 0) { - &update_seconds($r,$time_remaining); - $last_time = $time_remaining; - } + &update_seconds($r); if ($hitcountsum % 50 == 0) { &update_count_status($r,$hitcountsum); } - } # End of foreach (@results) + } $fh->close(); # $server is only deleted if the results file has been # found and (successfully) opened. This may be a bad idea. @@ -2253,35 +2411,35 @@ END &update_count_status($r,$hitcountsum); } last if ($connection->aborted()); - # Finished looping through the servers - $starttime = time if (@Servers_to_contact); - $time_remaining = $max_time - (time - $starttime) ; - if ($last_time - $time_remaining > 0) { - $last_time = $time_remaining; - &update_seconds($r,$time_remaining); - } + &update_seconds($r); } - &update_status($r,'Search Complete'.$server); - &update_seconds($r,0); - &Apache::lonmysql::disconnect_from_db(); + &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. + # 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') { +# 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 +=item &prev_next_buttons() + +Returns html for the previous and next buttons on the search results page. =cut @@ -2290,50 +2448,34 @@ END sub prev_next_buttons { my ($current_min,$show,$total,$parms) = @_; return '' if ($show eq 'all'); # No links if you get them all at once. - my $links; - ## - ## Prev - my $prev_min = $current_min - $show; - $prev_min = 1 if $prev_min < 1; - if ($prev_min < $current_min) { - $links .= qq{ -<a href="/adm/searchcat?$parms&start=$prev_min&show=$show">prev</a> -}; - } else { - $links .= 'prev'; - } - ## - ## Pages.... Someday. - ## - $links .= qq{ -<a href="/adm/searchcat?$parms&start=$current_min&$show=$show">reload</a> -}; - ## - ## Next - my $next_min = $current_min + $show; - $next_min = $current_min if ($next_min > $total); - if ($next_min != $current_min) { - $links .= qq{ -<a href="/adm/searchcat?$parms&start=$next_min&show=$show">next</a> -}; - } else { - $links .= ' next'; - } - return $links; + # + # 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 +=item &display_results() + +Prints the results out for selection and perusal. =cut ###################################################################### ###################################################################### sub display_results { - my ($r,$importbutton,$closebutton) = @_; + my ($r,$importbutton,$closebutton,$diropendb) = @_; my $connection = $r->connection; $r->print(&search_results_header($importbutton,$closebutton)); ## @@ -2354,8 +2496,7 @@ sub display_results { ## my $action = "/adm/searchcat?phase=results"; ## - ## Deal with groupsearch - ## + ## 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)) { @@ -2366,12 +2507,10 @@ sub display_results { } ## ## 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>"); @@ -2382,9 +2521,15 @@ sub display_results { } ## ## Determine how many results we need to get - ## - $ENV{'form.start'} = 1 if (! exists($ENV{'form.start'})); - $ENV{'form.show'} = 'all' if (! exists($ENV{'form.show'})); + $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') { @@ -2394,19 +2539,23 @@ sub display_results { $max = $total_results if ($max > $total_results); } ## - ## Output links (if necessary) for 'prev' and 'next' pages. + ## Output form elements + $r->print(&hidden_field('table'). + &hidden_field('phase'). + &hidden_field('persistent_db_id'). + &hidden_field('start') + ); ## + ## Output links (if necessary) for 'prev' and 'next' pages. $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" + ('<table width="100%"><tr><td width="50%" 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="1">'. - '<h3>There are currently no results.</h3>'. + $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="2">'. + '<h3>'.&mt('There are currently no results').'.</h3>'. "</form></body></html>"); return; } else { @@ -2415,12 +2564,10 @@ sub display_results { } ## ## Get results from MySQL table - ## my @Results = &Apache::lonmysql::get_rows($table, 'id>='.$min.' AND id<='.$max); ## ## Loop through the results and output them. - ## foreach my $row (@Results) { if ($connection->aborted()) { &cleanup(); @@ -2428,6 +2575,9 @@ sub display_results { } 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 @@ -2437,7 +2587,7 @@ sub display_results { $r->rflush(); } if (@Results < 1) { - $r->print("There were no results matching your query"); + $r->print(&mt("There were no results matching your query")); } else { $r->print ('<center>'. @@ -2498,7 +2648,7 @@ END =pod -=item &parse_row +=item &parse_row() Parse a row returned from the database. @@ -2509,11 +2659,14 @@ Parse a row returned from the database. 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{'lang'}); + &Apache::loncommon::languagedescription($Fields{'language'}); $Fields{'copyrighttag'} = &Apache::loncommon::copyrightdescription($Fields{'copyright'}); $Fields{'mimetag'} = @@ -2529,13 +2682,10 @@ sub parse_row { =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. @@ -2547,63 +2697,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'}); - if ($Fields{'author'}=~/^(\s*|error)$/) { - $Fields{'author'}="Unknown Author"; - } - # 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'}) { - # What is this anyway? - $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; } @@ -2653,7 +2751,7 @@ 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. @@ -2680,8 +2778,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 @@ -2690,6 +2796,7 @@ 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 # conditional output of script functions dependent on the mode in @@ -2718,14 +2825,31 @@ SCRIPT } elsif ($ENV{'form.mode'} eq 'edit') { my $form = $ENV{'form.form'}; my $element = $ENV{'form.element'}; + my $titleelement = $ENV{'form.titleelement'}; + my $changetitle; + if (!$titleelement) { + $changetitle='function changeTitle(val) {}'; + } else { + $changetitle=<<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); + changeTitle(title); parent.close(); } -function changeTitle(val) { -} +$changetitle function changeURL(val) { if (parent.targetwin.document) { parent.targetwin.document.forms["$form"].elements["$element"].value=val; @@ -2741,10 +2865,19 @@ SCRIPT $result.=<<SCRIPT if $ENV{'form.catalogmode'} eq 'groupsearch'; <script type="text/javascript"> function queue(checkbox_num,val) { - if (document.forms.results.returnvalues[checkbox_num].checked) { - parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b'; + 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 { - parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b'; + 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() { @@ -2757,7 +2890,7 @@ SCRIPT $result.=<<END; </head> $bodytag -<form name="results" method="post" action="" > +<form name="results" method="post" action="/adm/searchcat" > <input type="hidden" name="Queue" value="" /> $importbutton END @@ -2767,6 +2900,7 @@ END ###################################################################### ###################################################################### sub search_status_header { + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); return <<ENDSTATUS; <html><head><title>Search Status</title></head> $bodytag @@ -2814,11 +2948,47 @@ ENDFRAMES ###################################################################### ###################################################################### +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; + } + 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'); +} + +###################################################################### +###################################################################### + =pod =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 @@ -2834,25 +3004,108 @@ extra custom metadata to show. ###################################################################### sub detailed_citation_view { my ($prefix,%values) = @_; - my $result=<<END; -<b>$prefix<a href="http://$ENV{'HTTP_HOST'}$values{'url'}" - target='search_preview'>$values{'title'}</a></b> -<p> -<b>$values{'author'}</b>, <i>$values{'owner'}</i><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>Copyright/Distribution:</b> $values{'cprtag'}<br /> -</p> -$values{'extrashow'} -<p> -$values{'shortabstract'} -</p> -<hr align='left' width='200' noshade /> -END + my $result; + $result .= '<b>'.$prefix. + '<img src="'.&Apache::loncommon::icon($values{'url'}).' " />'.' '. + '<a href="http://'.$ENV{'HTTP_HOST'}.$values{'url'}.'" '. + '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="/res/'.$item.'">'.$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="'.$values{'url'}.'" '. + '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; } @@ -2868,8 +3121,10 @@ END ###################################################################### sub summary_view { my ($prefix,%values) = @_; + my $icon=&Apache::loncommon::icon($values{'url'}); my $result=<<END; -$prefix<a href="http://$ENV{'HTTP_HOST'}$values{'url'}" +$prefix<img src="$icon" /> +<a href="http://$ENV{'HTTP_HOST'}$values{'url'}" target='search_preview'>$values{'author'}</a><br /> $values{'title'}<br /> $values{'owner'} -- $values{'lastrevisiondate'}<br /> @@ -2894,11 +3149,11 @@ END ###################################################################### sub compact_view { my ($prefix,%values) = @_; - my $result=<<END; -$prefix <a href="http://$ENV{'HTTP_HOST'}$values{'url'}" target='search_preview'> -$values{'title'}</a> -<b>$values{'author'}</b><br /> -END + my $result = + $prefix.'<img src="'.&Apache::loncommon::icon($values{'url'}). + '"> <a href="'.$values{'url'}.'" target="search_preview">'. + $values{'title'}.'</a>'.(' 'x2). + '<b>'.$values{'author'}.'</b><br />'; return $result; } @@ -2916,28 +3171,36 @@ END ###################################################################### sub fielded_format_view { my ($prefix,%values) = @_; + my $icon=&Apache::loncommon::icon($values{'url'}); + my %Translated = &Apache::lonmeta::fieldnames(); my $result=<<END; -$prefix -<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> -<hr align='left' width='200' noshade /> -END +$prefix <img src="$icon" /> +<dl> +<dt>URL:</dt> + <dd><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" + target='search_preview'>$values{'url'}</a></dd> +END + foreach my $field ('title','author','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; } @@ -2956,34 +3219,59 @@ END ###################################################################### sub xml_sgml_view { my ($prefix,%values) = @_; + my $xml = '<LonCapaResource>'."\n"; + # The usual suspects + foreach my $field ('url','title','author','subject','keywords','notes') { + $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 /> @@ -3005,11 +3293,55 @@ 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 + $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 } ###################################################################### @@ -3033,33 +3365,32 @@ $parms is extra information to include i ###################################################################### ###################################################################### sub output_blank_field_error { - my ($r,$closebutton,$parms)=@_; - # make query information persistent to allow for subsequent revision - $r->print(<<BEGINNING); + 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'); + $r->print(<<ENDPAGE); <html> <head> <title>The LearningOnline Network with CAPA</title> -BEGINNING - $r->print(<<RESULTS); </head> $bodytag -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> <form method="post" action="/adm/searchcat"> $hidden_fields -<a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}" ->Revise search request</a> $closebutton <hr /> -<h3>Unactionable search query.</h3> +<h2>$heading</h2> +<p> +$errormsg +</p> <p> -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. +<a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}">$revise</a> </p> </body> </html> -RESULTS +ENDPAGE + return; } ###################################################################### @@ -3082,8 +3413,9 @@ Inputs: ###################################################################### ###################################################################### sub output_date_error { - my ($r,$message,$closebutton)=@_; + my ($r,$message,$closebutton,$hidden_fields)=@_; # make query information persistent to allow for subsequent revision + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); $r->print(<<RESULTS); <html> <head> @@ -3137,7 +3469,6 @@ sub start_fresh_session { sub cleanup { if (tied(%groupsearch_db)) { - &Apache::lonnet::logthis('Cleanup searchcat: groupsearch_db'); unless (untie(%groupsearch_db)) { &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db'); }