--- loncom/interface/lonsearchcat.pm 2004/04/19 17:40:00 1.210 +++ loncom/interface/lonsearchcat.pm 2005/03/21 21:01:15 1.240 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.210 2004/04/19 17:40:00 matthew Exp $ +# $Id: lonsearchcat.pm,v 1.240 2005/03/21 21:01:15 matthew Exp $ # # Copyright Michigan State University Board of Trustees # @@ -74,6 +74,9 @@ use Apache::lonmeta; use Apache::lonhtmlcommon; use Apache::lonlocal; use LONCAPA::lonmetadata(); +use HTML::Entities(); +use Parse::RecDescent; +use Apache::lonnavmaps; ###################################################################### ###################################################################### @@ -116,7 +119,7 @@ sub handler { 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. @@ -149,7 +152,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. @@ -172,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 @@ -180,24 +185,32 @@ sub handler { '&launch='.$ENV{'form.launch'}. '&mode='.$ENV{'form.mode'}, text=>"Catalog Search", + target=>'_top', bug=>'Searching',}); } # - 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(< -LON-CAPA Search Error + if ($ENV{'form.phase'} !~ m/(basic|adv|course)_search/) { + if (! &get_persistent_form_data($persistent_db_file)) { + if ($ENV{'form.phase'} =~ /(run_search|results)/) { + &Apache::lonnet::logthis('lonsearchcat:'. + 'Unable to recover data from '. + $persistent_db_file); + my $html=&Apache::lonxml::xmlbegin(); + $r->print(< +LON-CAPA Search Error $bodytag We were unable to retrieve data describing your search. This is a serious error and has been logged. Please alert your LON-CAPA administrator. END - return OK; + return OK; + } } + } else { + &clean_up_environment(); } ## ## Clear out old values from groupsearch database @@ -213,7 +226,8 @@ END } else { # This is a stupid error to give to the user. # It really tells them nothing. - $r->print(''.$bodytag. + my $html=&Apache::lonxml::xmlbegin(); + $r->print($html.''.$bodytag. 'Unable to tie hash to db file'); return OK; } @@ -224,24 +238,19 @@ END $hidden_fields = ''."\n"; if (exists($ENV{'form.catalogmode'})) { - $hidden_fields .= ''."\n"; + $hidden_fields .= &hidden_field('catalogmode'); } if (exists($ENV{'form.form'})) { - $hidden_fields .= ''."\n"; + $hidden_fields .= &hidden_field('form'); } if (exists($ENV{'form.element'})) { - $hidden_fields .= ''."\n"; + $hidden_fields .= &hidden_field('element'); } if (exists($ENV{'form.titleelement'})) { - $hidden_fields .= ''."\n"; + $hidden_fields .= &hidden_field('titleelement'); } if (exists($ENV{'form.mode'})) { - $hidden_fields .= ''."\n"; + $hidden_fields .= &hidden_field('mode'); } ## ## Configure dynamic components of interface @@ -284,7 +293,7 @@ END $ENV{'form.phase'} = 'disp_basic' if (! exists($ENV{'form.phase'})); $ENV{'form.show'} = 20 if (! exists($ENV{'form.show'})); # - $ENV{'form.searchmode'} = 'basic'; + $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'; @@ -333,15 +342,43 @@ 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(); + &Apache::lonnet::logthis('lonsearchcat.pm: Unable to create '. + 'needed table. lonmysql error:'. + $errorstring); + my $html=&Apache::lonxml::xmlbegin(); $r->print(<Search Error +$html + +Search Error $bodytag Unable to create table in which to store search results. The search has been aborted. -
$errorstring END @@ -349,8 +386,11 @@ END } delete($ENV{'form.launch'}); if (! &make_form_data_persistent($r,$persistent_db_file)) { - $r->print(<Search Error + my $html=&Apache::lonxml::xmlbegin(); + $r->print(< +Search Error $bodytag Unable to properly store search information. The search has been aborted. @@ -358,33 +398,52 @@ 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; } +# +# 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 ''.$/; +} + ###################################################################### ###################################################################### ## @@ -399,13 +458,22 @@ my %alreadyseen; my %hash; my $totalfound; +sub make_symb { + my ($id)=@_; + my ($mapid,$resid)=split(/\./,$id); + my $map=$hash{'map_id_'.$mapid}; + my $res=$hash{'src_'.$id}; + my $symb=&Apache::lonnet::encode_symb($map,$resid,$res); + return $symb; +} + sub course_search { my $r=shift; - my $bodytag=&Apache::loncommon::bodytag('Course Search'). - &Apache::loncommon::help_open_bug('Searching'); + my $bodytag=&Apache::loncommon::bodytag('Course Search'); my $pretty_search_string = ''.$ENV{'form.courseexp'}.''; my $search_string = $ENV{'form.courseexp'}; my @New_Words; + undef(%alreadyseen); if ($ENV{'form.crsrelated'}) { ($search_string,@New_Words) = &related_version($ENV{'form.courseexp'}); if (@New_Words) { @@ -415,29 +483,108 @@ sub course_search { } } my $fulltext=$ENV{'form.crsfulltext'}; + my $discuss=$ENV{'form.crsdiscuss'}; my @allwords=($search_string,@New_Words); $totalfound=0; - $r->print('LON-CAPA Course Search'. - $bodytag.'
'.$pretty_search_string.'

'); + my $html=&Apache::lonxml::xmlbegin(); + $r->print($html.'LON-CAPA Course Search'. + $bodytag.'
'.$pretty_search_string.'

'.&mt('Course content').':
'); $r->rflush(); # ======================================================= Go through the course - undef %alreadyseen; - %alreadyseen=(); my $c=$r->connection; if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db", &GDBM_READER(),0640)) { - foreach (keys %hash) { + foreach (sort(keys(%hash))) { if ($c->aborted()) { last; } - if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) { - &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext, - @allwords); + if (($_=~/^src\_(.+)$/)) { + if ($hash{'randomout_'.$1} & !$ENV{'request.role.adv'}) { + next; + } + my $symb=&make_symb($1); + &checkonthis($r,$1,$hash{$_},0,&Apache::lonnet::gettitle($symb), + $fulltext,$symb,@allwords); } } untie(%hash); } unless ($totalfound) { - $r->print('

'.&mt('No resources found').'.

'); + $r->print('

'.&mt('No matches found in resources').'.

'); + } + +# Check discussions if requested + if ($discuss) { + my $totaldiscussions = 0; + $r->print('

'.&mt('Discussion postings').':
'); + 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('
'. + ($title?$title:$url).'  - '.$disctype.'
'); + $totaldiscussions++; + } else { + $r->print(' .'); + } + } + unless ($totaldiscussions) { + $r->print('

'.&mt('No matches found in postings').'.

'); + } } + # =================================================== Done going through course $r->print(''); } @@ -445,33 +592,41 @@ sub course_search { # =============================== This pulls up a resource and its dependencies sub checkonthis { - my ($r,$url,$level,$title,$fulltext,@allwords)=@_; - $alreadyseen{$url}=1; + my ($r,$id,$url,$level,$title,$fulltext,$symb,@allwords)=@_; + $alreadyseen{$id}=1; + if (&Apache::loncommon::connection_aborted($r)) { return; } $r->rflush(); - my $result=&Apache::lonnet::metadata($url,'title').' '. - &Apache::lonnet::metadata($url,'subject').' '. - &Apache::lonnet::metadata($url,'abstract').' '. - &Apache::lonnet::metadata($url,'keywords'); - if (($url) && ($fulltext)) { - $result.=&Apache::lonnet::ssi_body($url); + + my $result=$title.' '; + if ($ENV{'request.role.adv'} || !$hash{'encrypted_'.$id}) { + $result.=&Apache::lonnet::metadata($url,'title').' '. + &Apache::lonnet::metadata($url,'subject').' '. + &Apache::lonnet::metadata($url,'abstract').' '. + &Apache::lonnet::metadata($url,'keywords'); + } + my ($extension)=($url=~/\.(\w+)$/); + if (&Apache::loncommon::fileembstyle($extension) eq 'ssi' && + ($url) && ($fulltext)) { + $result.=&Apache::lonnet::ssi_body($url.'?symb='.&Apache::lonnet::escape($symb)); } $result=~s/\s+/ /gs; - my $applies=0; - foreach (@allwords) { - if ($_=~/\w/) { - if ($result=~/$_/si) { - $applies++; - } - } - } + my $applies = 0; + $applies = &checkwords($result,$applies,@allwords); # Does this resource apply? if ($applies) { $r->print('
'); for (my $i=0;$i<=$level*5;$i++) { $r->print(' '); } - $r->print(''. - ($title?$title:$url).'
'); + my $href=$url; + if ($hash{'encrypted_'.$id} && !$ENV{'request.role.adv'}) { + $href=&Apache::lonenc::encrypted($href) + .'?symb='.&Apache::lonenc::encrypted($symb); + } else { + $href.='?symb='.&Apache::lonnet::escape($symb); + } + $r->print(''.($title?$title:$url). + '
'); $totalfound++; } elsif ($fulltext) { $r->print(' .'); @@ -481,12 +636,24 @@ sub checkonthis { my $dependencies= &Apache::lonnet::metadata($url,'dependencies'); foreach (split(/\,/,$dependencies)) { - if (($_=~/^\/res\//) && (!$alreadyseen{$_})) { - &checkonthis($r,$_,$level+1,'',$fulltext,@allwords); + if (($_=~/^\/res\//) && (!$alreadyseen{$id})) { + &checkonthis($r,$id,$_,$level+1,'',$fulltext,undef,@allwords); } } } +sub checkwords { + my ($result,$applies,@allwords) = @_; + foreach (@allwords) { + if ($_=~/\w/) { + if ($result=~/$_/si) { + $applies++; + } + } + } + return $applies; +} + sub untiehash { if (tied(%hash)) { untie(%hash); @@ -496,17 +663,11 @@ sub untiehash { } # End of course search scoping sub search_html_header { + my $html=&Apache::lonxml::xmlbegin(); my $Str = < +$html The LearningOnline Network with CAPA - ENDHEADER return $Str; @@ -527,69 +688,84 @@ 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','Finding_Resources', - 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'})) { - my $Statement=&searchhelp(); - $scrout.=(< - -$hidden_fields -

-$Statement. -

-

- - - - -
-ENDDOCUMENT - $scrout.=' '. - &Apache::lonhtmlcommon::textbox('basicexp', - $ENV{'form.basicexp'},40). - ' '; - my $relatedcheckbox = - &Apache::lonhtmlcommon::checkbox('related', - $ENV{'form.related'}); - my $domain = $r->dir_config('lonDefDomain'); - my $domaincheckbox = - &Apache::lonhtmlcommon::checkbox('domains', - $ENV{'form.domains'}); - my $srch=&mt('Search'); - my $header=&mt('Advanced Search'); - my $userelatedwords=&mt('use related words'); - my $onlysearchdomain=&mt('only search domain'); - my $view=&viewoptions(); - $scrout.=<$header
$relatedcheckbox $userelatedwords$domaincheckbox $onlysearchdomain $domain
-

-$view -

  -$closebutton -END - $scrout.=< - -ENDDOCUMENT + # Define interface components + my $userelatedwords= ''; + my $onlysearchdomain=''; + my $adv_search_link = + ''.&mt('Advanced Search').''; + # + $scrout.='

'. + ''. + $hidden_fields; + # + $scrout .= '
'.$/; + if ($ENV{'request.course.id'}) { + $scrout .= '

'.&mt('LON-CAPA Catalog Search').'

'; + } else { + # No need to tell them they are searching + $scrout.= ('
'x2); + } + $scrout.=''. + ''. + ''. + ''.$/; + # + $scrout .= ''.$/; + $scrout .= '
'. + &Apache::lonhtmlcommon::textbox + ('basicexp', + &HTML::Entities::encode($ENV{'form.basicexp'},'<>&"'),50 + ). + '
'. + ''.&searchhelp().''.'
'. + ''.(' 'x3).$adv_search_link.''.'
'. + ''.(' 'x1).$userelatedwords.''.'
'. + ''.(' 'x1).$onlysearchdomain.''.'
'. + '
'. + ''. + ''. + (' 'x2).$closebutton.(' 'x2). + &viewoptions(). + ''. + '
'.$/.'
'.'
'; } if ($ENV{'request.course.id'}) { - my %lt=&Apache::lonlocal::texthash( - 'srch' => 'Search', + 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)' + 'full' =>'fulltext search (time consuming)', + 'disc' => 'search discussion postings (resources and bulletin boards)', ); $scrout.=(< +

$lt{'header'}

-
$hidden_fields

@@ -608,13 +784,19 @@ ENDCOURSESEARCH my $relcheckbox = &Apache::lonhtmlcommon::checkbox('crsrelated', $ENV{'form.crsrelated'}); + my $discheckbox = + &Apache::lonhtmlcommon::checkbox('crsdiscuss', + $ENV{'form.crsrelated'}); $scrout.=(< -$relcheckbox $lt{'use'} -$crscheckbox $lt{'full'} + + +

 

+
+ ENDENDCOURSE } $scrout.=(< 'Search', 'reset' => 'Reset', 'help' => 'Help'); - my $advanced_buttons = <<"END"; -

+ my $advanced_buttons=<<"END"; $closebutton - END - my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search'). - &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching', - 'Finding_Resources', - undef,undef, - ! $ENV{'form.launch'}); - my $searchhelp=&searchhelp(); my $scrout=&search_html_header(); $scrout .= <<"ENDHEADER"; $bodytag

+

$advanced_buttons ENDHEADER $scrout.=(' 'x2).&viewoptions().'

'.$hidden_fields. ''; my %fields=&Apache::lonmeta::fieldnames(); # - $scrout.= '

'.$searchhelp.'

'. - "\n"; + $scrout .= '

'.&mt('Standard Metadata').'

'; + $scrout .= "
\n"; + $scrout .= '\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, + ('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', @@ -719,18 +901,127 @@ ENDHEADER &Apache::loncommon::domain_select('domains', $ENV{'form.domains'},1). ''.$/; - $scrout .= "
 '. + (' 'x2).&searchhelp()."
\n
\n\n"; - my %dates=&Apache::lonlocal::texthash - ('creationdatestart' => 'Creation Date After', - 'creationdateend' => 'Creation Date Before', - 'lastrevisiondatestart' => 'Last Revision Date After', - 'lastrevisiondateend' => 'Last Revision Date Before'); - foreach my $field (sort keys %dates) { - $scrout.=''. - ''.$/; - } + # + # Misc metadata + $scrout.=''.$/; + $scrout.=''; + $scrout .= "
'.&titlefield($dates{$field}).''. - &Apache::lonhtmlcommon::date_setter('advsearch',$field,0,'',1). - '
'. + &titlefield(&mt('Copyright/Distribution')).''. + &Apache::lonmeta::selectbox('copyright', + $ENV{'form.copyright'}, + \&Apache::loncommon::copyrightdescription, + ( undef, + &Apache::loncommon::copyrightids) + ).'
'. + &titlefield(&mt('Language')).''. + &Apache::lonmeta::selectbox('language', + $ENV{'form.language'}, + \&Apache::loncommon::languagedescription, + ('any',&Apache::loncommon::languageids) + ).'
\n"; + # + # Dynamic metadata + $scrout .= '

'.&mt('Problem Statistics').'

'; + $scrout .= "\n"; + $scrout .= ''. + ''."\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 .= ''.$/; + } + $scrout .= "
 '.&mt('Minimum').''.&mt('Maximum').'
'. + &titlefield(&mt($statistic->{'description'})). + ''. + ''. + ''. + ''. + '
\n"; + $scrout .= '

'.&mt('Evaluation Data').'

'; + $scrout .= "\n"; + $scrout .= ''. + ''."\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 .= ''.$/; + } + $scrout .= "
 '.&mt('Minimum').''.&mt('Maximum').'
'. + &titlefield(&mt($evaluation->{'description'})). + ''. + ''. + ''. + ''. + '
\n"; + # + # Creation/Modification date limits + $scrout .= '

'.&mt('Creation and Modification dates').'

'; + $scrout .= "\n\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(''. + ''. + ''. + '',$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(''. + ''. + ''. + '',$lafter,$lbefore); $scrout.="
Created between[_1]
and [_2]
Last modified between [_1]
and[_2]
\n"; $scrout.=<'; + my $scrout; if (! defined($ENV{'form.viewselect'})) { $ENV{'form.viewselect'}='detailed'; } @@ -813,11 +1104,12 @@ sub viewoptions { \&viewoptiontext, sort(keys(%Views))); $scrout.= '  '; - $scrout.=&Apache::lonmeta::selectbox('show', - $ENV{'form.show'}, - undef, - (10,20,50,100,1000,10000)); - $scrout .= (' 'x2).&mt('Records per Page').''.$/; + my $countselect = &Apache::lonmeta::selectbox('show', + $ENV{'form.show'}, + undef, + (10,20,50,100,1000,10000)); + $scrout .= (' 'x2).&mt('[_1] Records per Page',$countselect). + ''.$/; return $scrout; } @@ -837,7 +1129,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'); } ###################################################################### @@ -1027,39 +1319,56 @@ 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','authorspace', +# 'custommetadata','customshow', + 'modifyinguser','standards','mime'); + my @StatsFields = &statfields(); + my @EvalFields = &evalfields(); my $fillflag=0; - my $pretty_search_string = "
\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) { + 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 ; @@ -1069,24 +1378,31 @@ sub parse_advanced_search { my @queries; my $font = ''; # Evaluate logical expression AND/OR/NOT phrase fields. - foreach my $field ('title','author','subject','notes','abstract','url', - 'keywords','version','owner','standards') { - if ($ENV{'form.'.$field}) { - my $searchphrase = $ENV{'form.'.$field}; - $pretty_search_string .= $font."$field contains ". - $searchphrase.""; + 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.': '.$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: ". - "@New_Words."; + 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 .= "
\n"; - push @queries,&build_SQL_query($field,$searchphrase); + $pretty_search_string .= '
'; + push (@queries,$SQLQuery); } } # @@ -1102,18 +1418,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 contains '. $searchphrase.'
'; } # # 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'}."
\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'}."
\n"; + } if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') { push @queries,"(language like \"$ENV{'form.language'}\")"; $pretty_search_string.=$font."language= ". @@ -1124,31 +1452,61 @@ sub parse_advanced_search { push @queries,"(copyright like \"$ENV{'form.copyright'}\")"; $pretty_search_string.=$font."copyright = ". &Apache::loncommon::copyrightdescription($ENV{'form.copyright'}). - "
\n"; + "
\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.'>'.$min.'
'; + } + if (defined($max) && $max =~ /^(\d+\.\d+|\d+|\.\d+)$/) { + push(@queries,'('.$field.'<'.$max.')'); + $pretty_search_string.=$font.$field.'<'.$max.'
'; + } } # # 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,$hidden_fields); - 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 .= '
'.$prettydate.'
'; push @queries,$datequery; } # @@ -1177,16 +1535,17 @@ sub parse_advanced_search { ## ## Deal with restrictions to given domains ## - my ($libraries_to_query,$pretty_domains_string) = + my ($libraries_to_query,$pretty_domains_string,$domain_sql_restriction) = &parse_domain_restrictions(); + push(@queries,$domain_sql_restriction); $pretty_search_string .= $pretty_domains_string."
\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('advanced query = '.$/.$query); return ($query,$customquery,$customshow,$libraries_to_query, $pretty_search_string); } @@ -1195,8 +1554,8 @@ 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'})) { - return (undef,''); + if (! exists($ENV{'form.domains'}) || $ENV{'form.domains'} eq '') { + return (undef,'',undef); } my @allowed_domains; if (ref($ENV{'form.domains'})) { @@ -1207,17 +1566,21 @@ sub parse_domain_restrictions { # my %domain_hash = (); my $pretty_domains_string; + my $domain_sql_restriction; foreach (@allowed_domains) { $domain_hash{$_}++; } if ($domain_hash{'any'}) { $pretty_domains_string = "In all LON-CAPA domains."; + $domain_sql_restriction = undef; } else { if (@allowed_domains > 1) { $pretty_domains_string = "In LON-CAPA domains:"; } else { $pretty_domains_string = "In LON-CAPA domain "; } + $domain_sql_restriction = + '(domain="'.join('" OR domain="',@allowed_domains).'")'; foreach (sort @allowed_domains) { $pretty_domains_string .= "".$_." "; } @@ -1227,7 +1590,9 @@ sub parse_domain_restrictions { } } } - return ($libraries_to_query,$pretty_domains_string); + return ($libraries_to_query, + $pretty_domains_string, + $domain_sql_restriction); } ###################################################################### @@ -1248,7 +1613,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. @@ -1256,43 +1621,228 @@ sub parse_basic_search { $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"}); $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; } - my ($libraries_to_query,$pretty_domains_string) = + my ($libraries_to_query,$pretty_domains_string,$domain_sql_restriction) = &parse_domain_restrictions(); # # Check to see if enough of a query is filled in - unless (&filled($ENV{'form.basicexp'})) { + 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 = ''.$ENV{'form.basicexp'}.''; - 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: @New_Words."; - } 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); + if (defined($domain_sql_restriction) && $domain_sql_restriction ne '') { + push(@Queries,$domain_sql_restriction); + } + #foreach my $q (@Queries) { + # &Apache::lonnet::logthis(' '.$q); + #} + my $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries); # - # 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 .= "
\n"; - my $final_query = 'SELECT * FROM metadata WHERE '.$query; - # &Apache::lonnet::logthis($final_query); + $pretty_search_string =~ s:^
and ::; + &Apache::lonnet::logthis('simple search final query = '.$/.$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; +} + ###################################################################### ###################################################################### @@ -1311,45 +1861,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; -} ###################################################################### ###################################################################### @@ -1384,47 +1904,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); -} ###################################################################### ###################################################################### @@ -1441,51 +1920,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 .= '
' 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 .= '
' 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); } ###################################################################### @@ -1595,7 +2096,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', @@ -1627,8 +2132,9 @@ sub print_sort_form { return; } my $result; + my $html=&Apache::lonxml::xmlbegin(); $result.=< +$html \n"); - $r->rflush(); + my ($r) = @_; + my $time = &time_left(); + if (($last_time-$time) > 0) { + $r->print("\n"); + $r->rflush(); + } + $last_time = $time; +} + } ###################################################################### @@ -1835,30 +2367,34 @@ 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; # - # Timing variables - # - my $starttime = time; - my $max_time = 30; # seconds for the search to complete - # # Print run_search header # + my $html=&Apache::lonxml::xmlbegin(); $r->print(< -Search Status +$html + +Search Status $bodytag END - # Check to see if $pretty_string has more than one carriage return. - # Assume \n s are following
s and truncate the value. - # (there is probably a better way)... - my @Lines = split /
/,$pretty_string; + # Remove leading and trailing
+ $pretty_string =~ s:^\s*
::i; + $pretty_string =~ s:(
)*\s*$::im; + my @Lines = split("
",$pretty_string); + # I keep getting blank items at the end of the list, hence the following: + while ($Lines[-1] =~ /^\s*$/ && @Lines) { + pop(@Lines); + } if (@Lines > 2) { - $pretty_string = join '
',(@Lines[0..2],'....
'); + $pretty_string = join '
',(@Lines[0..2],'....
'); } - $r->print(&mt("Search").": ".$pretty_string); + $r->print(&mt("Search: [_1]",$pretty_string)); $r->rflush(); # # Determine the servers we need to contact. @@ -1873,6 +2409,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.". @@ -1908,7 +2446,7 @@ END - + @@ -1917,19 +2455,18 @@ END END $r->rflush(); - my $time_remaining = $max_time - (time - $starttime) ; - $time_remaining = 0 if ($time_remaining <0); - 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))) { - &update_seconds($r,$time_remaining); - # 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,&mt('contacting').' '.$server); + &update_status($r,&mt('contacting [_1]',$server)); my $reply=&Apache::lonnet::metadata_query($query,$customquery, $customshow,[$server]); ($server) = keys(%$reply); @@ -1941,21 +2478,17 @@ END # left to contact. if (scalar (keys(%Server_status))) { &update_status($r, - &mt('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()); - $time_remaining = $max_time - (time - $starttime) ; - $time_remaining = 0 if ($time_remaining < 0); - if ($last_time - $time_remaining > 0) { - $last_time = $time_remaining; - &update_seconds($r,$time_remaining); - } + &update_seconds($r); + my $status = $Server_status{$server}; if ($status eq 'con_lost') { delete ($Server_status{$server}); next; @@ -1963,12 +2496,12 @@ END $status=~/^([\.\w]+)$/; my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$1; if (-e $datafile && ! -e "$datafile.end") { - &update_status($r,&mt('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,&mt('Reading results from').' '.$server); + &update_status($r,&mt('Reading results from [_1]',$server)); if (-z "$datafile") { delete($Server_status{$server}); next; @@ -1983,30 +2516,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) ; - $time_remaining = 0 if ($time_remaining < 0); - 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. @@ -2016,29 +2549,24 @@ 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,&mt('Search Complete').$server); - &update_seconds($r,0); + &update_status($r,&mt('Search Complete [_1]',$server)); + &update_seconds($r); # - &Apache::lonmysql::disconnect_from_db(); + &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(""); - if ($ENV{'form.catalogmode'} ne 'groupsearch') { +# if ($ENV{'form.catalogmode'} ne 'groupsearch') { $r->print(""); - } +# } return; } @@ -2058,38 +2586,17 @@ Returns html for the previous and next b 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{}. - &mt('prev').''; - } else { - $links .= &mt('prev'); - } - ## - ## Pages.... Someday. - ## - $links .= - qq{  - }. - &mt('reload').''; - ## - ## Next - my $next_min = $current_min + $show; - $next_min = $current_min if ($next_min > $total); - if ($next_min != $current_min) { - $links .= - qq{  - }. - &mt('next').''; - } else { - $links .= ' '.&mt('next'); - } - return $links; + # + # Create buttons + my $buttons = ''; + $buttons .= ' 'x3; + $buttons .= '$total_results); my $min = $ENV{'form.start'}; my $max; if ($ENV{'form.show'} eq 'all') { @@ -2163,28 +2677,105 @@ sub display_results { $max = $total_results if ($max > $total_results); } ## + ## Output form elements + $r->print(&hidden_field('table'). + &hidden_field('phase'). + &hidden_field('persistent_db_id'). + &hidden_field('start') + ); + # + # Build sorting selector + my @field_order = ('default', + 'title', + 'author', + 'subject', + 'url', + 'keywords', + 'version', + 'language', + 'creationdate'=>, + 'lastrevisiondate', + 'owner', + 'copyright', + 'authorspace', + 'lowestgradeleve', + 'highestgradelevel', + 'standards', + 'count', + 'stdno', + 'avetries', + 'difficulty', + 'disc', + 'clear', + 'technical', + 'correct', + 'helpful', + 'depth', + ); + my %sort_fields = ('default' => 'Default', + 'title' => 'Title', + 'author' => 'Author', + 'subject' => 'Subject', + 'url' => 'URL', + 'keywords' => 'Keywords', + 'version' => 'Version', + 'language' => 'Language', + 'creationdate'=> 'Creation Date', + 'lastrevisiondate' => 'Last Revision Date', + 'owner' => 'Owner', + 'copyright' => 'Copyright', + 'authorspace' => 'Authorspace', + 'lowestgradeleve' => 'Lowest Grade Level', + 'highestgradelevel' => 'Highest Grade Level', + 'standards' => 'Standards', + 'count' => 'Number of Accesses', + 'stdno' => 'Students Attempting', + 'avetries' => 'Average Number of Tries', + 'difficulty' => 'Mean Degree of Difficulty', + 'disc' => 'Mean Degree of Discrimination', + 'clear' => 'Evaluation: Clear', + 'technical' => 'Evaluation: Technically Correct', + 'correct' => 'Evaluation: Material is Correct', + 'helpful' => 'Evaluation: Material is Helpful', + 'depth' => 'Evaluation: Material has Depth', + 'select_form_order' => \@field_order, + ); + + my $sortform = &mt('Sort by [_1]', + &Apache::loncommon::select_form($ENV{'form.sortfield'}, + 'sortfield', + %sort_fields)); + ## ## Output links (if necessary) for 'prev' and 'next' pages. $r->print - ('
'. - &prev_next_buttons($min,$ENV{'form.show'},$total_results, - "table=".$ENV{'form.table'}. - "&phase=results". - "&persistent_db_id=".$ENV{'form.persistent_db_id'}) - ."
\n" + ('
StatusTotal MatchesTime Remaining
$revise
'. + $sortform. + ''. + &prev_next_buttons($min,$ENV{'form.show'},$total_results). + ''. + &viewoptions().'
' ); if ($total_results == 0) { - $r->print(''. + $r->print(''. '

'.&mt('There are currently no results').'.

'. ""); return; } else { - $r->print - ("
Results $min to $max out of $total_results
\n"); + $r->print('
'. + mt('Results [_1] to [_2] out of [_3]', + $min,$max,$total_results). + "
\n"); } ## ## Get results from MySQL table - my @Results = &Apache::lonmysql::get_rows($table, - 'id>='.$min.' AND id<='.$max); + my $sort_command = 'id>='.$min.' AND id<='.$max; + if ($ENV{'form.sortfield'} ne 'default' && + exists($sort_fields{$ENV{'form.sortfield'}})) { + $sort_command = $ENV{'form.sortfield'}.' IS NOT NULL '. + 'ORDER BY '.$ENV{'form.sortfield'}. + ' LIMIT '.($min-1).','.($max-$min); + } + my @Results = &Apache::lonmysql::get_rows($table,$sort_command); ## ## Loop through the results and output them. foreach my $row (@Results) { @@ -2509,7 +3100,7 @@ SCRIPT $result.=< $bodytag -
+ $importbutton END @@ -2520,8 +3111,11 @@ END ###################################################################### sub search_status_header { my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1); + my $html=&Apache::lonxml::xmlbegin(); return <Search Status +$html + +Search Status $bodytag

Search Status

Sending search request to LON-CAPA servers.
@@ -2544,8 +3138,9 @@ sub print_frames_interface { "&persistent_db_id=".$ENV{'form.persistent_db_id'}; my $run_search_link = $basic_link."&phase=run_search"; my $results_link = &results_link(); + my $html=&Apache::lonxml::xmlbegin(); my $result = <<"ENDFRAMES"; - +$html