--- loncom/interface/lonsearchcat.pm 2004/05/05 17:29:06 1.223 +++ loncom/interface/lonsearchcat.pm 2004/07/19 16:38:07 1.231 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.223 2004/05/05 17:29:06 matthew Exp $ +# $Id: lonsearchcat.pm,v 1.231 2004/07/19 16:38:07 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -75,6 +75,8 @@ use Apache::lonhtmlcommon; use Apache::lonlocal; use LONCAPA::lonmetadata(); use HTML::Entities(); +use Parse::RecDescent; +use Apache::lonnavmaps; ###################################################################### ###################################################################### @@ -174,6 +176,7 @@ sub handler { '&launch='.$ENV{'form.launch'}. '&mode='.$ENV{'form.mode'}, text=>"Course and Catalog Search", + target=>'_top', bug=>'Searching',}); } else { &Apache::lonhtmlcommon::add_breadcrumb @@ -182,6 +185,7 @@ sub handler { '&launch='.$ENV{'form.launch'}. '&mode='.$ENV{'form.mode'}, text=>"Catalog Search", + target=>'_top', bug=>'Searching',}); } # @@ -335,6 +339,29 @@ END &course_search($r); } elsif(($ENV{'form.phase'} eq 'basic_search') || ($ENV{'form.phase'} eq 'adv_search')) { + # + # We are running a search, try to parse it + my ($query,$customquery,$customshow,$libraries) = + (undef,undef,undef,undef); + my $pretty_string; + if ($ENV{'form.phase'} eq 'basic_search') { + ($query,$pretty_string,$libraries) = + &parse_basic_search($r,$closebutton,$hidden_fields); + return OK if (! defined($query)); + &make_persistent({ basicexp => $ENV{'form.basicexp'}}, + $persistent_db_file); + } else { # Advanced search + ($query,$customquery,$customshow,$libraries,$pretty_string) + = &parse_advanced_search($r,$closebutton,$hidden_fields); + return OK if (! defined($query)); + } + &make_persistent({ query => $query, + customquery => $customquery, + customshow => $customshow, + libraries => $libraries, + pretty_string => $pretty_string }, + $persistent_db_file); + # # Set up table if (! defined(&create_results_table())) { my $errorstring=&Apache::lonmysql::get_error(); @@ -362,29 +389,12 @@ 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,$hidden_fields); - } 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); ## ## Print out the frames interface ## - &print_frames_interface($r); + if (defined($query)) { + &print_frames_interface($r); + } } return OK; } @@ -454,10 +464,11 @@ sub course_search { } } my $fulltext=$ENV{'form.crsfulltext'}; + my $discuss=$ENV{'form.crsdiscuss'}; my @allwords=($search_string,@New_Words); $totalfound=0; $r->print('<html><head><title>LON-CAPA Course Search</title></head>'. - $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr />'); + $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr /><b>'.&mt('Course content').':</b><br />'); $r->rflush(); # ======================================================= Go through the course undef %alreadyseen; @@ -475,8 +486,83 @@ sub course_search { untie(%hash); } unless ($totalfound) { - $r->print('<p>'.&mt('No resources found').'.</p>'); + $r->print('<p>'.&mt('No matches found in resources').'.</p>'); + } + +# Check discussions if requested + if ($discuss) { + my $totaldiscussions = 0; + $r->print('<br /><br /><b>'.&mt('Discussion postings').':</b><br />'); + my $navmap = Apache::lonnavmaps::navmap->new(); + my @allres=$navmap->retrieveResources(); + my %discussiontime = &Apache::lonnet::dump('discussiontimes', + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); + foreach my $resource (@allres) { + my $result = ''; + my $applies = 0; + my $symb = $resource->symb(); + my $ressymb = $symb; + if ($symb =~ m#(___adm/\w+/\w+)/(\d+)/bulletinboard$#) { + $ressymb = 'bulletin___'.$2.$1.'/'.$2.'/bulletinboard'; + unless ($ressymb =~ m#bulletin___\d+___adm/wrapper#) { + $ressymb=~s#(bulletin___\d+___)#$1adm/wrapper/#; + } + } + if (defined($discussiontime{$ressymb})) { + my %contrib = &Apache::lonnet::restore($ressymb,$ENV{'request.course.id'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.domain'}, + $ENV{'course.'.$ENV{'request.course.id'}.'.num'}); + if ($contrib{'version'}) { + for (my $id=1;$id<=$contrib{'version'};$id++) { + unless (($contrib{'hidden'}=~/\.$id\./) || ($contrib{'deleted'}=~/\.$id\./)) { + if ($contrib{$id.':subject'}) { + $result .= $contrib{$id.':subject'}; + } + if ($contrib{$id.':message'}) { + $result .= $contrib{$id.':message'}; + } + if ($contrib{$id,':attachmenturl'}) { + if ($contrib{$id,':attachmenturl'} =~ m-/([^/]+)$-) { + $result .= $1; + } + } + $applies = &checkwords($result,$applies,@allwords); + } + } + } + } +# Does this discussion apply? + if ($applies) { + my ($map,$ind,$url)=&Apache::lonnet::decode_symb($ressymb); + my $disctype = &mt('resource'); + if ($url =~ m#/bulletinboard$#) { + if ($url =~m#^adm/wrapper/adm/.*/bulletinboard$#) { + $url =~s#^adm/wrapper##; + } + $disctype = &mt('bulletin board'); + } else { + $url = '/res/'.$url; + } + if ($url =~ /\?/) { + $url .= '&symb='; + } else { + $url .= '?symb='; + } + $url .= &Apache::lonnet::escape($resource->symb()); + my $title = $resource->compTitle(); + $r->print('<br /><a href="'.$url.'" target="cat">'. + ($title?$title:$url).'</a> - '.$disctype.'<br />'); + $totaldiscussions++; + } else { + $r->print(' .'); + } + } + unless ($totaldiscussions) { + $r->print('<p>'.&mt('No matches found in postings').'.</p>'); + } } + # =================================================== Done going through course $r->print('</body></html>'); } @@ -495,14 +581,8 @@ sub checkonthis { $result.=&Apache::lonnet::ssi_body($url); } $result=~s/\s+/ /gs; - my $applies=0; - foreach (@allwords) { - if ($_=~/\w/) { - if ($result=~/$_/si) { - $applies++; - } - } - } + my $applies = 0; + $applies = &checkwords($result,$applies,@allwords); # Does this resource apply? if ($applies) { $r->print('<br />'); @@ -526,6 +606,18 @@ sub checkonthis { } } +sub checkwords { + my ($result,$applies,@allwords) = @_; + foreach (@allwords) { + if ($_=~/\w/) { + if ($result=~/$_/si) { + $applies++; + } + } + } + return $applies; +} + sub untiehash { if (tied(%hash)) { untie(%hash); @@ -559,9 +651,11 @@ Prints the form for the basic search. S ###################################################################### 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','Searching', - undef,undef,! $ENV{'form.launch'}); + &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 @@ -599,8 +693,11 @@ sub print_basic_search_form { } $scrout.='<table>'. '<tr><td align="center" valign="top">'. - &Apache::lonhtmlcommon::textbox('basicexp', - $ENV{'form.basicexp'},50).'<br />'. + &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 />'. @@ -609,12 +706,6 @@ sub print_basic_search_form { '</font></td>'. '</tr>'.$/; # -# $scrout .= '<tr><td align="center">'. -# '<font size="-1">'. -# $userelatedwords.(' 'x3). -# $onlysearchdomain.(' 'x2).$adv_search_link. -# '</font>'. -# '</td></tr>'.$/; $scrout .= '<tr><td align="center" colspan="2">'. '<font size="-1">'. '<input type="submit" name="basicsubmit" '. @@ -630,7 +721,8 @@ sub print_basic_search_form { 'header' => 'Course Search', 'note' => 'Enter terms or phrases, then press "Search" below', 'use' => 'use related words', - 'full' =>'fulltext search (time consuming)' + 'full' =>'fulltext search (time consuming)', + 'disc' => 'search discussion postings (resources and bulletin boards)', ); $scrout.=(<<ENDCOURSESEARCH); <form name="loncapa_search" method="post" action="/adm/searchcat"> @@ -655,10 +747,14 @@ ENDCOURSESEARCH my $relcheckbox = &Apache::lonhtmlcommon::checkbox('crsrelated', $ENV{'form.crsrelated'}); + my $discheckbox = + &Apache::lonhtmlcommon::checkbox('crsdiscuss', + $ENV{'form.crsrelated'}); $scrout.=(<<ENDENDCOURSE); </td></tr> <tr><td>$relcheckbox $lt{'use'}</td><td></td></tr> <tr><td>$crscheckbox $lt{'full'}</td><td></td></tr> +<tr><td>$discheckbox $lt{'disc'}</td><td></td></tr> </table><p> <input type="submit" name="coursesubmit" value='$lt{'srch'}' /> </p> @@ -690,10 +786,9 @@ sub print_advanced_search_form{ my ($r,$closebutton,$hidden_fields) = @_; my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search'). &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching', - 'Searching', + 'Search_Advanced', undef,undef, - ! $ENV{'form.launch'}); - + $ENV{'form.catalogmode'} ne 'groupsearch'); my %lt=&Apache::lonlocal::texthash('srch' => 'Search', 'reset' => 'Reset', 'help' => 'Help'); @@ -774,7 +869,7 @@ ENDHEADER $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) @@ -782,7 +877,7 @@ ENDHEADER $scrout.='<tr><td align="right" valign="top">'. &titlefield(&mt('Language')).'</td><td colspan="2">'. &Apache::lonmeta::selectbox('language', - 'notset',, + $ENV{'form.language'}, \&Apache::loncommon::languagedescription, ('any',&Apache::loncommon::languageids) ).'</td></tr>'; @@ -997,7 +1092,7 @@ Outputs: return little blurb on how to e ###################################################################### ###################################################################### sub searchhelp { - return &mt('Enter terms or phrases separated by AND, OR, or NOT'); + return &mt('Enter words and quoted phrases'); } ###################################################################### @@ -1188,13 +1283,13 @@ Parse advanced search form and return th sub parse_advanced_search { my ($r,$closebutton,$hidden_fields)=@_; my @BasicFields = ('title','author','subject','keywords','url','version', - 'notes','abstract','extension','owner', + '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 (@BasicFields, 'creationdatestart_month','creationdatestart_day', @@ -1204,7 +1299,6 @@ sub parse_advanced_search { 'lastrevisiondatestart_year','lastrevisiondateend_month', 'lastrevisiondateend_day','lastrevisiondateend_year') { $ENV{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\']//g; - $ENV{'form.'.$field}=~s/(not\s*$|^\s*(and|or)|)//gi; } foreach ('mode','form','element') { # is this required? Hmmm. @@ -1248,22 +1342,30 @@ sub parse_advanced_search { my $font = '<font color="#800000" face="helvetica">'; # Evaluate logical expression AND/OR/NOT phrase fields. foreach my $field (@BasicFields) { - if ($ENV{'form.'.$field}) { - my $searchphrase = $ENV{'form.'.$field}; - $pretty_search_string .= $font."$field</font> contains <b>". - $searchphrase."</b>"; + 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); } } # @@ -1280,7 +1382,8 @@ sub parse_advanced_search { } } 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 />'; } @@ -1312,7 +1415,7 @@ 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 @@ -1400,11 +1503,11 @@ sub parse_advanced_search { $pretty_search_string .= $pretty_domains_string."<br />\n"; # if (@queries) { - $query="select * from metadata where ".join(" AND ",@queries); + $query="SELECT * FROM metadata WHERE (".join(") AND (",@queries).')'; } elsif ($customquery) { $query = ''; } -# &Apache::lonnet::logthis('query = '.$/.$query); + # &Apache::lonnet::logthis('query = '.$/.$query); return ($query,$customquery,$customshow,$libraries_to_query, $pretty_search_string); } @@ -1466,7 +1569,7 @@ sub parse_basic_search { # # 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. @@ -1479,39 +1582,220 @@ sub parse_basic_search { # # Check to see if enough of a query is filled in my $search_string = $ENV{'form.basicexp'}; - $search_string =~ s/(not\s*$|^\s*(and|or)|)//gi; if (! &filled($search_string)) { &output_blank_field_error($r,$closebutton,'phase=disp_basic'); return OK; } - my $pretty_search_string = '<b>'.$ENV{'form.basicexp'}.'</b>'; - 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; } + push(@Queries,$SQLQuery); + #foreach my $q (@Queries) { + # &Apache::lonnet::logthis(' '.$q); + #} + my $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries); # - # 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); 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; + $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; +} + ###################################################################### ###################################################################### @@ -1530,45 +1814,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; -} ###################################################################### ###################################################################### @@ -1603,47 +1857,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); -} ###################################################################### ###################################################################### @@ -1836,7 +2049,11 @@ a link to change the search query. ###################################################################### sub print_sort_form { my ($r,$pretty_query_string) = @_; - my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1). + &Apache::lonhtmlcommon::breadcrumbs + (undef,'Searching','Searching',undef,undef, + $ENV{'form.catalogmode'} ne 'groupsearch'); + ## my %SortableFields=&Apache::lonlocal::texthash( id => 'Default', @@ -2102,6 +2319,9 @@ results into MySQL. sub run_search { my ($r,$query,$customquery,$customshow,$serverlist,$pretty_string) = @_; my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + $bodytag.=&Apache::lonhtmlcommon::breadcrumbs + (undef,'Searching','Searching',undef,undef, + $ENV{'form.catalogmode'} ne 'groupsearch'); my $connection = $r->connection; # # Print run_search header @@ -2113,12 +2333,16 @@ $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(&mt("Search: [_1]",$pretty_string)); $r->rflush(); @@ -2211,9 +2435,10 @@ END # # 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; @@ -2285,13 +2510,13 @@ END # 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; } @@ -2811,6 +3036,41 @@ 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 @@ -2858,41 +3118,73 @@ sub detailed_citation_view { { 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', - translate => '<b>Correcy:</b> [_1]',}, + 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',}, ) { - if (exists($values{$field->{'name'}}) && - $values{$field->{'name'}} ne '') { + 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>'); + &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 '') { @@ -2908,41 +3200,6 @@ sub detailed_citation_view { ###################################################################### ###################################################################### -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 &summary_view() @@ -3135,6 +3392,51 @@ sub filled { =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 +} + +###################################################################### +###################################################################### + +=pod + =item &output_blank_field_error() Output a complete page that indicates the user has not filled in enough @@ -3152,33 +3454,31 @@ $parms is extra information to include i ###################################################################### sub output_blank_field_error { my ($r,$closebutton,$parms,$hidden_fields)=@_; - my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); - # make query information persistent to allow for subsequent revision - $r->print(<<BEGINNING); + 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> -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. +$errormsg +</p> +<p> +<a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}">$revise</a> </p> </body> </html> -RESULTS +ENDPAGE + return; } ######################################################################