--- loncom/interface/lonsearchcat.pm 2001/12/13 14:00:37 1.112 +++ loncom/interface/lonsearchcat.pm 2013/12/31 01:07:28 1.340 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Search Catalog # -# $Id: lonsearchcat.pm,v 1.112 2001/12/13 14:00:37 harris41 Exp $ +# $Id: lonsearchcat.pm,v 1.340 2013/12/31 01:07:28 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,723 +25,1763 @@ # # http://www.lon-capa.org/ # -# YEAR=2001 -# 3/8, 3/12, 3/13, 3/14, 3/15, 3/19 Scott Harrison -# 3/20, 3/21, 3/22, 3/26, 3/27, 4/2, 8/15, 8/24, 8/25 Scott Harrison -# 10/12,10/14,10/15,10/16,11/28,11/29,12/10,12/12 Scott Harrison -# -### +############################################################################### +############################################################################### + +=pod + +=head1 NAME + +lonsearchcat - LONCAPA Search Interface + +=head1 SYNOPSIS + +Search interface to LON-CAPAs digital library + +=head1 DESCRIPTION + +This module enables searching for a distributed browseable catalog. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +lonsearchcat presents the user with an interface to search the LON-CAPA +digital library. lonsearchcat also initiates the execution of a search +by sending the search parameters to LON-CAPA servers. The progress of +search (on a server basis) is displayed to the user in a separate window. + +=head1 Internals + +=cut ############################################################################### -## ## -## ORGANIZATION OF THIS PERL MODULE ## -## ## -## 1. Modules used by this module ## -## 2. Choices for different output views (detailed, summary, xml, etc) ## -## 3. BEGIN block (to be run once after compilation) ## -## 4. Handling routine called via Apache and mod_perl ## -## 5. Other subroutines ## -## ## ############################################################################### package Apache::lonsearchcat; -# ------------------------------------------------- modules used by this module use strict; -use Apache::Constants qw(:common); -use Apache::lonnet(); +use Apache::Constants qw(:common :http); +use Apache::lonnet; use Apache::File(); use CGI qw(:standard); use Text::Query; use GDBM_File; use Apache::loncommon(); +use Apache::lonmysql(); +use Apache::lonmeta; +use Apache::lonhtmlcommon; +use Apache::lonlocal; +use LONCAPA::lonmetadata(); +use HTML::Entities(); +use Parse::RecDescent; +use Apache::lonnavmaps; +use Apache::lonindexer(); +use Apache::lonwishlist(); +use LONCAPA; + +###################################################################### +###################################################################### +## +## Global variables +## +###################################################################### +###################################################################### +my %groupsearch_db; # Database hash used to save values for the + # groupsearch RAT interface. +my %persistent_db; # gdbm hash which holds data which is supposed to + # persist across calls to lonsearchcat.pm + +# The different view modes and associated functions + +my %Views = ("detailed" => \&detailed_citation_view, + "detailedpreview" => \&detailed_citation_preview, + "summary" => \&summary_view, + "summarypreview" => \&summary_preview, + "fielded" => \&fielded_format_view, + "xml" => \&xml_sgml_view, + "compact" => \&compact_view); -# ---------------------------------------- variables used throughout the module - -# -- information holders -# my %language; # holds contents of language.tab -# my %cprtag; # holds contents of copyright.tab -# my %mimetag; # holds contents of filetypes.tab -my %hostdomains; # matches host name to host domain -my %hostips; # matches host name to host ip -my %hitcount; # stores number of hits per host - -# -- dynamically rendered interface components -my $closebutton; # button that closes the search window -my $importbutton; # button to take the selected results and go to group sorting - -# -- miscellaneous variables -my $scrout; # string that holds portions of the screen output -my $yourself; # allows for quickly limiting to oneself -my %hash; - -# ------------------------------------------ choices for different output views -# Detailed Citation View ---> sub detailed_citation_view -# Summary View ---> sub summary_view -# Fielded Format ---> sub fielded_format_view -# XML/SGML ---> sub xml_sgml_view -my $basicviewselect=<<END; -<select name='basicviewselect'> -<option value='Detailed Citation View'>Detailed Citation View</option> -<option value='Summary View'>Summary View</option> -<option value='Fielded Format'>Fielded Format</option> -<option value='XML/SGML'>XML/SGML</option> -</select> -END -my $advancedviewselect=<<END; -<select name='advancedviewselect'> -<option value='Detailed Citation View'>Detailed Citation View</option> -<option value='Summary View'>Summary View</option> -<option value='Fielded Format'>Fielded Format</option> -<option value='XML/SGML'>XML/SGML</option> -</select> -END - -# ----------------------------------------------------------------------- BEGIN -sub BEGIN { - { - my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. - '/hosts.tab'); - while (<$fh>) { - $_=~/(\w+?)\:(\w+?)\:(\w+?)\:(.*)/; chomp; - if ($3 eq 'library') { - $hostdomains{$1}=$2; - $hostips{$1}=$4; - } - } - } -} - -my $diropendb = ""; -my $domain = ""; - -# ----------------------------- Handling routine called via Apache and mod_perl +###################################################################### +###################################################################### sub handler { my $r = shift; - untie %hash; - &get_unprocessed_cgi(); - - $r->content_type('text/html'); +# &set_defaults(); + # + # set form defaults + # + my $hidden_fields;# Hold all the hidden fields used to keep track + # of the search system state + my $importbutton; # button to take the selected results and go to group + # sorting + my $diropendb; # The full path to the (temporary) search database file. + # This is set and used in &handler() and is also used in + # &output_results(). + + # + my $closebutton; # button that closes the search window + # This button is different for the RAT compared to + # normal invocation. + # + &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK if $r->header_only; - - $domain = $r->dir_config('lonDefDomain'); - - $diropendb= "/home/httpd/perl/tmp/$domain\_$ENV{'user.name'}_searchcat.db"; - - if ($ENV{'form.launch'} eq '1') { - if (tie(%hash,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { + ## + ## Prevent caching of the search interface window. Hopefully this means + ## we will get the launch=1 passed in a little more. + &Apache::loncommon::no_cache($r); + ## + ## Pick up form fields passed in the links. + ## + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['catalogmode','launch','acts','mode','form','element','pause', + 'phase','persistent_db_id','table','start','show', + 'cleargroupsort','titleelement','area','inhibitmenu']); + ## + ## The following is a trick - we wait a few seconds if asked to so + ## the daemon running the search can get ahead of the daemon + ## printing the results. We only need (theoretically) to do + ## this once, so the pause indicator is deleted + ## + if (exists($env{'form.pause'})) { + sleep(1); + delete($env{'form.pause'}); + } + ## + ## Initialize global variables + ## + my $domain = $r->dir_config('lonDefDomain'); + my $temp_file_dir = LONCAPA::tempdir(); + $diropendb= $temp_file_dir . + "$env{'user.domain'}_$env{'user.name'}_sel_res.db"; + # + # set the name of the persistent database + # $env{'form.persistent_db_id'} can only have digits in it. + if (! exists($env{'form.persistent_db_id'}) || + ($env{'form.persistent_db_id'} =~ /\D/) || + ($env{'form.launch'} eq '1')) { + $env{'form.persistent_db_id'} = time; + } + + my $persistent_db_file = $temp_file_dir . + &escape($domain). + '_'.&escape($env{'user.name'}). + '_'.$env{'form.persistent_db_id'}.'_persistent_search.db'; + ## + &Apache::lonhtmlcommon::clear_breadcrumbs(); + + my @allowed_searches = ('portfolio'); + if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) { + push(@allowed_searches,'res'); + } + my $crumb_text = 'Portfolio Search'; + if (@allowed_searches ==2) { + $crumb_text = 'Portfolio and Catalog Search'; + } + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?'. + &Apache::loncommon::inhibit_menu_check(). + '&catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"$crumb_text", + target=>'_top', + bug=>'Searching',}); + # + if ($env{'form.phase'} !~ m/(basic|adv|course)_search/) { + if (! &get_persistent_form_data($persistent_db_file)) { + if ($env{'form.phase'} =~ /(run_search|results)/) { + &Apache::lonnet::logthis('lonsearchcat:'. + 'Unable to recover data from '. + $persistent_db_file); + my $msg = + 'We were unable to retrieve data describing your search. '. + 'This is a serious error and has been logged. '. + 'Please alert your LON-CAPA administrator.'; + &Apache::loncommon::simple_error_page($r,'Search Error', + $msg); + return OK; + } + } + } else { + &clean_up_environment(); + } + ## + ## Clear out old values from groupsearch database + ## + untie %groupsearch_db if (tied(%groupsearch_db)); + if (($env{'form.cleargroupsort'} eq '1') || + (($env{'form.launch'} eq '1') && + ($env{'form.catalogmode'} eq 'import'))) { + if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) { &start_fresh_session(); - untie %hash; - } - else { - $r->print('<html><head></head><body>Unable to tie hash to db '. - 'file</body></html>'); + untie %groupsearch_db; + delete($env{'form.cleargroupsort'}); + } else { + # This is a stupid error to give to the user. + # It really tells them nothing. + my $msg = 'Unable to tie hash to db file.'; + &Apache::loncommon::simple_error_page($r,'Search Error', + $msg); return OK; } } - -# ----------------------------------- configure dynamic components of interface - my $hidden=''; - if ($ENV{'form.catalogmode'} eq 'interactive') { - $hidden="<input type='hidden' name='catalogmode' value='interactive'>". - "\n"; - $closebutton="<input type='button' name='close' value='CLOSE' ". - "onClick='self.close()'>"."\n"; - } - elsif ($ENV{'form.catalogmode'} eq 'groupsearch') { - $hidden=<<END; -<input type='hidden' name='catalogmode' value='groupsearch'> -END - $closebutton=<<END; -<input type='button' name='close' value='CLOSE' onClick='self.close()'> -END + ## + ## Configure hidden fields + ## + $hidden_fields = '<input type="hidden" name="persistent_db_id" value="'. + $env{'form.persistent_db_id'}.'" />'."\n"; + if (exists($env{'form.catalogmode'})) { + $hidden_fields .= &hidden_field('catalogmode'); + } + if (exists($env{'form.form'})) { + $hidden_fields .= &hidden_field('form'); + } + if (exists($env{'form.element'})) { + $hidden_fields .= &hidden_field('element'); + } + if (exists($env{'form.titleelement'})) { + $hidden_fields .= &hidden_field('titleelement'); + } + if (exists($env{'form.mode'})) { + $hidden_fields .= &hidden_field('mode'); + } + if (exists($env{'form.area'})) { + $hidden_fields .= &hidden_field('area'); + } + if (exists($env{'form.inhibitmenu'})) { + $hidden_fields .= &hidden_field('inhibitmenu'); + } + ## + ## Configure dynamic components of interface + ## + if ($env{'form.catalogmode'} eq 'interactive') { + $closebutton="<input type='button' name='close' value='".&mt('Close')."' "; + if ($env{'form.phase'} =~ /(results|run_search)/) { + $closebutton .="onclick='parent.close()'"; + } else { + $closebutton .="onclick='self.close()'"; + } + $closebutton .=" />\n"; + } elsif ($env{'form.catalogmode'} eq 'import') { + $closebutton="<input type='button' name='close' value='".&mt('Close')."' "; + if ($env{'form.phase'} =~ /(results|run_search)/) { + $closebutton .="onclick='parent.close()'"; + } else { + $closebutton .="onclick='self.close()'"; + } + $closebutton .= " />"; + my $txt_import = &mt('IMPORT'); $importbutton=<<END; -<input type='button' name='import' value='IMPORT' -onClick='javascript:select_group()'> +<input type='button' name='import' value='$txt_import' +onclick='javascript:select_group()' /> END + } else { + $closebutton = ''; + $importbutton = ''; + } + ## + ## Sanity checks on form elements + ## + if (!defined($env{'form.viewselect'})) { + $env{'form.viewselect'} ="summary"; + } + $env{'form.phase'} = 'disp_basic' if (! exists($env{'form.phase'})); + $env{'form.show'} = 20 if (! exists($env{'form.show'})); + # + $env{'form.searchmode'} = 'basic' if (! exists($env{'form.searchmode'})); + if ($env{'form.phase'} eq 'adv_search' || + $env{'form.phase'} eq 'disp_adv') { + $env{'form.searchmode'} = 'advanced'; + } + # + if ($env{'form.searchmode'} eq 'advanced') { + my $srchtype = 'Catalog'; + if ($env{'form.area'} eq 'portfolio') { + $srchtype = 'Portfolio'; + } + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'/adm/searchcat?'.&Apache::loncommon::inhibit_menu_check(). + '&phase=disp_adv'. + '&catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}, + text=>"Advanced $srchtype Search", + bug=>'Searching',}); + } + ## + ## Switch on the phase + ## + if ($env{'form.phase'} eq 'disp_basic') { + &print_basic_search_form($r,$closebutton,$hidden_fields); + } elsif ($env{'form.phase'} eq 'disp_adv') { + &print_advanced_search_form($r,$closebutton,$hidden_fields); + } elsif ($env{'form.phase'} eq 'results') { + &display_results($r,$importbutton,$closebutton,$diropendb, + $env{'form.area'}); + } elsif ($env{'form.phase'} =~ /^(sort|run_search)$/) { + my ($query,$customquery,$customshow,$libraries,$pretty_string,$domainsref) = + &get_persistent_data($persistent_db_file, + ['query','customquery','customshow', + 'libraries','pretty_string','domains']); + if ($env{'form.phase'} eq 'sort') { + &print_sort_form($r,$pretty_string); + } elsif ($env{'form.phase'} eq 'run_search') { + &run_search($r,$query,$customquery,$customshow, + $libraries,$pretty_string,$env{'form.area'},$domainsref); + } + } 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,$domains) = + (undef,undef,undef,undef,undef); + my $pretty_string; + if ($env{'form.phase'} eq 'basic_search') { + ($query,$pretty_string,$libraries,$domains) = + &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,$domains) + = &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, + domains => $domains }, + $persistent_db_file); + # + # Set up table + if (! defined(&create_results_table($env{'form.area'}))) { + my $errorstring=&Apache::lonmysql::get_error(); + &Apache::lonnet::logthis('lonsearchcat.pm: Unable to create '. + 'needed table. lonmysql error:'. + $errorstring); + + my $msg = + 'Unable to create table in which to save search results. '. + 'The search has been aborted.'; + &Apache::loncommon::simple_error_page($r,'Search Error', + $msg); + return OK; + } + delete($env{'form.launch'}); + if (! &make_form_data_persistent($r,$persistent_db_file)) { + my $msg= + 'Unable to properly save search information. '. + 'The search has been aborted.'; + &Apache::loncommon::simple_error_page($r,'Search Error', + $msg); + return OK; + } + ## + ## Print out the frames interface + ## + if (defined($query)) { + &print_frames_interface($r); + } + } + return OK; +} + +# +# The mechanism used to store values away and retrieve them does not +# handle the case of missing environment variables being significant. +# +# This routine sets non existant checkbox form elements to ''. +# +sub clean_up_environment { + if ($env{'form.phase'} eq 'basic_search') { + if (! exists($env{'form.related'})) { + $env{'form.related'} = ''; + } + if (! exists($env{'form.domains'})) { + $env{'form.domains'} = ''; + } + } elsif ($env{'form.phase'} eq 'adv_search') { + foreach my $field ('title','keywords','notes', + 'abstract','standards','mime') { + if (! exists($env{'form.'.$field.'_related'})) { + $env{'form.'.$field.'_related'} = ''; + } + } + } elsif ($env{'form.phase'} eq 'course_search') { + if (! exists($env{'form.crsrelated'})) { + $env{'form.crsrelated'} = ''; + } + } +} + +sub hidden_field { + my ($name,$value) = @_; + if (! defined($value)) { + $value = $env{'form.'.$name}; } + return '<input type="hidden" name="'.$name.'" value="'.$value.'" />'.$/; +} + +###################################################################### + +=pod + +=over 4 + +=item &print_basic_search_form() + +Prints the form for the basic search. Sorry the name is so cryptic. + +=cut + +###################################################################### +###################################################################### +sub print_basic_search_form { + my ($r,$closebutton,$hidden_fields) = @_; + my $result = ($env{'form.catalogmode'} ne 'import'); + my $bread_crumb = + &Apache::lonhtmlcommon::breadcrumbs('Searching','Search_Basic', + $env{'form.catalogmode'} ne 'import'); + my $scrout = &Apache::loncommon::start_page('Content Library').$bread_crumb; +# Search form for resource space + if (&Apache::lonnet::allowed('bre',$env{'request.role.domain'})) { + $scrout .= &setup_basic_search($r,'res',$hidden_fields,$closebutton); + $scrout .= '<hr /><br />'; + } +# Search form for accessible portfolio files + $scrout.= &setup_basic_search($r,'portfolio',$hidden_fields,$closebutton); + $scrout .= &Apache::loncommon::end_page(); + $r->print($scrout); + return; +} + +sub setup_basic_search { + my ($r,$area,$hidden_fields,$closebutton) = @_; + # Define interface components + my %lt = &Apache::lonlocal::texthash ( + res => 'LON-CAPA Catalog Search', + portfolio => 'Portfolio Search', + ); + my ($userelatedwords,$onlysearchdomain,$inclext,$adv_search_link,$scrout); + + $userelatedwords = '<label>' + .&Apache::lonhtmlcommon::checkbox( + 'related', + $env{'form.related'}, + 'related') + .' ' + .&mt('use related words') + .'</label>'; + + $onlysearchdomain = '<label>' + .&Apache::lonhtmlcommon::checkbox( + 'domains', + $env{'form.domains'}, + $r->dir_config('lonDefDomain')) + .' ' + .&mt('only search domain [_1]' + ,'<b>'.$r->dir_config('lonDefDomain').'</b>') + .'</label>'; + + $adv_search_link = '<a href="/adm/searchcat?'. + &Apache::loncommon::inhibit_menu_check(). + '&phase=disp_adv'. + '&catalogmode='.$env{'form.catalogmode'}. + '&launch='.$env{'form.launch'}. + '&mode='.$env{'form.mode'}. + '&area='.$area. + '&form='.$env{'form.form'}. + '&titleelement='.$env{'form.titleelement'}. + '&element='.$env{'form.element'}. + '">'.&mt('Advanced Search').'</a>'; + # + $scrout.='<form name="loncapa_search" method="post" '. + 'action="/adm/searchcat">'. + '<input type="hidden" name="phase" value="basic_search" />'. + $hidden_fields; + if (!exists($env{'form.area'})) { + $scrout .= '<input type="hidden" name="area" value="'.$area.'" />'; + } + # + $scrout .= '<div align="center">'.$/; +# if ($env{'request.course.id'}) { + $scrout .= '<h1>'.$lt{$area}.'</h1>'; +# } else { + # No need to tell them they are searching +# $scrout.= ('<br />'x2); +# } + $scrout.='<table>'. + '<tr><td align="center" valign="top">'. + &Apache::lonhtmlcommon::textbox('basicexp', + $env{'form.basicexp'},50). + '<br />'. + '<span class="LC_fontsize_small">'.&searchhelp().'</span>'.'</td>'. + '<td>'. + '<span class="LC_nobreak">'.(' 'x3).$adv_search_link.'</span>'.'<br />'. + '<span class="LC_nobreak">'.(' 'x1).$userelatedwords.'</span>'.'<br />'. + '<span class="LC_nobreak">'.(' 'x1).$onlysearchdomain.'</span>'.'<br />'. + '<span class="LC_nobreak">'.(' 'x1).$inclext.'</span>'.'<br />'. + '</td>'. + '</tr>'. + '</table>'.$/; + # + $scrout .= '<p>' + .&viewoptions() + .'</p>' + .'<p>' + .'<input type="submit" name="basicsubmit" ' + .'value="'.&mt('Search').'" />' + .' ' + .$closebutton + .'</p>'; + # + $scrout .= '</div>'.'</form>'; + return $scrout; +} + +###################################################################### +###################################################################### + +=pod + +=item &advanced_search_form() -# ------------------------------------------------------ Determine current user - $yourself=$ENV{'user.name'}.'@'.$ENV{'user.domain'}; +Prints the advanced search form. -# --- Now, depending on the interface actions, do one of three things here: -# --- 1. a basic search -# --- 2. an advanced search -# --- 3. output a search interface +=cut -# ----------------------------------- See if a search invocation should be done - if ($ENV{'form.basicsubmit'} eq 'SEARCH') { - untie %hash; return &basicsearch($r,\%ENV); +###################################################################### +###################################################################### +sub print_advanced_search_form{ + my ($r,$closebutton,$hidden_fields) = @_; + my $bread_crumb = + &Apache::lonhtmlcommon::breadcrumbs('Searching','Search_Advanced', + $env{'form.catalogmode'} ne 'import'); + my %lt=&Apache::lonlocal::texthash('srch' => 'Search', + 'reset' => 'Reset', + 'help' => 'Help'); + my $advanced_buttons=<<"END"; +<p> +<input type="submit" name="advancedsubmit" value='$lt{"srch"}' /> +<input type="reset" name="reset" value='$lt{"reset"}' /> +$closebutton +</p> +END + my $srchtype = 'Catalog'; + my $jscript; + if ($env{'form.area'} eq 'portfolio') { + $srchtype = 'Portfolio'; + $jscript = '<script type="text/javascript"> +// <![CDATA[ +function additional_metadata() { + if (document.advsearch.newfield.checked) { + document.advsearch.phase.value = "disp_adv"; + document.advsearch.numaddedfields.value = parseInt(document.advsearch.numaddedfields.value) +1; + document.advsearch.submit(); + } +} +// ]]> +</script>'; + } + my $scrout= &Apache::loncommon::start_page("Advanced $srchtype Search", + $jscript); + $scrout .= $bread_crumb; + + $scrout .= '<form method="post" action="/adm/searchcat" name="advsearch">' + .$hidden_fields + .'<input type="hidden" name="phase" value="adv_search" />'; + + $scrout .= '<fieldset>'."\n" + .'<legend>'.&mt('Display Options').'</legend>'."\n" + .&viewoptions() + .'</fieldset>'; + + $scrout .= $advanced_buttons; + + $scrout .= &Apache::lonhtmlcommon::start_pick_box(); + + my %fields=&Apache::lonmeta::fieldnames(); + + # Standard Metadata + $scrout .= &Apache::lonhtmlcommon::row_headline() + .'<h3>'.&mt("Standard $srchtype Metadata").'</h3>' + .&searchhelp() + .&Apache::lonhtmlcommon::row_closure(); + my %related_word_search = + ('title' => 1, + 'author' => 0, + 'owner' => 0, + 'authorspace' => 0, + 'modifyinguser'=> 0, + 'keywords' => 1, + 'notes' => 1, + 'abstract' => 1, + 'standards'=> 1, + 'mime' => 1, + 'subject' => 1, + ); + # + foreach my $field ('title','author','subject','owner','authorspace', + 'modifyinguser','keywords','notes','abstract', + 'standards','mime') { + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield($fields{$field})) + .&Apache::lonmeta::prettyinput($field, + $env{'form.'.$field}, + $field, + 'advsearch', + $related_word_search{$field}, + '', + $env{'form.'.$field.'_related'}, + 50); + if ($related_word_search{$field}) { + $scrout .= &mt('related words'); + } else { + $scrout .= ''; + } + $scrout .= &Apache::lonhtmlcommon::row_closure(); } - elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') { - untie %hash; return &advancedsearch($r,\%ENV); + foreach my $field ('lowestgradelevel','highestgradelevel') { + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield($fields{$field})) + .&Apache::lonmeta::prettyinput($field, + $env{'form.'.$field}, + $field, + 'advsearch', + 0) + .&Apache::lonhtmlcommon::row_closure(); + } + + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt('MIME Type Category'))) + .&Apache::loncommon::filecategoryselect('category', + $env{'form.category'}) + .&Apache::lonhtmlcommon::row_closure(); + + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt('Domains'))) + .&Apache::loncommon::domain_select('domains', + $env{'form.domains'},1) + .&Apache::lonhtmlcommon::row_closure(); + + # Misc metadata + if ($env{'form.area'} ne 'portfolio') { + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt('Copyright/Distribution'))) + .&Apache::lonmeta::selectbox('copyright', + $env{'form.copyright'}, + \&Apache::loncommon::copyrightdescription, + ( undef, + &Apache::loncommon::copyrightids) + ) + .&Apache::lonhtmlcommon::row_closure(); + } + + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt('Language'))) + .&Apache::lonmeta::selectbox('language', + $env{'form.language'}, + \&Apache::loncommon::languagedescription, + ('any',&Apache::loncommon::languageids) + ) + .&Apache::lonhtmlcommon::row_closure(); + + # Portfolio Metadata + if ($env{'form.area'} eq 'portfolio') { + # Added fields + my $curnumadd = $env{'form.numaddedfields'}; + if ($curnumadd eq '') { + $curnumadd = 1; + } + $scrout .= &Apache::lonhtmlcommon::row_headline() + .'<h3>'.&mt('Custom Metadata fields').'</h3>' + .&Apache::lonhtmlcommon::row_closure() + .&Apache::lonhtmlcommon::row_title('') + .&mt('Field Name').' | '.&mt('Field Value(s)') + .&Apache::lonhtmlcommon::row_closure(); + + for (my $j=0; $j<$curnumadd; $j++) { + my $num = $j+1; + $scrout .= &Apache::lonhtmlcommon::row_title(&mt('Custom metadata [_1]',$num)) + .'<input type="text"' + .' name="addedfield_'.$j.'" size="10"' + .' value="'.$env{'form.addedfield_'.$j}.'" />' + .' ' + .'<input type="text" ' + .'name="addedvalues_'.$j.'" size="15"' + .' value="'.$env{'form.addedvalues_'.$j}.'" />' + .&Apache::lonhtmlcommon::row_closure(); + } + $scrout .= &Apache::lonhtmlcommon::row_title('') + .'<label>' + .'<input type="checkbox" name="newfield"' + .' value="1" onclick="javascript:additional_metadata()" />' + .&mt('Another custom field/value pair?').'</label>' + .'<input type="hidden" name="numaddedfields"' + .' value="'.$curnumadd.'" />' + .&Apache::lonhtmlcommon::row_closure(); +} else { + # + # Dynamic metadata + $scrout .= &Apache::lonhtmlcommon::row_headline() + .'<h3>'.&mt('Problem Statistics').'</h3>' + .&Apache::lonhtmlcommon::row_closure(); + $scrout .= &Apache::lonhtmlcommon::row_title('') + .&mt('Minimum').' | '.&mt('Maximum') + .&Apache::lonhtmlcommon::row_closure(); + foreach my $statistic + ({ name=>'count', + description=>'Network-wide number of accesses (hits)',}, + { name=>'stdno', + description=> + 'Statistics calculated for number of students',}, + { name => 'avetries', + description=>'Average number of tries till solved',}, + { name => 'difficulty', + description=>'Degree of difficulty',}, + { name => 'disc', + description=>'Degree of discrimination'}) { + $scrout .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt($statistic->{'description'}))) + .'<input type="text" name="'.$statistic->{'name'} + .'_min" value="" size="6" />' + .' ' + .'<input type="text" name="'.$statistic->{'name'} + .'_max" value="" size="6" />' + .&Apache::lonhtmlcommon::row_closure(); + } + + $scrout .= &Apache::lonhtmlcommon::row_headline() + .'<h3>'.&mt('Evaluation Data').'</h3>' + .&Apache::lonhtmlcommon::row_closure(); + $scrout .= &Apache::lonhtmlcommon::row_title('') + .&mt('Minimum').' | '.&mt('Maximum') + .&Apache::lonhtmlcommon::row_closure(); + 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 .= &Apache::lonhtmlcommon::row_title(&titlefield(&mt($evaluation->{'description'}))) + .'<input type="text" name="' + .$evaluation->{'name'}.'_min" value="" size="6" />' + .' ' + .'<input type="text" name="' + .$evaluation->{'name'}.'_max" value="" size="6" />' + .&Apache::lonhtmlcommon::row_closure(); + } } + # + # Creation/Modification date limits + $scrout .= &Apache::lonhtmlcommon::row_headline() + .'<h3>'.&mt('Creation and Modification dates').'</h3>' + .&Apache::lonhtmlcommon::row_closure(); + $scrout .= &Apache::lonhtmlcommon::row_title('') + .&mt('Month[_1]Day[_2]Year',' 'x14,' 'x6) + .&Apache::lonhtmlcommon::row_closure(); + + 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 .= &Apache::lonhtmlcommon::row_title(&mt('Created between')) + .$cafter + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::row_title(&mt('and')) + .$cbefore + .&Apache::lonhtmlcommon::row_closure(); + + 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 .= &Apache::lonhtmlcommon::row_title(&mt('Last modified between')) + .$lafter + .&Apache::lonhtmlcommon::row_closure(1) + .&Apache::lonhtmlcommon::row_title(&mt('and')) + .$lbefore + .&Apache::lonhtmlcommon::row_closure(1); # Last row of total pick_box + + $scrout .= &Apache::lonhtmlcommon::end_pick_box(); + + $scrout .= $advanced_buttons + .'</form>'; + + $scrout .= &Apache::loncommon::end_page(); + $r->print($scrout); + return; +} + +###################################################################### +###################################################################### -# ----------------------------- Else, begin building search interface to output - $scrout=''; # building a part of screen output - $scrout.=&searchphrasefield('Limit by title','title', - $ENV{'form.title'}); +=pod - $scrout.=&searchphrasefield('Limit by author','author', - $ENV{'form.author'}); +=item &titlefield() - $scrout.=&searchphrasefield('Limit by subject','subject', - $ENV{'form.subject'}); +Inputs: title text - $scrout.=&searchphrasefield('Limit by keywords','keywords', - $ENV{'form.keywords'}); +Outputs: titletext with font wrapper - $scrout.=&searchphrasefield('Limit by URL','url', - $ENV{'form.url'}); +=cut -# $scrout.=&searchphrasefield('Limit by version','version', -# $ENV{'form.version'}); +###################################################################### +###################################################################### +sub titlefield { + my $title=shift; + return $title; +} - $scrout.=&searchphrasefield('Limit by notes','notes', - $ENV{'form.notes'}); +###################################################################### +###################################################################### - $scrout.=&searchphrasefield('Limit by abstract','abstract', - $ENV{'form.abstract'}); +=pod - $ENV{'form.mime'}='any' unless length($ENV{'form.mime'}); - $scrout.=&selectbox('Limit by MIME type','mime', - $ENV{'form.mime'}, - 'any','Any type', - \&{Apache::loncommon::filedescriptionex}, - (&Apache::loncommon::fileextensions)); +=item viewoptiontext() - $ENV{'form.language'}='any' unless length($ENV{'form.language'}); +Inputs: codename for view option - $scrout.=&selectbox('Limit by language','language', - $ENV{'form.language'},'any','Any Language', - \&{Apache::loncommon::languagedescription}, - (&Apache::loncommon::languageids), - ); +Outputs: displayed text -# ------------------------------------------------ Compute date selection boxes - $scrout.=<<CREATIONDATESTART; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY CREATION DATE RANGE:</b> -</font> -<br /> -between: -CREATIONDATESTART - $scrout.=&dateboxes('creationdatestart',1,1,1976, - $ENV{'form.creationdatestart_month'}, - $ENV{'form.creationdatestart_day'}, - $ENV{'form.creationdatestart_year'}, - ); - $scrout.=<<CREATIONDATEEND; -and: -CREATIONDATEEND - $scrout.=&dateboxes('creationdateend',12,31,2051, - $ENV{'form.creationdateend_month'}, - $ENV{'form.creationdateend_day'}, - $ENV{'form.creationdateend_year'}, - ); - $scrout.="</p>"; +=cut - $scrout.=<<LASTREVISIONDATESTART; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY LAST REVISION DATE RANGE: -</b></font> -<br />between: -LASTREVISIONDATESTART - $scrout.=&dateboxes('lastrevisiondatestart',1,1,1976, - $ENV{'form.lastrevisiondatestart_month'}, - $ENV{'form.lastrevisiondatestart_day'}, - $ENV{'form.lastrevisiondatestart_year'}, - ); - $scrout.=<<LASTREVISIONDATEEND; -and: -LASTREVISIONDATEEND - $scrout.=&dateboxes('lastrevisiondateend',12,31,2051, - $ENV{'form.lastrevisiondateend_month'}, - $ENV{'form.lastrevisiondateend_day'}, - $ENV{'form.lastrevisiondateend_year'}, - ); - $scrout.='</p>'; - - $scrout.=&searchphrasefield('Limit by publisher/owner','owner', - $ENV{'form.owner'}); - - $ENV{'form.copyright'}='any' unless length($ENV{'form.copyright'}); - $scrout.=&selectbox('Limit by copyright/distribution','copyright', - $ENV{'form.copyright'}, - 'any','Any copyright/distribution', - \&{Apache::loncommon::copyrightdescription}, - (&Apache::loncommon::copyrightids), - ); +###################################################################### +###################################################################### +sub viewoptiontext { + my $code=shift; + my %desc=&Apache::lonlocal::texthash + ('detailed' => "Detailed Citation View", + 'xml' => 'XML/SGML', + 'compact' => 'Compact View', + 'fielded' => 'Fielded Format', + 'summary' => 'Summary View', + 'summarypreview' => 'Summary Preview', + 'detailedpreview' => 'Detailed Citation Preview'); + return $desc{$code}; +} + +###################################################################### +###################################################################### -# ------------------------------------------- Compute customized metadata field - $scrout.=<<CUSTOMMETADATA; -<p> -<font color="#800000" face="helvetica"><b>LIMIT BY SPECIAL METADATA FIELDS:</b> -</font> -For resource-specific metadata, enter in an expression in the form of -<i>key</i>=<i>value</i> separated by operators such as AND, OR or NOT.<br /> -<b>Example:</b> grandmother=75 OR grandfather=85 -<br /> -CUSTOMMETADATA -$scrout.=&simpletextfield('custommetadata',$ENV{'form.custommetadata'}); -$scrout.=' <i>initial users of this system do not need to worry about this option</i>'; +=pod - $scrout.=<<CUSTOMSHOW; -<p> -<font color="#800000" face="helvetica"><b>SHOW SPECIAL METADATA FIELDS:</b> -</font> -Enter in a space-separated list of special metadata fields to show -in a fielded listing for each record result. -<br /> -CUSTOMSHOW -$scrout.=&simpletextfield('customshow',$ENV{'form.customshow'}); -$scrout.=' <i>initial users of this system do not need to worry about this option</i>'; - -# ---------------------------------------------------------------- Print screen - $r->print(<<ENDDOCUMENT); -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -<script type="text/javascript"> - function openhelp(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=400,height=300'); - openhelpwin.focus(); +=item viewoptions() + +Inputs: none + +Outputs: text for box with view options + +=cut + +###################################################################### +###################################################################### +sub viewoptions { + my $scrout; + if (! defined($env{'form.viewselect'})) { + $env{'form.viewselect'}='detailed'; + } + $scrout .= '<span class="LC_nobreak">' + .&mt('Type:').' ' + .&Apache::lonmeta::selectbox('viewselect', + $env{'form.viewselect'}, + \&viewoptiontext, + sort(keys(%Views))) + .'</span>'; + my $countselect = &Apache::lonmeta::selectbox('show', + $env{'form.show'}, + undef, + (10,20,50,100,1000,10000)); + $scrout .= ' <span class="LC_nobreak">' + .&mt('Records per Page:').' '.$countselect + .'</span>'.$/; + return $scrout; +} + +###################################################################### +###################################################################### + +=pod + +=item searchhelp() + +Inputs: none + +Outputs: return little blurb on how to enter searches + +=cut + +###################################################################### +###################################################################### +sub searchhelp { + return &mt('Enter words and quoted phrases'); +} + +###################################################################### +###################################################################### + +=pod + +=item &get_persistent_form_data() + +Inputs: filename of database + +Outputs: returns undef on database errors. + +This function is the reverse of &make_persistent() for form data. +Retrieve persistent data from %persistent_db. Retrieved items will have their +values unescaped. If a form value already exists in $env, it will not be +overwritten. Form values that are array references may have values appended +to them. + +=cut + +###################################################################### +###################################################################### +sub get_persistent_form_data { + my $filename = shift; + return 0 if (! -e $filename); + return undef if (! tie(%persistent_db,'GDBM_File',$filename, + &GDBM_READER(),0640)); + # + # These make sure we do not get array references printed out as 'values'. + my %arrays_allowed = ('form.domains'=>1); + # + # Loop through the keys, looking for 'form.' + foreach my $name (keys(%persistent_db)) { + next if ($name !~ /^form./); + # Kludgification begins! + if ($name eq 'form.domains' && + $env{'form.searchmode'} eq 'basic' && + $env{'form.phase'} ne 'disp_basic') { + next; + } + # End kludge (hopefully) + next if (exists($env{$name})); + my @values = map { + &unescape($_); + } split(',',$persistent_db{$name}); + next if (@values <1); + if ($arrays_allowed{$name}) { + $env{$name} = [@values]; + } else { + $env{$name} = $values[0] if ($values[0]); + } } -</script> -</head> -<body bgcolor="#FFFFFF"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> -<form method="post" action="/adm/searchcat"> -$hidden -<hr /> -<h3>Basic Search</h3> -<p> -Enter terms or phrases separated by search operators -such as AND, OR, or NOT then press SEARCH below. Terms should be specific -to the title, author, subject, notes, or abstract information associated -with a resource. -<br /> -ENDDOCUMENT - $r->print(&simpletextfield('basicexp',$ENV{'form.basicexp'})); - $r->print(' '); - $r->print(&simplecheckbox('titleonly',$ENV{'form.titleonly'})); - $r->print('<font color="#800000">Title only</font> '); -# $r->print(&simplecheckbox('allversions',$ENV{'form.allversions'})); -# <font color="#800000">Search historic archives</font> - $r->print(<<ENDDOCUMENT); -<br /> -<input type="submit" name="basicsubmit" value='SEARCH' /> -<input type="reset" name="reset" value='RESET' /> -$closebutton -$basicviewselect -<input type="button" value="HELP" onClick="openhelp()" /> -</p> -<hr /> -<h3>Advanced Search</h3> -$scrout -<p> -<input type="submit" name="advancedsubmit" value='SEARCH' /> -<input type="reset" name="reset" value='RESET' /> -$closebutton -$advancedviewselect -<input type="button" value="HELP" onClick="openhelp()" /> -</p> -</form> -</body> -</html> -ENDDOCUMENT - return OK; -} + untie (%persistent_db); + return 1; +} -# ----------- grab unprocessed CGI variables that may have been appended to URL -sub get_unprocessed_cgi { - foreach (split(/&/,$ENV{'QUERY_STRING'})) { - my ($name, $value) = split(/=/,$_); - $value =~ tr/+/ /; - $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - if ($name eq 'catalogmode' or $name eq 'launch' or $name eq 'acts') { - $ENV{'form.'.$name}=$value; - } +###################################################################### +###################################################################### + +=pod + +=item &get_persistent_data() + +Inputs: filename of database, ref to array of values to recover. + +Outputs: array of values. Returns undef on error. + +This function is the reverse of &make_persistent(); +Retrieve persistent data from %persistent_db. Retrieved items will have their +values unescaped. If the item is 'domains; then the returned +value will be a hash pointer. Otherwise, if the item contains +commas (before unescaping), the returned value will be an array pointer. + +=cut + +###################################################################### +###################################################################### +sub get_persistent_data { + my $filename = shift; + my @Vars = @{shift()}; + my @Values; # Return array + return undef if (! -e $filename); + return undef if (! tie(%persistent_db,'GDBM_File',$filename, + &GDBM_READER(),0640)); + foreach my $name (@Vars) { + if (! exists($persistent_db{$name})) { + push @Values, undef; + next; + } + if ($name eq 'domains') { + my %valueshash; + my @items= map { &unescape($_); } split(',',$persistent_db{$name}); + foreach my $item (@items) { + if ($item =~ /=/) { + my ($key,$val) = map { &unescape($_); } split(/=/,$item); + $valueshash{$key} = $val; + } + } + push(@Values,\%valueshash); + } else { + my @values = map { + &unescape($_); + } split(',',$persistent_db{$name}); + if (@values <= 1) { + push @Values,$values[0]; + } else { + push @Values,\@values; + } + } } + untie (%persistent_db); + return @Values; } -# ------------------------------------------------------------- make persistent +###################################################################### +###################################################################### + +=pod + +=item &make_persistent() + +Inputs: Hash of values to save, filename of persistent database. + +Store variables away to the %persistent_db. +Values will be escaped. Values that are array pointers will have their +elements escaped and concatenated in a comma separated string. Values +that are hash pointers will have their keys and values escaped and +concatenated in a comma separated string. + +=cut + +###################################################################### +###################################################################### sub make_persistent { - my $persistent=''; - - foreach (keys %ENV) { - if (/^form\./ && !/submit/) { - my $name=$_; - my $key=$name; - $ENV{$key}=~s/\'//g; # do not mess with html field syntax - $name=~s/^form\.//; - $persistent.=<<END; -<input type='hidden' name='$name' value='$ENV{$key}' /> -END + my %save = %{shift()}; + my $filename = shift; + return undef if (! tie(%persistent_db,'GDBM_File', + $filename,&GDBM_WRCREAT(),0640)); + foreach my $name (keys(%save)) { + my @values=(); + if (ref($save{$name}) eq 'ARRAY') { + @values = @{$save{$name}}; + } elsif (ref($save{$name}) eq 'HASH') { + foreach my $key (%{$save{$name}}) { + push(@values,&escape($key).'='.&escape($save{$name}{$key})); + } + } else { + @values = $save{$name}; } + # We handle array and hash references, but not recursively. + my $store = join(',', map { &escape($_); } @values ); + $persistent_db{$name} = $store; } - return $persistent; + untie(%persistent_db); + return 1; } -# --------------------------------------------------------- Various form fields +###################################################################### +###################################################################### -sub simpletextfield { - my ($name,$value)=@_; - return '<input type=text name=\''.$name. - '\' size=20 value=\''.$value.'\' />'; -} +=pod -sub simplecheckbox { - my ($name,$value)=@_; - my $checked=''; - $checked="CHECKED" if $value eq 'on'; - return '<input type=checkbox name=\''.$name.'\' '. $checked . '>'; -} +=item &make_form_data_persistent() -sub searchphrasefield { - my ($title,$name,$value)=@_; - my $instruction=<<END; -Enter terms or phrases separated by search operators such -as AND, OR, or NOT. -END - my $uctitle=uc($title); - return "\n<p><font color=\"#800000\" face=\"helvetica\"><b>$uctitle:</b>". - "</FONT> $instruction<br />". - '<input type=text name="'.$name.'" size=80 value=\''.$value.'\'>'; -} - -sub dateboxes { - my ($name,$defaultmonth,$defaultday,$defaultyear, - $currentmonth,$currentday,$currentyear)=@_; - ($defaultmonth,$defaultday,$defaultyear)=('','',''); - my $month=<<END; -<select name="${name}_month"> -<option value='$defaultmonth'> </option> -<option value="1">January</option> -<option value="2">February</option> -<option value="3">March</option> -<option value="4">April</option> -<option value="5">May</option> -<option value="6">June</option> -<option value="7">July</option> -<option value="8">August</option> -<option value="9">September</option> -<option value="10">October</option> -<option value="11">November</option> -<option value="12">December</option> -</select> -END - $month=~s/(\"$currentmonth\")/$1 SELECTED/ if length($currentmonth); - my $day=<<END; -<select name="${name}_day"> -<option value='$defaultday'> </option> -<option value="1">1</option> -<option value="2">2</option> -<option value="3">3</option> -<option value="4">4</option> -<option value="5">5</option> -<option value="6">6</option> -<option value="7">7</option> -<option value="8">8</option> -<option value="9">9</option> -<option value="10">10</option> -<option value="11">11</option> -<option value="12">12</option> -<option value="13">13</option> -<option value="14">14</option> -<option value="15">15</option> -<option value="16">16</option> -<option value="17">17</option> -<option value="18">18</option> -<option value="19">19</option> -<option value="20">20</option> -<option value="21">21</option> -<option value="22">22</option> -<option value="23">23</option> -<option value="24">24</option> -<option value="25">25</option> -<option value="26">26</option> -<option value="27">27</option> -<option value="28">28</option> -<option value="29">29</option> -<option value="30">30</option> -<option value="31">31</option> -</select> -END - $day=~s/(\"$currentday\")/$1 SELECTED/ if length($currentday); - my $year=<<END; -<select name="${name}_year"> -<option value='$defaultyear'> </option> -<option value="1976">1976</option> -<option value="1977">1977</option> -<option value="1978">1978</option> -<option value="1979">1979</option> -<option value="1980">1980</option> -<option value="1981">1981</option> -<option value="1982">1982</option> -<option value="1983">1983</option> -<option value="1984">1984</option> -<option value="1985">1985</option> -<option value="1986">1986</option> -<option value="1987">1987</option> -<option value="1988">1988</option> -<option value="1989">1989</option> -<option value="1990">1990</option> -<option value="1991">1991</option> -<option value="1992">1992</option> -<option value="1993">1993</option> -<option value="1994">1994</option> -<option value="1995">1995</option> -<option value="1996">1996</option> -<option value="1997">1997</option> -<option value="1998">1998</option> -<option value="1999">1999</option> -<option value="2000">2000</option> -<option value="2001">2001</option> -<option value="2002">2002</option> -<option value="2003">2003</option> -<option value="2004">2004</option> -<option value="2005">2005</option> -<option value="2006">2006</option> -<option value="2007">2007</option> -<option value="2008">2008</option> -<option value="2009">2009</option> -<option value="2010">2010</option> -<option value="2011">2011</option> -<option value="2012">2012</option> -<option value="2013">2013</option> -<option value="2014">2014</option> -<option value="2015">2015</option> -<option value="2016">2016</option> -<option value="2017">2017</option> -<option value="2018">2018</option> -<option value="2019">2019</option> -<option value="2020">2020</option> -<option value="2021">2021</option> -<option value="2022">2022</option> -<option value="2023">2023</option> -<option value="2024">2024</option> -<option value="2025">2025</option> -<option value="2026">2026</option> -<option value="2027">2027</option> -<option value="2028">2028</option> -<option value="2029">2029</option> -<option value="2030">2030</option> -<option value="2031">2031</option> -<option value="2032">2032</option> -<option value="2033">2033</option> -<option value="2034">2034</option> -<option value="2035">2035</option> -<option value="2036">2036</option> -<option value="2037">2037</option> -<option value="2038">2038</option> -<option value="2039">2039</option> -<option value="2040">2040</option> -<option value="2041">2041</option> -<option value="2042">2042</option> -<option value="2043">2043</option> -<option value="2044">2044</option> -<option value="2045">2045</option> -<option value="2046">2046</option> -<option value="2047">2047</option> -<option value="2048">2048</option> -<option value="2049">2049</option> -<option value="2050">2050</option> -<option value="2051">2051</option> -</select> -END - $year=~s/(\"$currentyear\")/$1 SELECTED/ if length($currentyear); - return "$month$day$year"; -} +Inputs: filename of persistent database. -sub selectbox { - my ($title,$name,$value,$anyvalue,$anytag,$functionref,@idlist)=@_; - my $uctitle=uc($title); - my $selout="\n<p><font color=\"#800000\" face=\"helvetica\"><b>$uctitle:". - "</b></font><br />".'<select name="'.$name.'">'; - foreach ($anyvalue,@idlist) { - $selout.='<option value=\''.$_.'\''; - if ($_ eq $value and !/^any$/) { - $selout.=' selected>'.&{$functionref}($_).'</option>'; - } - elsif ($_ eq $value and /^$anyvalue$/) { - $selout.=' selected>'.$anytag.'</option>'; - } - else {$selout.='>'.&{$functionref}($_).'</option>';} +Store most form variables away to the %persistent_db. +Values will be escaped. Values that are array pointers will have their +elements escaped and concatenated in a comma separated string. + +=cut + +###################################################################### +###################################################################### +sub make_form_data_persistent { + my $r = shift; + my $filename = shift; + my %save; + foreach (keys(%env)) { + next if (!/^form/ || /submit/); + $save{$_} = $env{$_}; } - return $selout.'</select>'; + return &make_persistent(\%save,$filename); } -sub testf { - return @_[0]; -} +###################################################################### +###################################################################### + +=pod + +=item &parse_advanced_search() + +Parse advanced search form and return the following: + +=over 4 -# ----------------------------------------------- Performing an advanced search -sub advancedsearch { - my ($r,$envhash)=@_; - my %ENV=%{$envhash}; +=item $query Scalar containing an SQL query. + +=item $customquery Scalar containing a custom query. + +=item $customshow Scalar containing commands to show custom metadata. + +=item $libraries_to_query Reference to array of domains to search. + +=back +=cut + +###################################################################### +###################################################################### +sub parse_advanced_search { + my ($r,$closebutton,$hidden_fields)=@_; + my @BasicFields = ('title','author','subject','keywords','url','version', + 'notes','abstract','extension','owner','authorspace', +# 'custommetadata','customshow', + 'modifyinguser','standards','mime'); + my @StatsFields = &statfields(); + my @EvalFields = &evalfields(); my $fillflag=0; + my $pretty_search_string = ""; # Clean up fields for safety - for my $field ('title','author','subject','keywords','url','version', - 'creationdatestart_month','creationdatestart_day', + for my $field (@BasicFields, + 'creationdatestart_month','creationdatestart_day', 'creationdatestart_year','creationdateend_month', 'creationdateend_day','creationdateend_year', 'lastrevisiondatestart_month','lastrevisiondatestart_day', 'lastrevisiondatestart_year','lastrevisiondateend_month', - 'lastrevisiondateend_day','lastrevisiondateend_year', - 'notes','abstract','mime','language','owner', - 'custommetadata','customshow') { - $ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g; + 'lastrevisiondateend_day','lastrevisiondateend_year') { + $env{'form.'.$field}=~s/[^\w\/\s\(\)\=\-\"\'.\*]//g; } - + foreach ('mode','form','element') { + # is this required? Hmmm. + next if (! exists($env{'form.'.$_})); + $env{'form.'.$_}=&unescape($env{'form.'.$_}); + $env{'form.'.$_}=~s/[^\w\/\s\(\)\=\-\"\']//g; + } + # Preprocess the category form element. + $env{'form.category'} = 'any' if (! defined($env{'form.category'}) || + ref($env{'form.category'})); + # # Check to see if enough information was filled in - for my $field ('title','author','subject','keywords','url','version', - 'notes','abstract','mime','language','owner', - 'custommetadata') { - if (&filled($ENV{"form.$field"})) { + foreach my $field (@BasicFields) { + if (&filled($env{'form.'.$field})) { $fillflag++; } } - unless ($fillflag) { - &output_blank_field_error($r); - return OK; + foreach my $field (@StatsFields,@EvalFields) { + if (&filled($env{'form.'.$field.'_max'})) { + $fillflag++; + } + if (&filled($env{'form.'.$field.'_min'})) { + $fillflag++; + } } - + for my $field ('lowestgradelevel','highestgradelevel') { + if ( $env{'form.'.$field} =~ /^\d+$/ && + $env{'form.'.$field} > 0) { + $fillflag++; + } + } + if ($env{'form.area'} eq 'portfolio') { + # Added metadata fields + for (my $i=0; $i<$env{'form.numaddedfields'} ; $i++) { + my $field = $env{'form.addedfield_'.$i}; + $field =~ s/^\s*(\S*)\s*$/$1/; + $field =~ s/\W/_/g; + if ($field ne '') { + $fillflag++; + } + } + } + if (! $fillflag) { + &output_blank_field_error($r,$closebutton, + 'phase=disp_adv',$hidden_fields); + return ; + } # Turn the form input into a SQL-based query my $query=''; - my @queries; + my $font = '<font color="#800000" face="helvetica">'; # Evaluate logical expression AND/OR/NOT phrase fields. - foreach my $field ('title','author','subject','notes','abstract','url', - 'keywords','version','owner') { - if ($ENV{'form.'.$field}) { - push @queries,&build_SQL_query($field,$ENV{'form.'.$field}); - } + foreach my $field (@BasicFields) { + next if (!defined($env{'form.'.$field}) || $env{'form.'.$field} eq ''); + my ($error,$SQLQuery) = + &process_phrase_input($env{'form.'.$field}, + $env{'form.'.$field.'_related'},$field); + if (defined($error)) { + &output_unparsed_phrase_error($r,$closebutton,'phase=disp_adv', + $hidden_fields,$field); + return; + } else { + $pretty_search_string .= + $font.$field.'</font>: '.$env{'form.'.$field}; + if ($env{'form.'.$field.'_related'}) { + my @Words = + &Apache::loncommon::get_related_words + ($env{'form.'.$field}); + if (@Words) { + $pretty_search_string.= ' with related words: '. + join(', ',@Words[0..4]); + } else { + $pretty_search_string.= ' with related words.'; + } + } + $pretty_search_string .= '<br />'; + push (@queries,$SQLQuery); + } } - # Evaluate option lists - if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') { - push @queries,"(language like \"$ENV{'form.language'}\")"; + # + # Make the 'mime' from 'form.category' and 'form.extension' + # + my $searchphrase; + if (exists($env{'form.category'}) && + $env{'form.category'} !~ /^\s*$/ && + $env{'form.category'} ne 'any') { + my @extensions = &Apache::loncommon::filecategorytypes + ($env{'form.category'}); + if (scalar(@extensions) > 0) { + $searchphrase = join(' OR ',@extensions); + } } - if ($ENV{'form.mime'} and $ENV{'form.mime'} ne 'any') { - push @queries,"(mime like \"$ENV{'form.mime'}\")"; + if (defined($searchphrase)) { + my ($error,$SQLsearch) = &process_phrase_input($searchphrase,0,'mime'); + push @queries,$SQLsearch; + $pretty_search_string .=$font.'mime</font> contains <b>'. + $searchphrase.'</b><br />'; } - if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') { - push @queries,"(copyright like \"$ENV{'form.copyright'}\")"; + # + # Evaluate option lists + if ($env{'form.lowestgradelevel'} && + $env{'form.lowestgradelevel'} ne '0' && + $env{'form.lowestgradelevel'} =~ /^\d+$/) { + push(@queries, + '(lowestgradelevel>='.$env{'form.lowestgradelevel'}.')'); + $pretty_search_string.="lowestgradelevel>=". + $env{'form.lowestgradelevel'}."<br />\n"; + } + if ($env{'form.highestgradelevel'} && + $env{'form.highestgradelevel'} ne '0' && + $env{'form.highestgradelevel'} =~ /^\d+$/) { + push(@queries, + '(highestgradelevel<='.$env{'form.highestgradelevel'}.')'); + $pretty_search_string.="highestgradelevel<=". + $env{'form.highestgradelevel'}."<br />\n"; + } + if ($env{'form.language'} and $env{'form.language'} ne 'any') { + push @queries,"(language like \"$env{'form.language'}\")"; + $pretty_search_string.=$font."language</font>= ". + &Apache::loncommon::languagedescription($env{'form.language'}). + "<br />\n"; + } + if ($env{'form.copyright'} and $env{'form.copyright'} ne 'any') { + push @queries,"(copyright like \"$env{'form.copyright'}\")"; + $pretty_search_string.=$font."copyright</font> = ". + &Apache::loncommon::copyrightdescription($env{'form.copyright'}). + "<br />\n"; + } + if ($env{'form.area'} eq 'portfolio') { + # + # Added metadata fields + for (my $i=0; $i<$env{'form.numaddedfields'} ; $i++) { + my $field = $env{'form.addedfield_'.$i}; + $field =~ s/^\s*(\S*)\s*$/$1/; + $field =~ s/\W/_/g; + $field =~ tr/A-Z/a-z/; + if ($field ne '') { + my $value = $env{'form.addedvalues_'.$i}; + if ($value ne '') { + $value =~ s/'/''/g; #' stupid emacs + my ($error,$query) = + &process_phrase_input($value,0,'pf.value'); + if (!defined($error)) { + push(@queries,"pf.field = '$field' AND $query"); + $pretty_search_string .= + $font.$field.'</font>: '. + $env{'form.addedvalues_'.$i}.'<br />'; + } + } else { + push(@queries,"pf.field = '$field' AND pf.value IS NULL"); + } + } + } + } else { + # + # Statistics + foreach my $field (@StatsFields,@EvalFields) { + my ($min,$max); + if (exists($env{'form.'.$field.'_min'}) && + $env{'form.'.$field.'_min'} ne '') { + $min = $env{'form.'.$field.'_min'}; + } + if (exists($env{'form.'.$field.'_max'}) && + $env{'form.'.$field.'_max'} ne '') { + $max = $env{'form.'.$field.'_max'}; + } + next if (! defined($max) && ! defined($min)); + if (defined($min) && defined($max)) { + ($min,$max) = sort {$a <=>$b} ($min,$max); + } + if (defined($min) && $min =~ /^(\d+\.\d+|\d+|\.\d+)$/) { + push(@queries,'('.$field.'>'.$min.')'); + $pretty_search_string.=$font.$field.'</font>>'.$min.'<br />'; + } + if (defined($max) && $max =~ /^(\d+\.\d+|\d+|\.\d+)$/) { + push(@queries,'('.$field.'<'.$max.')'); + $pretty_search_string.=$font.$field.'</font><'.$max.'<br />'; + } + } } + # # Evaluate date windows - my $datequery=&build_date_queries( - $ENV{'form.creationdatestart_month'}, - $ENV{'form.creationdatestart_day'}, - $ENV{'form.creationdatestart_year'}, - $ENV{'form.creationdateend_month'}, - $ENV{'form.creationdateend_day'}, - $ENV{'form.creationdateend_year'}, - $ENV{'form.lastrevisiondatestart_month'}, - $ENV{'form.lastrevisiondatestart_day'}, - $ENV{'form.lastrevisiondatestart_year'}, - $ENV{'form.lastrevisiondateend_month'}, - $ENV{'form.lastrevisiondateend_day'}, - $ENV{'form.lastrevisiondateend_year'}, - ); - # Test to see if date windows are legitimate - if ($datequery=~/^Incorrect/) { - &output_date_error($r,$datequery); - return OK; - } - elsif ($datequery) { + my $cafter = + &Apache::lonhtmlcommon::get_date_from_form('creationdate1'); + my $cbefore = + &Apache::lonhtmlcommon::get_date_from_form('creationdate2'); + if ($cafter > $cbefore) { + my $tmp = $cafter; + $cafter = $cbefore; + $cbefore = $tmp; + } + my $mafter = + &Apache::lonhtmlcommon::get_date_from_form('revisiondate1'); + my $mbefore = + &Apache::lonhtmlcommon::get_date_from_form('revisiondate2'); + if ($mafter > $mbefore) { + my $tmp = $mafter; + $mafter = $mbefore; + $mbefore = $tmp; + } + my ($datequery,$error,$prettydate)=&build_date_queries($cafter,$cbefore, + $mafter,$mbefore); + if (defined($error)) { + &output_date_error($r,$error,$closebutton,$hidden_fields); + } elsif (defined($datequery)) { + # Here is where you would set up pretty_search_string to output + # date query information. + $pretty_search_string .= '<br />'.$prettydate.'<br />'; push @queries,$datequery; } - + # # Process form information for custom metadata querying - my $customquery=''; - if ($ENV{'form.custommetadata'}) { - $customquery=&build_custommetadata_query('custommetadata', - $ENV{'form.custommetadata'}); + my $customquery=undef; + ## + ## The custom metadata search was removed q long time ago mostly + ## because I was unable to figureout exactly how it worked and could + ## not imagine people actually using it. MH + ## + # if ($env{'form.custommetadata'}) { + # $pretty_search_string .=$font."Custom Metadata Search</font>: <b>". + # $env{'form.custommetadata'}."</b><br />\n"; + # $customquery=&build_custommetadata_query('custommetadata', + # $env{'form.custommetadata'}); + # } + my $customshow=undef; + # if ($env{'form.customshow'}) { + # $pretty_search_string .=$font."Custom Metadata Display</font>: <b>". + # $env{'form.customshow'}."</b><br />\n"; + # $customshow=$env{'form.customshow'}; + # $customshow=~s/[^\w\s]//g; + # my @fields=split(/\s+/,$customshow); + # $customshow=join(" ",@fields); + # } + ## + ## Deal with restrictions to given domains + ## + my ($libraries_to_query,$pretty_domains_string,$domains_to_query) = + &parse_domain_restrictions(); + if ($pretty_domains_string) { + $pretty_search_string .= $pretty_domains_string."<br />\n"; } - my $customshow=''; - if ($ENV{'form.customshow'}) { - $customshow=$ENV{'form.customshow'}; - $customshow=~s/[^\w\s]//g; - my @fields=split(/\s+/,$customshow); - $customshow=join(" ",@fields); - } - # Send query statements over the network to be processed by either the SQL - # database or a recursive scheme of 'grep'-like actions (for custom - # metadata). + # if (@queries) { - $query=join(" AND ",@queries); - $query="select * from metadata where $query"; - my $reply; # reply hash reference - unless ($customquery or $customshow) { - $reply=&Apache::lonnet::metadata_query($query); - } - else { - $reply=&Apache::lonnet::metadata_query($query, - $customquery,$customshow); - } - &output_results('Advanced',$r,$envhash,$customquery,$reply); + if ($env{'form.area'} eq 'portfolio') { + $query ="SELECT pm.*,pa.keynum,pa.scope FROM portfolio_metadata pm, portfolio_access pa, portfolio_addedfields pf WHERE (pm.url = pa.url AND pf.url = pm.url AND (pa.start < UTC_TIMESTAMP() AND (pa.end IS NULL OR pa.end > UTC_TIMESTAMP())) AND (".join(') AND (',@queries).'))'; + } else { + $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,$domains_to_query); +} + +sub parse_domain_restrictions { + my $libraries_to_query = undef; + my $domains_to_query = undef; + # $env{'form.domains'} can be either a scalar or an array reference. + # We need an array. + if (! exists($env{'form.domains'}) || $env{'form.domains'} eq '') { + return (undef,'',undef); + } + my @allowed_domains = &Apache::loncommon::get_env_multiple('form.domains'); + # + my %domain_hash = (); + my $pretty_domains_string; + foreach (@allowed_domains) { + $domain_hash{$_}++; + } + if ($domain_hash{'any'}) { + $pretty_domains_string = &mt("in all LON-CAPA domains."); + } else { + if (@allowed_domains > 1) { + $pretty_domains_string = &mt("in LON-CAPA domains:"); + } else { + $pretty_domains_string = &mt("in LON-CAPA domain "); + } + foreach (sort @allowed_domains) { + $pretty_domains_string .= "<b>".$_."</b> "; + } + my %library_servers = &Apache::lonnet::get_unique_servers(\@allowed_domains, + 'library'); + my (%older_library_servers,%okdoms,%domains_for_id); + map { $okdoms{$_} = 1; } @allowed_domains; + foreach my $key (keys(%library_servers)) { + if (&Apache::lonnet::get_server_loncaparev('',$key) =~ /^\'?(\d+)\.(\d+)/) { + my $major = $1; + my $minor = $2; + if (($major < 2) || (($major == 2) && ($minor < 11))) { + map { $older_library_servers{$_} = 1; } + &Apache::lonnet::machine_ids($library_servers{$key}); + } else { + my %possdoms; + map { $possdoms{$_}=1 if ($okdoms{$_}); } + &Apache::lonnet::machine_domains($library_servers{$key}); + $domains_for_id{$key} = join(',',sort(keys(%possdoms))); + } + } + } + my %servers = (%library_servers,%older_library_servers); + $libraries_to_query = [keys(%servers)]; + $domains_to_query = \%domains_for_id; } - elsif ($customquery) { - my $reply; # reply hash reference - $reply=&Apache::lonnet::metadata_query('', - $customquery,$customshow); - &output_results('Advanced',$r,$envhash,$customquery,$reply); - } - # should not get to this point - return 'Error. Should not have gone to this point.'; -} - -# --------------------------------------------------- Performing a basic search -sub basicsearch { - my ($r,$envhash)=@_; - my %ENV=%{$envhash}; + return ($libraries_to_query, + $pretty_domains_string, + $domains_to_query); +} + +###################################################################### +###################################################################### + +=pod + +=item &parse_basic_search() + +Parse the basic search form and return a scalar containing an sql query. + +=cut + +###################################################################### +###################################################################### +sub parse_basic_search { + my ($r,$closebutton)=@_; + # # Clean up fields for safety for my $field ('basicexp') { - $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g; + $env{"form.$field"}=~s/[^\w\s\'\"\!\(\)\-\*]//g; } - - # Check to see if enough is filled in - unless (&filled($ENV{'form.basicexp'})) { - &output_blank_field_error($r); + foreach ('mode','form','element') { + # is this required? Hmmm. + next unless (exists($env{"form.$_"})); + $env{"form.$_"}=&unescape($env{"form.$_"}); + $env{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g; + } + my ($libraries_to_query,$pretty_domains_string,$domains_to_query) = + &parse_domain_restrictions(); + # + # Check to see if enough of a query is filled in + my $search_string = $env{'form.basicexp'}; + if (! &filled($search_string)) { + &output_blank_field_error($r,$closebutton,'phase=disp_basic'); return OK; } + my $pretty_search_string=$search_string; + my @Queries; + my @fields = ('title','author','subject','notes','abstract','keywords'); + my $searchfield; + if ($env{'form.area'} eq 'portfolio') { + $searchfield = 'concat_ws(" ",pm.'.join(',pm.',@fields).')'; + } else { + $searchfield = 'concat_ws(" ",'.join(',',@fields).')'; + } + 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; + if ($env{'form.area'} eq 'portfolio') { + $final_query = 'SELECT pm.*,pa.keynum,pa.scope FROM portfolio_metadata pm, portfolio_access pa WHERE (pm.url = pa.url AND (pa.start < UTC_TIMESTAMP() AND (pa.end IS NULL OR pa.end > UTC_TIMESTAMP())) AND '.join(" AND ",@Queries).')'; + } else { + $final_query = 'SELECT * FROM metadata WHERE '.join(" AND ",@Queries); + } + # + if ($env{'form.related'}) { + $pretty_search_string.=' '.&mt('(including related words)'); + } + if (defined($pretty_domains_string) && $pretty_domains_string ne '') { + $pretty_search_string .= ' '.$pretty_domains_string; + } + $pretty_search_string .= "<br />\n"; + $pretty_search_string =~ s:^<br /> and ::; + &Apache::lonnet::logthis('simple search final query = '.$/.$final_query); + return ($final_query,$pretty_search_string, + $libraries_to_query,$domains_to_query); +} - # Build SQL query string based on form page - my $query=''; - my $concatarg=join('," ",', - ('title', 'author', 'subject', 'notes', 'abstract')); - $concatarg='title' if $ENV{'form.titleonly'}; - $query=&build_SQL_query('concat('.$concatarg.')',$ENV{'form.'.'basicexp'}); +############################################################### +############################################################### + +my @Phrases; + +sub concat { + my ($item) = @_; + my $results = ''; + foreach (@$item) { + if (ref($_) eq 'ARRAY') { + $results .= join(' ',@$_); + } + } + return $results; +} - # Get reply (either a hash reference to filehandles or bad connection) - my $reply=&Apache::lonnet::metadata_query('select * from metadata where '.$query); +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; +} - # Output search results +###################################################################### +###################################################################### - &output_results('Basic',$r,$envhash,$query,$reply); +=pod - return OK; -} +=item &related_version() -# ------------------------------------------------------------- build_SQL_query -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; +Modifies an input string to include related words. Words in the string +are replaced with parenthesized lists of 'OR'd words. For example +"torque" is replaced with "(torque OR word1 OR word2 OR ...)". + +Note: Using this twice on a string is probably silly. + +=cut + +###################################################################### +###################################################################### +sub related_version { + my ($word) = @_; + return (undef) if (lc($word) =~ /\b(or|and|not)\b/); + my @Words = &Apache::loncommon::get_related_words($word); + # Only use 4 related words + @Words = ($#Words>4? @Words[0..4] : @Words); + my $result = join " OR ", ($word,@Words); + return $result,sort(@Words); } -# ------------------------------------------------- build custom metadata query + +###################################################################### +###################################################################### + +=pod + +=item &build_custommetadata_query() + +Constructs a custom metadata query using a rather heinous regular +expression. + +=cut + +###################################################################### +###################################################################### sub build_custommetadata_query { my ($field_name,$logic_statement)=@_; my $q=new Text::Query('abc', @@ -751,1023 +1791,2038 @@ sub build_custommetadata_query { my $matchexp=${$q}{'-parse'}{'-build'}{'matchstring'}; # quick fix to change literal into xml tag-matching # will eventually have to write a separate builder module - my $oldmatchexp=$matchexp; - $matchexp=~s/(\w+)\\\=([\w\\\+]+)/\\\<$1\\\>\[\^\\\<\]\*$2\[\^\\\<\]\*\\\<\\\/$1\\\>/g; + # wordone=wordtwo becomes\<wordone\>[^\<] *wordtwo[^\<]*\<\/wordone\> + $matchexp =~ s/(\w+)\\=([\w\\\+]+)?# wordone=wordtwo is changed to + /\\<$1\\>?# \<wordone\> + \[\^\\<\]?# [^\<] + \*$2\[\^\\<\]?# *wordtwo[^\<] + \*\\<\\\/$1\\>?# *\<\/wordone\> + /g; return $matchexp; } -# - Recursively parse a reverse notation expression into a SQL query expression -sub recursive_SQL_query_build { - my ($dkey,$pattern)=@_; - my @matches=($pattern=~/(\[[^\]|\[]*\])/g); - return $pattern unless @matches; - foreach my $match (@matches) { - $match=~/\[ (\w+)\s(.*) \]/; - my ($key,$value)=($1,$2); - my $replacement=''; - if ($key eq 'literal') { - $replacement="($dkey like \"\%$value\%\")"; - } - elsif ($key eq 'not') { - $value=~s/like/not like/; -# $replacement="($dkey not like $value)"; - $replacement="$value"; - } - elsif ($key eq 'and') { - $value=~/(.*[\"|\)]) ([|\(|\^].*)/; - $replacement="($1 AND $2)"; - } - elsif ($key eq 'or') { - $value=~/(.*[\"|\)]) ([|\(|\^].*)/; - $replacement="($1 OR $2)"; - } - substr($pattern, - index($pattern,$match), - length($match), - $replacement - ); + +###################################################################### +###################################################################### + +=pod + +=item &build_date_queries() + +Builds a SQL logic query to check time/date entries. +Also reports errors (check for /^Incorrect/). + +=cut + +###################################################################### +###################################################################### +sub build_date_queries { + my ($cafter,$cbefore,$mafter,$mbefore) = @_; + my ($result,$error,$pretty_string); + # + # Verify the input + if (! defined($cafter) && ! defined($cbefore) && + ! defined($mafter) && ! defined($mbefore)) { + # This is an okay situation, so return undef for the error + return (undef,undef,undef); + } + if ((defined($cafter) && ! defined($cbefore)) || + (defined($cbefore) && ! defined($cafter))) { + # This is bad, so let them know + $error = &mt('Incorrect entry for the creation date. '. + 'You must specify both the beginning and ending dates.'); + } + if (! defined($error) && + ((defined($mafter) && ! defined($mbefore)) || + (defined($mbefore) && ! defined($mafter)))) { + # This is also bad, so let them know + $error = &mt('Incorrect entry for the last revision date. '. + 'You must specify both the beginning and ending dates.'); + } + if (! defined($error)) { + # + # Build the queries + my @queries; + if (defined($cbefore) && defined($cafter)) { + my (undef,undef,undef,$caday,$camon,$cayear) = localtime($cafter); + my (undef,undef,undef,$cbday,$cbmon,$cbyear) = localtime($cbefore); + # Correct for year being relative to 1900 + $cayear+=1900; $cbyear+=1900; + my $cquery= + '(creationdate BETWEEN '. + "'".$cayear.'-'.$camon.'-'.$caday."'". + ' AND '. + "'".$cbyear.'-'.$cbmon.'-'.$cbday." 23:59:59')"; + $pretty_string .= '<br />' if (defined($pretty_string)); + $pretty_string .= + &mt('created between [_1] and [_2]', + &Apache::lonlocal::locallocaltime($cafter), + &Apache::lonlocal::locallocaltime($cbefore+24*60*60-1)); + push(@queries,$cquery); + $pretty_string =~ s/ 00:00:00//g; + } + if (defined($mbefore) && defined($mafter)) { + my (undef,undef,undef,$maday,$mamon,$mayear) = localtime($mafter); + my (undef,undef,undef,$mbday,$mbmon,$mbyear) = localtime($mbefore); + # Correct for year being relative to 1900 + $mayear+=1900; $mbyear+=1900; + my $mquery= + '(lastrevisiondate BETWEEN '. + "'".$mayear.'-'.$mamon.'-'.$maday."'". + ' AND '. + "'".$mbyear.'-'.$mbmon.'-'.$mbday." 23:59:59')"; + push(@queries,$mquery); + $pretty_string .= '<br />' if (defined($pretty_string)); + $pretty_string .= + &mt('last revised between [_1] and [_2]', + &Apache::lonlocal::locallocaltime($mafter), + &Apache::lonlocal::locallocaltime($mbefore+24*60*60-1)); + $pretty_string =~ s/ 00:00:00//g; + } + if (@queries) { + $result .= join(" AND ",@queries); + } } - &recursive_SQL_query_build($dkey,$pattern); + return ($result,$error,$pretty_string); } -# ------------------------------------------------------------ Build date query -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; +###################################################################### +###################################################################### + +=pod + +=item ©right_check() + +Inputs: $Metadata, a hash pointer of metadata for a resource. + +Returns: 1 if the resource is available to the user making the query, + 0 otherwise. + +=cut + +###################################################################### +###################################################################### +sub copyright_check { + my $Metadata = shift; + # Check copyright tags and skip results the user cannot use + my (undef,undef,$resdom,$resname) = split('/', + $Metadata->{'url'}); + # Check for priv + if (($Metadata->{'copyright'} eq 'priv') && + (($env{'user.name'} ne $resname) && + ($env{'user.domain'} ne $resdom))) { + return 0; } - if (@queries) { - return join(" AND ",@queries); + # Check for domain + if (($Metadata->{'copyright'} eq 'domain') && + ($env{'user.domain'} ne $resdom)) { + return 0; + } + # Check for custom rights + if ($Metadata->{'copyright'} eq 'custom') { + return &Apache::lonnet::customaccess('bre',$Metadata->{'url'}); } - return ''; + return 1; } -# ----------------------------- format and output results based on a reply list -# There are two windows that this function writes to. The main search -# window ("srch") has a listing of the results. A secondary window ("popwin") -# gives the status of the network search (time elapsed, number of machines -# contacted, etc.) -sub output_results { - my $fnum; # search result counter - my ($mode,$r,$envhash,$query,$replyref)=@_; - my %ENV=%{$envhash}; - my %rhash=%{$replyref}; - my $compiledresult=''; - my $timeremain=300; - my $elapsetime=0; - my $resultflag=0; - my $tflag=1; +###################################################################### +###################################################################### - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); +=pod - # output beginning of search page - $r->print(<<BEGINNING); -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -BEGINNING +=item &ensure_db_and_table() - # conditional output of script functions dependent on the mode in - # which the search was invoked - $r->print(<<SCRIPT) if $ENV{'form.catalogmode'} eq 'interactive'; -<script type="text/javascript"> - function select_data(title,url) { - changeTitle(title); - changeURL(url); - self.close(); - } - function changeTitle(val) { - if (opener.inf.document.forms.resinfo.elements.t) { - opener.inf.document.forms.resinfo.elements.t.value=val; - } +Ensure we can get lonmysql to connect to the database and the table we +need exists. + +Inputs: $r, table id + +Returns: undef on error, 1 if the table exists. + +=cut + +###################################################################### +###################################################################### +sub ensure_db_and_table { + my ($r,$table) = @_; + ## + ## Sanity check the table id. + ## + if (! defined($table) || $table eq '' || $table =~ /\D/ ) { + $r->print(&Apache::loncommon::start_page('Error') + .'<p class="LC_error">' + .&mt('Unable to retrieve search results. ' + .'Unable to determine the table results were saved in.') + .'</p>' + . '<p>'.&mt('Internal info:').'</p>' + .'<pre>'.$table.'</pre>' + .&Apache::loncommon::end_page() + ); + return undef; + } + ## + ## Make sure we can connect and the table exists. + ## + my $connection_result = &Apache::lonmysql::connect_to_db(); + if (!defined($connection_result)) { + $r->print( + '<p class="LC_error">' + .&mt('Unable to connect to the MySQL database where your results are saved.') + .'</p>' + .&Apache::loncommon::end_page() + ); + &Apache::lonnet::logthis("lonsearchcat: unable to get lonmysql to". + " connect to database."); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + return undef; + } + my $table_check = &Apache::lonmysql::check_table($table); + if (! defined($table_check)) { + $r->print( + '<p class="LC_error">' + .&mt('A MySQL error has occurred.') + .'</p></form>' + .&Apache::loncommon::end_page()); + &Apache::lonnet::logthis("lonmysql was unable to determine the status". + " of table ".$table); + return undef; + } elsif (! $table_check) { + $r->print( + '<p class="LC_error">' + .&mt('The table of results could not be found.') + .'</p>' + ); + &Apache::lonnet::logthis("The user requested a table, ".$table. + ", that could not be found."); + return undef; + } + return 1; +} + +###################################################################### +###################################################################### + +=pod + +=item &print_sort_form() + +The sort feature is not implemented at this time. This form just prints +a link to change the search query. + +=cut + +###################################################################### +###################################################################### +sub print_sort_form { + my ($r,$pretty_query_string) = @_; + + ## + my %SortableFields=&Apache::lonlocal::texthash( + id => 'Default', + title => 'Title', + author => 'Author', + subject => 'Subject', + url => 'URL', + version => 'Version Number', + mime => 'Mime type', + lang => 'Language', + owner => 'Owner/Publisher', + copyright => 'Copyright', + hostname => 'Host', + creationdate => 'Creation Date', + lastrevisiondate => 'Revision Date' + ); + ## + my $table = $env{'form.table'}; + return if (! &ensure_db_and_table($r,$table)); + ## + ## Get the number of results + ## + my $total_results = &Apache::lonmysql::number_of_rows($table); + if (! defined($total_results)) { + $r->print("A MySQL error has occurred.</form>". + &Apache::loncommon::end_page()); + &Apache::lonnet::logthis("lonmysql was unable to determine the number". + " of rows in table ".$table); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + return; } - function changeURL(val) { - if (opener.inf.document.forms.resinfo.elements.u) { - opener.inf.document.forms.resinfo.elements.u.value=val; - } + my $js =<<END; +<script type="text/javascript"> +// <![CDATA[ + function change_sort() { + var newloc = "/adm/searchcat?phase=results"; + newloc += "&persistent_db_id=$env{'form.persistent_db_id'}"; + newloc += "&sortby="; + newloc += document.forms.statusform.elements.sortby.value; + parent.resultsframe.location= newloc; } +// ]]> +</script> +END + + my $start_page = &Apache::loncommon::start_page('Results',$js); + my $breadcrumbs= + &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching', + $env{'form.catalogmode'} ne 'import'); + + my $result = <<END; +$start_page +$breadcrumbs +<form name="statusform" action="" method="post" target="_top"> +<input type="hidden" name="catalogmode" value="import" /> +<input type="hidden" name="acts" value="" /> +END + +#<h2>Sort Results</h2> +#Sort by: <select size="1" name="sortby" onchange="javascript:change_sort();"> +# $env{'form.sortby'} = 'id' if (! defined($env{'form.sortby'})); +# foreach (keys(%SortableFields)) { +# $result.="<option name=\"$_\""; +# if ($_ eq $env{'form.sortby'}) { +# $result.=" selected "; +# } +# $result.=" >$SortableFields{$_}</option>\n"; +# } +# $result.="</select>\n"; + my $revise = &revise_button(); + $result.='<p>' + .&mt('Total of [quant,_1,match,matches] to your query.',$total_results) + .' '.$revise.'</p>' + .'<p>'.&mt('Search: ').$pretty_query_string + .'</p></form>'; + $r->print($result.&Apache::loncommon::end_page()); + return; +} + +##################################################################### +##################################################################### + +=pod + +=item MySQL Table Description + +MySQL table creation requires a precise description of the data to be +stored. The use of the correct types to hold data is vital to efficient +storage and quick retrieval of records. The columns must be described in +the following format: + +=cut + +##################################################################### +##################################################################### +# +# These should probably be scoped but I don't have time right now... +# +my @Datatypes; +my @Fullindicies; + +###################################################################### +###################################################################### + +=pod + +=item &create_results_table() + +Creates the table of search results by calling lonmysql. Stores the +table id in $env{'form.table'} + +Inputs: search area - either res or portfolio + +Returns: the identifier of the table on success, undef on error. + +=cut + +###################################################################### +###################################################################### +sub set_up_table_structure { + my ($tabletype) = @_; + my ($datatypes,$fullindicies) = + &LONCAPA::lonmetadata::describe_metadata_storage($tabletype); + # Copy the table description before modifying it... + @Datatypes = @{$datatypes}; + unshift(@Datatypes,{name => 'id', + type => 'MEDIUMINT', + restrictions => 'UNSIGNED NOT NULL', + primary_key => 'yes', + auto_inc => 'yes' }); + @Fullindicies = @{$fullindicies}; + return; +} + +sub create_results_table { + my ($area) = @_; + if ($area eq 'portfolio') { + &set_up_table_structure('portfolio_search'); + } else { + &set_up_table_structure('metadata'); + } + my $table = &Apache::lonmysql::create_table + ( { columns => \@Datatypes, + FULLTEXT => [{'columns' => \@Fullindicies},], + } ); + if (defined($table)) { + $env{'form.table'} = $table; + return $table; + } + return undef; # Error... +} + +###################################################################### +###################################################################### + +=pod + +=item Search Status update functions + +Each of the following functions changes the values of one of the +input fields used to display the search status to the user. The names +should be explanatory. + +Inputs: Apache request handler ($r), text to display. + +Returns: Nothing. + +=over 4 + +=item &update_count_status() + +=item &update_status() + +=item &update_seconds() + +=back + +=cut + +###################################################################### +###################################################################### +sub update_count_status { + my ($r,$text) = @_; + $text =~ s/\'/\\\'/g; + $r->print(<<SCRIPT); +<script type="text/javascript"> +// <![CDATA[ +document.statusform.count.value = ' $text' +// ]]> </script> SCRIPT - $r->print(<<SCRIPT) if $ENV{'form.catalogmode'} eq 'groupsearch'; + + $r->rflush(); +} + +sub update_status { + my ($r,$text) = @_; + $text =~ s/\'/\\\'/g; + $r->print(<<SCRIPT); <script type="text/javascript"> - function select_data(title,url) { -// alert('DEBUG: Should be storing '+title+' and '+url); - } - function queue(val) { - if (eval("document.forms.results.returnvalues["+val+"].checked")) { - document.forms.results.acts.value+='1a'+val+'b'; - } - else { - document.forms.results.acts.value+='0a'+val+'b'; - } - } - function select_group() { - window.location="/adm/groupsort?catalogmode=groupsearch&acts="+ - document.forms.results.acts.value; - } +// <![CDATA[ +document.statusform.status.value = ' $text' +// ]]> </script> SCRIPT + + $r->rflush(); +} + +{ + my $max_time = 300; # seconds for the search to complete + my $start_time = 0; + my $last_time = 0; + +sub reset_timing { + $start_time = 0; + $last_time = 0; +} + +sub time_left { + if ($start_time == 0) { + $start_time = time; + } + my $time_left = $max_time - (time - $start_time); + $time_left = 0 if ($time_left < 0); + return $time_left; +} + +sub update_seconds { + my ($r) = @_; + my $time = &time_left(); + if (($last_time-$time) > 0) { $r->print(<<SCRIPT); <script type="text/javascript"> - function displayinfo(val) { - popwin.document.forms.popremain.sdetails.value=val; - } - function openhelp(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=400,height=300'); - openhelpwin.focus(); - } - function abortsearch(val) { - openhelpwin=open('/adm/help/searchcat.html','helpscreen', - 'scrollbars=1,width=400,height=300'); - openhelpwin.focus(); - } +// <![CDATA[ +document.statusform.seconds.value = '$time' +// ]]> </script> SCRIPT - $r->rflush(); - # begin showing the cataloged results - $r->print(<<CATALOGBEGIN); -</head> -<body bgcolor="#ffffff"> -<img align=right src=/adm/lonIcons/lonlogos.gif> -<h1>Search Catalog</h1> -CATALOGBEGIN - $r->print(<<CATALOGCONTROLS); -<form name='results' method="post" action="/adm/searchcat"> -<input type='hidden' name='acts' value='' /> -<input type='button' value='Revise search request' -onClick='this.form.submit();' /> -$importbutton -$closebutton -$persistent -<hr /> -<h3>Search Query</h3> -CATALOGCONTROLS - if ($mode eq 'Basic') { - $r->print(<<RESULTS); -<p> -<b>Basic search:</b> $ENV{'form.basicexp'} -</p> -RESULTS + $r->rflush(); } - elsif ($mode eq 'Advanced') { - $r->print(<<RESULTS); -<p> -<b>Advanced search</b> -$query -</p> -RESULTS + $last_time = $time; +} + +} + +###################################################################### +###################################################################### + +=pod + +=item &revise_button() + +Inputs: None + +Returns: html string for a 'revise search' button. + +=cut + +###################################################################### +###################################################################### +sub revise_button { + my $revisetext = &mt('Revise search'); + my $revise_phase = 'disp_basic'; + $revise_phase = 'disp_adv' if ($env{'form.searchmode'} eq 'advanced'); + my $newloc = '/adm/searchcat'. + '?persistent_db_id='.$env{'form.persistent_db_id'}. + '&cleargroupsort=1'. + '&phase='.$revise_phase; + my $result = qq{<input type="button" value="$revisetext" name="revise"} . + qq{ onclick="parent.location='$newloc';" /> }; + return $result; +} + +###################################################################### +###################################################################### + +=pod + +=item &run_search() + +Executes a search query by sending it the the other servers and putting the +results into MySQL. + +=cut + +###################################################################### +###################################################################### +sub run_search { + my ($r,$query,$customquery,$customshow,$serverlist, + $pretty_string,$area,$domainsref) = @_; + my $tabletype = 'metadata'; + if ($area eq 'portfolio') { + $tabletype = 'portfolio_search'; + } + my $connection = $r->connection; + # + # Print run_search header + # + my $start_page = &Apache::loncommon::start_page('Search Status',undef); + my $breadcrumbs = + &Apache::lonhtmlcommon::breadcrumbs('Searching','Searching', + $env{'form.catalogmode'} ne 'import'); + $r->print(<<END); +$start_page +$breadcrumbs +END + # Remove leading and trailing <br /> + $pretty_string =~ s:^\s*<br />::i; + $pretty_string =~ s:(<br />)*\s*$::im; + my @Lines = split("<br />",$pretty_string); + # I keep getting blank items at the end of the list, hence the following: + while ($Lines[-1] =~ /^\s*$/ && @Lines) { + pop(@Lines); + } + if (@Lines > 2) { + $pretty_string = join '<br />',(@Lines[0..2],'...<br />'); } - $r->print('<h3>Search Results</h3>'); + $r->print(&mt("Search: [_1]",$pretty_string)); $r->rflush(); - my $servernum=(keys %rhash)+0; - - # define server grid (shows status of multiple machines) - my $hcinit; - my $grid="'<br />'+"; - $grid.="\n"; - my $sn=1; - for my $sk (sort keys %rhash) { - # '<a href=" - $grid.="'<a href=\""; - # javascript:displayinfo('+ - $grid.="javascript:opener.displayinfo('+"; - # "'"+'key - $grid.="\"'\"+'"; - $grid.=$sk; - my $hc; - if ($rhash{$sk} eq 'con_lost') { - $hc="!!!BAD CONNECTION, CONTACT SYSTEM ADMINISTRATOR!!!"; - } - else { - $hc="'+\"'\"+\"+hc['$sk']+\"+\"'\"+'"; - $hcinit.="hc[\"$sk\"]=\"not yet connected...\";"; - } - $grid.=" hitcount=".$hc; - $grid.=" domain=".$hostdomains{$sk}; - $grid.=" IP=".$hostips{$sk}; - # '+"'"+'">'+ - $grid.="'+\"'\"+')\">'+"; - $grid.="\n"; - $grid.="'<img border=\"0\" name=\"img".$sn."\"". - " src=\"/adm/lonIcons/srvnull.gif\" alt=\"".$sk."\" /></a>'+\n"; - $grid.="'<br />'+\n" unless $sn%10; - $sn++; + # + # Determine the servers we need to contact. + my (@Servers_to_contact,%domains_by_server); + if (defined($serverlist)) { + if (ref($serverlist) eq 'ARRAY') { + @Servers_to_contact = @$serverlist; + } else { + @Servers_to_contact = ($serverlist); + } + if (ref($domainsref) eq 'HASH') { + foreach my $server (@Servers_to_contact) { + $domains_by_server{$server} = $domainsref->{$server}; + } + } + } else { + my %library_servers = &Apache::lonnet::unique_library(); + my (%all_library_servers, %older_library_servers); + foreach my $key (keys(%library_servers)) { + if (&Apache::lonnet::get_server_loncaparev('',$key) =~ /^\'?(\d+)\.(\d+)/) { + my $major = $1; + my $minor = $2; + if (($major < 2) || (($major == 2) && ($minor < 11))) { + map { $older_library_servers{$_} = 1; } + &Apache::lonnet::machine_ids($library_servers{$key}); + } + } + } + %all_library_servers = (%library_servers,%older_library_servers); + @Servers_to_contact = sort(keys(%all_library_servers)); + foreach my $server (@Servers_to_contact) { + my %possdoms; + map { $possdoms{$_}=1; } &Apache::lonnet::machine_domains($all_library_servers{$server}); + $domains_by_server{$server} = + join(',',sort(&Apache::lonnet::machine_domains($all_library_servers{$server}))); + } } - $r->print(<<ENDPOP); -<script type="text/javascript"> - popwin=open('','popwin','scrollbars=1,width=400,height=200'); - popwin.focus(); - popwin.document.writeln('<'+'html>'); - popwin.document.writeln('<'+'head>'); - popwin.document.writeln('<'+'script>'); - popwin.document.writeln('hc=new Array();$hcinit'); - popwin.document.writeln('<'+'/script>'); - popwin.document.writeln('<'+'/head>'+ - '<'+'body bgcolor="#FFFFFF">'+ - '<'+'image name="whirly" align="right" src="/adm/lonIcons/'+ - 'lonanim.gif" '+ - 'alt="animated logo" />'+ - '<'+'h3>Search Results Progress<'+'/h3>'+ - '<'+'form name="popremain">'+ - '<'+'tt>'+ - '<'+'br clear="all"/><i>PLEASE BE PATIENT</i>'+ - '<'+'br />SCANNING $servernum SERVERS'+ - '<'+'br clear="all" />Number of record hits found '+ - '<'+'input type="text" size="10" name="numhits"'+ - ' value="0" />'+ - '<'+'br clear="all" />Time elapsed '+ - '<'+'input type="text" size="10" name="elapsetime"'+ - ' value="0" />'+ - '<'+'br />'+ - 'SERVER GRID (click on any cell for details)'+ - $grid - '<'+'br />'+ - 'Server details '+ - '<'+'input type="text" size="25" name="sdetails"'+ - ' value="" />'+ - '<'+'br />'+ - ' <'+'input type="button" name="button"'+ - ' value="abort search and view current results" '+ - ' onClick="javascript:opener.abortsearch()" />'+ - ' <'+'input type="button" name="button"'+ - ' value="help" onClick="javascript:opener.openhelp()" />'+ - '<'+'/tt>'+ - '<'+'/form>'+ - '<'+'/body><'+'/html>'); - popwin.document.close(); -</script> -ENDPOP + 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 save search results in.". + "The search has been aborted.". + &Apache::loncommon::end_page()); + return; + } + my $table_status = &Apache::lonmysql::check_table($table); + if (! defined($table_status)) { + $r->print("Unable to determine status of table.". + &Apache::loncommon::end_page()); + &Apache::lonnet::logthis("Bogus table id of $table for ". + "$env{'user.name'} @ $env{'user.domain'}"); + &Apache::lonnet::logthis("lonmysql error = ". + &Apache::lonmysql::get_error()); + return; + } + if (! $table_status) { + &Apache::lonnet::logthis("lonmysql error = ". + &Apache::lonmysql::get_error()); + &Apache::lonnet::logthis("lonmysql debug = ". + &Apache::lonmysql::get_debug()); + &Apache::lonnet::logthis('table status = "'.$table_status.'"'); + $r->print("The table id,$table, we tried to use is invalid.". + "The search has been aborted.". + &Apache::loncommon::end_page()); + return; + } + ## + ## Prepare for the big loop. + my $hitcountsum; + my %matches; + my $server; + my $status; + my $revise = &revise_button(); + $r->print('<form name="statusform" action="" method="post">'."\n". + '<input type="hidden" name="acts" value="" />'."\n". + '<table border="0"><tr><td>'."\n". + &Apache::loncommon::start_data_table()); + $r->print(&Apache::loncommon::start_data_table_header_row() + .'<th>'.&mt('Status').'</th>' + .'<th>'.&mt('Total Matches').'</th>' + .'<th>'.&mt('Time Remaining').'</th>' + .&Apache::loncommon::end_data_table_header_row() + .&Apache::loncommon::start_data_table_row() + .'<td><input type="text" name="status" value="" size="50" readonly="readonly" /></td>' + .'<td><input type="text" name="count" value="" size="10" readonly="readonly" /></td>' + .'<td><input type="text" name="seconds" value="" size="8" readonly="readonly" /></td>' + .&Apache::loncommon::end_data_table_row() + .&Apache::loncommon::end_data_table() + .'</td><td> </td><td>'.$revise.'</td></tr></table></form>'); $r->rflush(); + &reset_timing(); + &update_seconds($r); + &update_status($r,&mt('contacting [_1]',$Servers_to_contact[0])); + while (&time_left() && + ((@Servers_to_contact) || keys(%Server_status))) { + &update_seconds($r); + # + # Send out a search request + if (@Servers_to_contact) { + # Contact one server + my $server = shift(@Servers_to_contact); + &update_status($r,&mt('contacting [_1]',$server)); + my $reply=&Apache::lonnet::metadata_query($query,$customquery, + $customshow,[$server],\%domains_by_server); + ($server) = keys(%$reply); + $Server_status{$server} = $reply->{$server}; + } else { + # wait a sec. to give time for files to be written + # This sleep statement is here instead of outside the else + # block because we do not want to pause if we have servers + # left to contact. + if (scalar (keys(%Server_status))) { + &update_status($r, + &mt('waiting on [_1]',join(' ',keys(%Server_status)))); + } + sleep(1); + } + # + # Loop through the servers we have contacted but do not + # have results from yet, looking for results. + foreach my $server (keys(%Server_status)) { + last if ($connection->aborted()); + &update_seconds($r); + my $status = $Server_status{$server}; + if ($status eq 'con_lost') { + delete ($Server_status{$server}); + next; + } + $status=~s|/||g; - my $servercount=0; - my $hitcountsum=0; - my $bloop=$servernum; - my %orkey; - BLOOP: while(1) { - my $sn=0; - last BLOOP unless $bloop; - last BLOOP unless $timeremain; - RLOOP: foreach my $rkey (sort keys %rhash) { - $sn++; - next RLOOP if $orkey{$rkey}; - $servercount++; - $tflag=1; - $compiledresult=''; - my $hostname=$rkey; - my $reply=$rhash{$rkey}; - my @results; - - my $replyfile=''; - - if ($reply eq 'con_lost') { - $r->print('<script type="text/javascript">popwin.document.img'. - $sn.'.'. - 'src="/adm/lonIcons/srvbad.gif";</script>'. - "\n"); - $r->rflush(); - $bloop--; - $orkey{$rkey}=1; - } - else { - $reply=~/^([\.\w]+)$/; # must do since 'use strict' checks for tainting - $replyfile=$r->dir_config('lonDaemons').'/tmp/'.$1; - $reply=~/(.*?)\_/; - { - my $temp=0; - WLOOP: while (1) { - if (-e $replyfile && $tflag) { - $r->print('<script type="text/javascript">'. - 'popwin.document.img'.$sn.'.'. - 'src="/adm/lonIcons/srvhalf.gif";</script>'. - "\n"); - $r->rflush(); - $r->print('<script type="text/javascript">'. - 'popwin.hc["'.$rkey.'"]='. - '"still transferring..."'.';</script>'. - "\n"); - $r->rflush(); - $tflag=0; - } - if (-e "$replyfile.end") { - $bloop--; - $orkey{$rkey}=1; - if (-s $replyfile) { - $r->print('<script type="text/javascript">'. - 'popwin.document.img'.$sn.'.'. - 'src="/adm/lonIcons/srvgood.gif";'. - '</script>'."\n"); - $r->rflush(); - my $fh=Apache::File->new($replyfile) or - ($r->print('ERROR: file '. - $replyfile.' cannot be opened') and - return OK); - @results=<$fh> if $fh; - $hitcount{$rkey}=@results+0; - $r->print('<script type="text/javascript">'. - 'popwin.hc["'.$rkey.'"]='. - $hitcount{$rkey}.';</script>'. - "\n"); - $r->rflush(); - $hitcountsum+=$hitcount{$rkey}; - $r->print('<script type="text/javascript">'. - 'popwin.document.forms.popremain.'. - 'numhits.value='.$hitcountsum. - ';</script>'. - "\n"); - $r->rflush(); - } - else { - $r->print('<script type="text/javascript">'. - 'popwin.document.img'.$sn.'.'. - 'src="/adm/lonIcons/srvempty.gif";'. - '</script>'. - "\n"); - $r->rflush(); - $r->print('<script type="text/javascript">'. - 'popwin.hc["'.$rkey.'"]=0'. - ';</script>'. - "\n"); - $r->rflush(); - } - last WLOOP; - } - if ($temp>1) { - sleep 1; - $timeremain--; - $elapsetime++; - last WLOOP; - } - last WLOOP unless $timeremain; - sleep 1; - $timeremain--; - $elapsetime++; - $r->print('<script type="text/javascript">'. - 'popwin.document.popremain.elapsetime.'. - 'value="'.$elapsetime.'";</script>'."\n"); - $r->rflush(); - $temp++; - } - } - $r->print('<script type="text/javascript">'. - 'popwin.document.whirly.'. - 'src="'.'/adm/lonIcons/lonanimend.gif'. - '";</script>'."\n"); - $r->rflush(); - } - my $customshow=''; - my $extrashow=''; - my @customfields; - if ($ENV{'form.customshow'}) { - $customshow=$ENV{'form.customshow'}; - $customshow=~s/[^\w\s]//g; - my @fields=map {"<font color=\"#008000\">$_:</font><!-- $_ -->"} - split(/\s+/,$customshow); - @customfields=split(/\s+/,$customshow); - if ($customshow) { - $extrashow="<ul><li>".join("</li><li>",@fields)."</li></ul>\n"; - } - } - my $customdata=''; - my %customhash; - foreach my $result (@results) { - if ($result=~/^(custom\=.*)$/) { # grab all custom metadata - my $tmp=$result; - $tmp=~s/^custom\=//; - my ($k,$v)=map {&Apache::lonnet::unescape($_); - } split(/\,/,$tmp); - $customhash{$k}=$v; - } - } - if (keys %hash) { - untie %hash; - } - if (tie(%hash,'GDBM_File',$diropendb,&GDBM_WRCREAT,0640)) { - if ($ENV{'form.launch'} eq '1') { - &start_fresh_session(); - } - foreach my $result (@results) { - next if $result=~/^custom\=/; - chomp $result; - next unless $result; - my @fields=map - {&Apache::lonnet::unescape($_)} - (split(/\,/,$result)); - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$abstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright)=@fields; - - unless ($ENV{'user.adv'}) { - $keywords='<i>- not displayed -</i>'; - $fields[4]=$keywords; - $notes='<i>- not displayed -</i>'; - $fields[6]=$notes; - $abstract='<i>- not displayed -</i>'; - $fields[7]=$abstract; - $subject='<i>- not displayed -</i>'; - $fields[2]=$subject; - } - - my $shortabstract=$abstract; - $shortabstract=substr($abstract,0,200).'...' if length($abstract)>200; - $fields[7]=$shortabstract; - my $shortkeywords=$keywords; - $shortkeywords=substr($keywords,0,200).'...' if length($keywords)>200; - $fields[4]=$shortkeywords; - - my $extrashow2=$extrashow; - if ($extrashow) { - foreach my $field (@customfields) { - my $value=''; - if ($customhash{$url}=~/\<${field}[^\>]*\>(.*?)\<\/${field}[^\>]*\>/s) { - $value=$1; - } - $extrashow2=~s/\<\!\-\- $field \-\-\>/ $value/g; - } + + + my $datafile=LONCAPA::tempdir().$status; + if (-e $datafile && ! -e "$datafile.end") { + &update_status($r,&mt('Receiving results from [_1]',$server)); + next; + } + last if ($connection->aborted()); + if (-e "$datafile.end") { + &update_status($r,&mt('Reading results from [_1]',$server)); + if (-z "$datafile") { + delete($Server_status{$server}); + next; } - - $compiledresult.=<<END if $compiledresult or $servercount!=$servernum; -<hr align='left' width='200' noshade /> -END - $compiledresult.=<<END; -<p> -END - $compiledresult.=<<END if $ENV{'form.catalogmode'} eq 'interactive'; -<font size='-1'><INPUT TYPE="button" NAME="returnvalues" VALUE="SELECT" -onClick="javascript:select_data('$title','$url')"> -</font> -<br /> -END - if ($ENV{'form.catalogmode'} eq 'groupsearch') { - $fnum+=0; - $hash{"pre_${fnum}_link"}=$url; - $hash{"pre_${fnum}_title"}=$title; - $compiledresult.=<<END; -<font size='-1'><input type="checkbox" name="returnvalues" value="SELECT" -onClick="javascript:queue($fnum)" /> -</font> -<br /> -END -# <input type="hidden" name="title$fnum" value="$title" /> -# <input type="hidden" name="url$fnum" value="$url" /> - $fnum++; - } - my $httphost=$ENV{'HTTP_HOST'}; - - my $viewselect; - if ($mode eq 'Basic') { - $viewselect=$ENV{'form.basicviewselect'}; - } - elsif ($mode eq 'Advanced') { - $viewselect=$ENV{'form.advancedviewselect'}; - } - - if ($viewselect eq 'Detailed Citation View') { - $compiledresult.=&detailed_citation_view(@fields, - $hostname,$httphost, - $extrashow2); - } - elsif ($viewselect eq 'Summary View') { - $compiledresult.=&summary_view(@fields,$hostname,$httphost, - $extrashow2); - } - elsif ($viewselect eq 'Fielded Format') { - $compiledresult.=&fielded_format_view(@fields,$hostname, - $httphost,$extrashow2); - } - elsif ($viewselect eq 'XML/SGML') { - $compiledresult.=&xml_sgml_view(@fields,$hostname,$httphost, - $extrashow2); - } - + my $fh; + if (!($fh=Apache::File->new($datafile))) { + $r->print("Unable to open search results file for ". + "server $server. Omitting from search"); + delete($Server_status{$server}); + next; + } + # Read in the whole file. + while (my $result = <$fh>) { + last if ($connection->aborted()); + # + # Records are stored one per line + chomp($result); + next if (! $result); + # + # Parse the result. + my %Fields = &parse_raw_result($result,$server,$tabletype); + $Fields{'hostname'} = $server; + # + # Skip based on copyright + next if (! ©right_check(\%Fields)); + + if ($area eq 'portfolio') { + next if (defined($matches{$Fields{'url'}})); + # Skip if inaccessible + next if (!&Apache::lonnet::portfolio_access($Fields{'url'})); + $matches{$Fields{'url'}} = 1; + } + # + # Store the result in the mysql database + my $result = &Apache::lonmysql::store_row($table,\%Fields); + if (! defined($result)) { + $r->print(&Apache::lonmysql::get_error()); + } + # + $hitcountsum ++; + &update_seconds($r); + if ($hitcountsum % 50 == 0) { + &update_count_status($r,$hitcountsum); + } + } + $fh->close(); + # $server is only deleted if the results file has been + # found and (successfully) opened. This may be a bad idea. + delete($Server_status{$server}); } - - untie %hash; + last if ($connection->aborted()); + &update_count_status($r,$hitcountsum); } - else { - $r->print('<html><head></head><body>Unable to tie hash to db '. - 'file</body></html>'); - } - if ($compiledresult) { - $resultflag=1; - } + last if ($connection->aborted()); + &update_seconds($r); + } + &update_status($r,&mt('Search Complete on Server [_1]',$server)); + &update_seconds($r); + # + &Apache::lonmysql::disconnect_from_db(); # This is unneccessary + # + # We have run out of time or run out of servers to talk to and + # results to get, so let the client know the top frame needs to be + # loaded from /adm/searchcat + $r->print(&Apache::loncommon::end_page()); +# if ($env{'form.catalogmode'} ne 'import') { + $r->print(<<SCRIPT); +<script> +window.location='/adm/searchcat?phase=sort&persistent_db_id=$env{'form.persistent_db_id'}'; +</script> +SCRIPT +# } + return; +} - $r->print(<<RESULTS); -$compiledresult -RESULTS - my $percent=sprintf('%3.0f',($servercount/$servernum*100)); +###################################################################### +###################################################################### + +=pod + +=item &prev_next_buttons() + +Returns html for the previous and next buttons on the search results page. + +=cut + +###################################################################### +###################################################################### +sub prev_next_buttons { + my ($current_min,$show,$total,$parms) = @_; + return '' if ($show eq 'all'); # No links if you get them all at once. + # + # Create buttons + return '<p class="LC_nobreak">' + .'<input type="submit" name="prev" value="<"' + .' title="'.&mt('Previous').'" />' + .' ' + .'<input type="submit" name="reload" value="'.&mt('Reload').'" />' + .' ' + .'<input type="submit" name="next" value=">"' + .' title="'.&mt('Next').'" />' + .'</p>'; +} + +###################################################################### +###################################################################### + +=pod + +=item &display_results() + +Prints the results out for selection and perusal. + +=cut + +###################################################################### +###################################################################### +sub display_results { + my ($r,$importbutton,$closebutton,$diropendb,$area) = @_; + my $connection = $r->connection; + $r->print(&search_results_header($importbutton,$closebutton)); + ## + ## Set viewing function + ## + my $viewfunction = $Views{$env{'form.viewselect'}}; + if (!defined($viewfunction)) { + $r->print('<p class="LC_error">' + .&mt('Internal Error - Bad view selected.') + .'</p>'."\n"); + $r->rflush(); + return; + } + ## + ## $checkbox_num is a count of the number of checkboxes output on the + ## page this is used only during catalogmode=import. + my $checkbox_num = 0; + ## + ## Get the catalog controls setup + ## + my $action = "/adm/searchcat?phase=results"; + ## + ## Deal with import by opening the import db file. + if ($env{'form.catalogmode'} eq 'import') { + if (! tie(%groupsearch_db,'GDBM_File',$diropendb, + &GDBM_WRCREAT(),0640)) { + $r->print('<p class="LC_error">'. + &mt('Unable to save import results.'). + '</p>'. + '</form>'. + &Apache::loncommon::end_page()); + $r->rflush(); + return; + } + } + ## + ## Prepare the table for querying + my $table = $env{'form.table'}; + return if (! &ensure_db_and_table($r,$table)); + ## + ## Get the number of results + my $total_results = &Apache::lonmysql::number_of_rows($table); + if (! defined($total_results)) { + $r->print('<p class="LC_error">'. + &mt('A MySQL error has occurred.'). + '</p>'. + '</form>'. + &Apache::loncommon::end_page()); + &Apache::lonnet::logthis("lonmysql was unable to determine the number". + " of rows in table ".$table); + &Apache::lonnet::logthis(&Apache::lonmysql::get_error()); + return; + } + ## + ## Determine how many results we need to get + $env{'form.start'} = 1 if (! exists($env{'form.start'})); + $env{'form.show'} = 20 if (! exists($env{'form.show'})); + if (exists($env{'form.prev'})) { + $env{'form.start'} -= $env{'form.show'}; + } elsif (exists($env{'form.next'})) { + $env{'form.start'} += $env{'form.show'}; + } + $env{'form.start'} = 1 if ($env{'form.start'}<1); + $env{'form.start'} = $total_results if ($env{'form.start'}>$total_results); + my $min = $env{'form.start'}; + my $max; + if ($env{'form.show'} eq 'all') { + $max = $total_results ; + } else { + $max = $min + $env{'form.show'} - 1; + $max = $total_results if ($max > $total_results); + } + ## + ## Output form elements + $r->print(&hidden_field('table'). + &hidden_field('phase'). + &hidden_field('persistent_db_id'). + &hidden_field('start'). + &hidden_field('area') + ); + # + # Build sorting selector + my @fields = + ( + {key=>'default' }, + {key=>'title' }, + {key =>'author' }, + {key =>'subject'}, + {key =>'url',desc=>'URL'}, + {key =>'keywords'}, + {key =>'language'}, + {key =>'creationdate'}, + {key =>'lastrevisiondate'}, + {key =>'owner'}, + {key =>'copyright'}, + {key =>'authorspace'}, + {key =>'lowestgradelevel'}, + {key =>'highestgradelevel'}, + {key =>'standards',desc=>'Standards'}, + ); + if ($area eq 'portfolio') { + push(@fields, + ( + {key => 'scope'}, + {key => 'keynum'}, + )); + } else { + push(@fields, + ( + {key =>'count',desc=>'Number of accesses'}, + {key =>'stdno',desc=>'Students Attempting'}, + {key =>'avetries',desc=>'Average Number of Tries'}, + {key =>'difficulty',desc=>'Mean Degree of Difficulty'}, + {key =>'disc',desc=>'Mean Degree of Discrimination'}, + {key =>'clear',desc=>'Evaluation: Clear'}, + {key =>'technical',desc=>'Evaluation: Technically Correct'}, + {key =>'correct',desc=>'Evaluation: Material is Correct'}, + {key =>'helpful',desc=>'Evaluation: Material is Helpful'}, + {key =>'depth',desc=>'Evaluation: Material has Depth'}, + )); + } + my %fieldnames = &Apache::lonmeta::fieldnames(); + my @field_order; + foreach my $field_data (@fields) { + push(@field_order,$field_data->{'key'}); + if (! exists($field_data->{'desc'})) { + $field_data->{'desc'}=$fieldnames{$field_data->{'key'}}; + } else { + if (! defined($field_data->{'desc'})) { + $field_data->{'desc'} = ucfirst($field_data->{'key'}); + } + $field_data->{'desc'} = &mt($field_data->{'desc'}); + } } - } - unless ($resultflag) { - $r->print("\nThere were no results that matched your query\n"); + my %sort_fields = map {$_->{'key'},$_->{'desc'}} @fields; + $sort_fields{'select_form_order'} = \@field_order; + $env{'form.sortorder'} = 'desc' if (! exists($env{'form.sortorder'})); + if (! exists($env{'form.sortfield'})) { + if ($area eq 'portfolio') { + $env{'form.sortfield'} = 'owner'; + } else { + $env{'form.sortfield'} = 'count'; + } } -# $r->print('<script type="text/javascript">'.'popwin.close()</script>'."\n"); $r->rflush(); - $r->print(<<RESULTS); -</body> -</html> -RESULTS + if (! exists($env{'form.sortorder'})) { + if ($env{'form.sortfield'}=~/^(count|stdno|disc|clear|technical|correct|helpful)$/) { + $env{'form.sortorder'}='desc'; + } else { + $env{'form.sortorder'}='asc'; + } + } + my $sortform = '<span class="LC_nobreak">' + .&mt('Sort by:').' ' + .&Apache::loncommon::select_form($env{'form.sortfield'}, + 'sortfield', + \%sort_fields) + .' ' + .&Apache::loncommon::select_form($env{'form.sortorder'}, + 'sortorder', + {asc =>&mt('Ascending'), + desc=>&mt('Descending') + }) + .'</span>'; + ## + ## Display links for 'prev' and 'next' pages (if necessary) and Display Options + $r->print('<fieldset>'."\n" + .'<legend>'.&mt('Display Options').'</legend>'."\n" + .$sortform + .' ' + .&viewoptions() + .'</fieldset>' + .&prev_next_buttons($min,$env{'form.show'},$total_results) + ); + + if ($total_results == 0) { + $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="2" />'. + '<p class="LC_info">'.&mt('There are currently no results.').'</p>'. + "</form>". + &Apache::loncommon::end_page()); + return; + } else { + $r->print('<div>'. + mt('Results [_1] to [_2] out of [_3]', + $min,$max,$total_results). + "</div>\n"); + } + ## + ## Get results from MySQL table + my $sort_command = 'id>='.$min.' AND id<='.$max; + my $order; + if (exists($env{'form.sortorder'})) { + if ($env{'form.sortorder'} eq 'asc') { + $order = 'ASC'; + } elsif ($env{'form.sortorder'} eq 'desc') { + $order = 'DESC'; + } else { + $order = ''; + } + } else { + $order = ''; + } + if ($env{'form.sortfield'} ne 'default' && + exists($sort_fields{$env{'form.sortfield'}})) { + $sort_command = $env{'form.sortfield'}.' IS NOT NULL '. + 'ORDER BY '.$env{'form.sortfield'}.' '.$order. + ' LIMIT '.($min-1).','.($max-$min+1); + } + my @Results = &Apache::lonmysql::get_rows($table,$sort_command); + ## + ## Loop through the results and output them. + my $tabletype = 'metadata'; + if ($area eq 'portfolio') { + $tabletype = 'portfolio_search'; + } + $r->print(&Apache::loncommon::start_data_table()); + foreach my $row (@Results) { + if ($connection->aborted()) { + &cleanup(); + return; + } + my %Fields = %{&parse_row($tabletype,@$row)}; + my $output; + if (! defined($Fields{'title'}) || $Fields{'title'} eq '') { + $Fields{'title'} = 'Untitled'; + } + my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'}, + $Fields{'id'},$checkbox_num++); + # Render the result into html + $output.= &$viewfunction($prefix,%Fields); + # Print them out as they come in. + $r->print(&Apache::loncommon::start_data_table_row() + .'<td>' + .$output + .'</td>' + .&Apache::loncommon::end_data_table_row() + ); + $r->rflush(); + } + $r->print(&Apache::loncommon::end_data_table()); + if (@Results < 1) { + $r->print('<p class="LC_info">' + .&mt('There were no results matching your query.') + .'</p>'); + } else { + $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'}) + ); + } + $r->print("</form>".&Apache::loncommon::end_page()); + $r->rflush(); + untie %groupsearch_db if (tied(%groupsearch_db)); + return; } -# ------------------------------------------------------ Detailed Citation View -sub detailed_citation_view { - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$shortabstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright, - $hostname,$httphost,$extrashow)=@_; - my $result=<<END; -<i>$owner</i>, last revised $lastrevisiondate -<h3><A HREF="http://$httphost$url" TARGET='search_preview'>$title</A></h3> -<h3>$author</h3> -</p> -<p> -<b>Subject:</b> $subject<br /> -<b>Keyword(s):</b> $keywords<br /> -<b>Notes:</b> $notes<br /> -<b>MIME Type:</b> -END - $result.=&Apache::loncommon::filedescription($mime); - $result.=<<END; -<br /> -<b>Language:</b> -END - $result.=&Apache::loncommon::languagedescription($lang); - $result.=<<END; -<br /> -<b>Copyright/Distribution:</b> +###################################################################### +###################################################################### + +=pod + +=item &catalogmode_output($title,$url,$fnum,$checkbox_num) + +Returns html needed for the various catalog modes. Gets inputs from +$env{'form.catalogmode'}. Stores data in %groupsearch_db. + +=cut + +###################################################################### +###################################################################### +sub catalogmode_output { + my $output = ''; + my ($title,$url,$fnum,$checkbox_num) = @_; + if ($env{'form.catalogmode'} eq 'interactive') { + $title=~ s/\'/\\\'/g; + if ($env{'form.catalogmode'} eq 'interactive') { + $output.=<<END +<font size='-1'><input type="button" name="returnvalues" value="select" +onclick="javascript:select_data('$title','$url')" /> +</font> END - $result.=&Apache::loncommon::copyrightdescription($copyright); - $result.=<<END; -<br /> -</p> -$extrashow -<p> -$shortabstract -</p> + } + } elsif ($env{'form.catalogmode'} eq 'import') { + $groupsearch_db{"pre_${fnum}_link"}=$url; + $groupsearch_db{"pre_${fnum}_title"}=$title; + $output.=<<END; +<font size='-1'> +<input type="checkbox" name="returnvalues" value="select" +onclick="javascript:queue($checkbox_num,$fnum)" /> +</font> END - return $result; + } + return $output; } +###################################################################### +###################################################################### -# ---------------------------------------------------------------- Summary View -sub summary_view { - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$shortabstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright, - $hostname,$httphost,$extrashow)=@_; - my $cprtag=&Apache::loncommon::copyrightdescription($copyright); - my $result=<<END; -<a href="http://$httphost$url" TARGET='search_preview'>$author</a><br /> -$title<br /> -$owner -- $lastrevisiondate<br /> -$cprtag<br /> -$extrashow -</p> -END - return $result; -} +=pod -# -------------------------------------------------------------- Fielded Format -sub fielded_format_view { - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$shortabstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright, - $hostname,$httphost,$extrashow)=@_; - my $mimetag=&Apache::loncommon::filedescription($mime); - my $language=&Apache::loncommon::languagedescription($lang); - my $cprtag=&Apache::loncommon::copyrightdescription($copyright); - my $result=<<END; -<b>URL: </b> <A HREF="http://$httphost$url" TARGET='search_preview'>$url</A> -<br /> -<b>Title:</b> $title<br /> -<b>Author(s):</b> $author<br /> -<b>Subject:</b> $subject<br /> -<b>Keyword(s):</b> $keywords<br /> -<b>Notes:</b> $notes<br /> -<b>MIME Type:</b> $mimetag<br /> -<b>Language:</b> $language<br /> -<b>Creation Date:</b> $creationdate<br /> -<b>Last Revision Date:</b> $lastrevisiondate<br /> -<b>Publisher/Owner:</b> $owner<br /> -<b>Copyright/Distribution:</b> $cprtag<br /> -<b>Repository Location:</b> $hostname<br /> -<b>Abstract:</b> $shortabstract<br /> -$extrashow -</p> -END - return $result; -} +=item &parse_row() -# -------------------------------------------------------------------- XML/SGML -sub xml_sgml_view { - my ($title,$author,$subject,$url,$keywords,$version, - $notes,$shortabstract,$mime,$lang, - $creationdate,$lastrevisiondate,$owner,$copyright, - $hostname,$httphost,$extrashow)=@_; - my $cprtag=&Apache::loncommon::copyrightdescription($copyright); - my $mimetag=&Apache::loncommon::filedescription($mime); - my $language=&Apache::loncommon::languagedescription($lang); - my $result=<<END; -<pre> -<LonCapaResource> -<url>$url</url> -<title>$title</title> -<author>$author</author> -<subject>$subject</subject> -<keywords>$keywords</keywords> -<notes>$notes</notes> -<mimeInfo> -<mime>$mime</mime> -<mimetag>$mimetag</mimetag> -</mimeInfo> -<languageInfo> -<language>$lang</language> -<languagetag>$language</languagetag> -</languageInfo> -<creationdate>$creationdate</creationdate> -<lastrevisiondate>$lastrevisiondate</lastrevisiondate> -<owner>$owner</owner> -<copyrightInfo> -<copyright>$copyright</copyright> -<copyrighttag>$cprtag</copyrighttag> -</copyrightInfo> -<repositoryLocation>$hostname</repositoryLocation> -<shortabstract>$shortabstract</shortabstract> -</LonCapaResource> -</pre> -$extrashow -END - return $result; -} +Parse a row returned from the database. -# ---------------------------------------------------- see if a field is filled -sub filled { - my ($field)=@_; - if ($field=~/\S/ && $field ne 'any') { - return 1; +=cut + +###################################################################### +###################################################################### +sub parse_row { + my ($tabletype,@Row) = @_; + my %Fields; + if (! scalar(@Datatypes)) { + &set_up_table_structure($tabletype); } - else { - return 0; + for (my $i=0;$i<=$#Row;$i++) { + $Fields{$Datatypes[$i]->{'name'}}=&unescape($Row[$i]); } + $Fields{'language'} = + &Apache::loncommon::languagedescription($Fields{'language'}); + $Fields{'copyrighttag'} = + &Apache::loncommon::copyrightdescription($Fields{'copyright'}); + $Fields{'mimetag'} = + &Apache::loncommon::filedescription($Fields{'mime'}); + return \%Fields; } -# ---------------- Message to output when there are not enough fields filled in -sub output_blank_field_error { - my ($r)=@_; - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); +########################################################### +########################################################### - $r->print(<<BEGINNING); -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -BEGINNING - $r->print(<<RESULTS); -</head> -<body bgcolor="#ffffff"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> -<form method="post" action="/adm/searchcat"> -$persistent -<input type='button' value='Revise search request' -onClick='this.form.submit();' /> -$closebutton -<hr /> -<h3>Helpful Message</h3> -<p> -Incorrect search query due to blank entry fields. -You need to fill in the relevant -fields on the search page in order for a query to be -processed. -</p> -</body> -</html> -RESULTS -} +=pod -# ----------------------------------------------------------- Output date error -sub output_date_error { - my ($r,$message)=@_; - # make query information persistent to allow for subsequent revision - my $persistent=&make_persistent(); +=item &parse_raw_result() - $r->print(<<BEGINNING); -<html> -<head> -<title>The LearningOnline Network with CAPA</title> -BEGINNING - $r->print(<<RESULTS); -</head> -<body bgcolor="#ffffff"> -<img align='right' src='/adm/lonIcons/lonlogos.gif' /> -<h1>Search Catalog</h1> -<form method="post" action="/adm/searchcat"> -$persistent -<input type='button' value='Revise search request' -onClick='this.form.submit();' /> -$closebutton -<hr /> -<h3>Helpful Message</h3> -<p> -$message -</p> -</body> -</html> -RESULTS +Takes a line from the file of results and parse it. Returns a hash +with keys according to column labels + +In addition, the following tags are set by calling the appropriate +lonnet function: 'language', 'copyrighttag', 'mimetag'. + +The 'title' field is set to "Untitled" if the title field is blank. + +'abstract' and 'keywords' are truncated to 200 characters. + +=cut + +########################################################### +########################################################### +sub parse_raw_result { + my ($result,$hostname,$tabletype) = @_; + # conclude from self to others regarding fields + my %Fields=&LONCAPA::lonmetadata::metadata_col_to_hash + ($tabletype, + map { + &unescape($_); + } (split(/\,/,$result)) ); + return %Fields; } -# --------- settings whenever the user causes the search window to be launched -sub start_fresh_session { - delete $hash{'mode_catalog'}; - foreach (keys %hash) { - if ($_ =~ /^pre_/) { - delete $hash{$_}; +########################################################### +########################################################### + +=pod + +=item &handle_custom_fields() + +=cut + +########################################################### +########################################################### +sub handle_custom_fields { + my @results = @{shift()}; + my $customshow=''; + my $extrashow=''; + my @customfields; + if ($env{'form.customshow'}) { + $customshow=$env{'form.customshow'}; + $customshow=~s/[^\w\s]//g; + my @fields=map { + "<font color=\"#008000\">$_:</font><!-- $_ -->"; + } split(/\s+/,$customshow); + @customfields=split(/\s+/,$customshow); + if ($customshow) { + $extrashow="<ul><li>".join("</li><li>",@fields)."</li></ul>\n"; + } + } + my $customdata=''; + my %customhash; + foreach my $result (@results) { + if ($result=~/^(custom\=.*)$/) { # grab all custom metadata + my $tmp=$result; + $tmp=~s/^custom\=//; + my ($k,$v)=map {&unescape($_); + } split(/\,/,$tmp); + $customhash{$k}=$v; } - if ($_ =~ /^store/) { - delete $hash{$_}; - } } + return ($extrashow,\@customfields,\%customhash); } -1; - -__END__ +###################################################################### +###################################################################### -=head1 NAME +=pod -Apache::lonsearchcat - mod_perl module for handling a searchable catalog +=item &search_results_header() -=head1 SYNOPSIS +Output the proper html headers and javascript code to deal with different +calling modes. -Invoked by /etc/httpd/conf/srm.conf: +Takes most inputs directly from %env, except $mode. - <Location /adm/searchcat> - PerlAccessHandler Apache::lonacc - SetHandler perl-script - PerlHandler Apache::lonsearchcat - ErrorDocument 403 /adm/login - ErrorDocument 500 /adm/errorhandler - </Location> +=over 4 -=head1 INTRODUCTION +=item $mode is either (at this writing) 'Basic' or 'Advanced' -This module enables searching for a distributed browseable catalog. +=back -This is part of the LearningOnline Network with CAPA project -described at http://www.lon-capa.org. +The following environment variables are checked: -=head1 BEGIN SUBROUTINE +=over 4 -This routine is only run once after compilation. +=item 'form.catalogmode' -=over 4 +Checked for 'interactive' and 'import'. -=item * +=item 'form.mode' -Initializes %language hash table. +Checked for existance & 'edit' mode. -=item * +=item 'form.form' -Initializes %cprtag hash table (for copyright.tab). +Contains the name of the form that has the input fields to set -=item * +=item 'form.element' -Initializes %mimetag hash table (for filetypes.tab). +the name of the input field to put the URL into -=item * +=item 'form.titleelement' -Initializes %hostdomains and hostips hash table (for hosts.tab). +the name of the input field to put the title into =back -=head1 HANDLER SUBROUTINE +=cut -This routine is called by Apache and mod_perl. +###################################################################### +###################################################################### +sub search_results_header { + my ($importbutton,$closebutton) = @_; -=over 4 + my $js; + # output beginning of search page + # conditional output of script functions dependent on the mode in + # which the search was invoked + if ($env{'form.catalogmode'} eq 'interactive'){ + if (! exists($env{'form.mode'}) || $env{'form.mode'} ne 'edit') { + $js.=<<SCRIPT; +<script type="text/javascript"> +// <![CDATA[ + function select_data(title,url) { + changeTitle(title); + changeURL(url); + parent.close(); + } + function changeTitle(val) { + if (parent.opener.inf.document.forms.resinfo.elements.t) { + parent.opener.inf.document.forms.resinfo.elements.t.value=val; + } + } + function changeURL(val) { + if (parent.opener.inf.document.forms.resinfo.elements.u) { + parent.opener.inf.document.forms.resinfo.elements.u.value=val; + } + } +// ]]> +</script> +SCRIPT + } elsif ($env{'form.mode'} eq 'edit') { + my $form = $env{'form.form'}; + my $element = $env{'form.element'}; + my $titleelement = $env{'form.titleelement'}; + my $changetitle; + if (!$titleelement) { + $changetitle='function changeTitle(val) {}'; + } else { + $changetitle=<<END; +function changeTitle(val) { + if (parent.targetwin.document) { + parent.targetwin.document.forms["$form"].elements["$titleelement"].value=val; + } else { + var url = 'forms[\"$form\"].elements[\"$titleelement\"].value'; + alert("Unable to transfer data to "+url); + } +} +END + } -=item * + $js.=<<SCRIPT; +<script type="text/javascript"> +// <![CDATA[ +function select_data(title,url) { + changeURL(url); + changeTitle(title); + parent.close(); +} +$changetitle +function changeURL(val) { + if (parent.targetwin.document) { + parent.targetwin.document.forms["$form"].elements["$element"].value=val; + } else { + var url = 'forms[\"$form\"].elements[\"$element\"].value'; + alert("Unable to transfer data to "+url); + } +} +// ]]> +</script> +SCRIPT + } + } + my $inhibit_menu = "&".&Apache::loncommon::inhibit_menu_check(); + $js.=<<SCRIPT if $env{'form.catalogmode'} eq 'import'; +<script type="text/javascript"> +// <![CDATA[ + function queue(checkbox_num,val) { + if (document.forms.results.returnvalues.length != "undefined" && + typeof(document.forms.results.returnvalues.length) == "number") { + if (document.forms.results.returnvalues[checkbox_num].checked) { + parent.statusframe.document.forms.statusform.elements.acts.value +='1a'+val+'b'; + } else { + parent.statusframe.document.forms.statusform.elements.acts.value +='0a'+val+'b'; + } + } else { + if (document.forms.results.returnvalues.checked) { + parent.statusframe.document.forms.statusform.elements.acts.value +='1a'+val+'b'; + } else { + parent.statusframe.document.forms.statusform.elements.acts.value +='0a'+val+'b'; + } + } + } + function select_group() { + parent.window.location= + "/adm/groupsort?mode=$env{'form.mode'}&catalogmode=import$inhibit_menu&acts="+ + parent.statusframe.document.forms.statusform.elements.acts.value; + } +// ]]> +</script> +SCRIPT -configure dynamic components of interface + my $start_page = &Apache::loncommon::start_page(undef,$js, + {'only_body' =>1, + 'add_wishlist' =>1, + 'add_modal' =>1}); + my $result=<<END; +$start_page +<form name="results" method="post" action="/adm/searchcat"> +$importbutton +END + return $result; +} -=item * +sub results_link { + my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}. + "&persistent_db_id=".$env{'form.persistent_db_id'}; + my $results_link = $basic_link."&phase=results". + "&pause=1"."&start=1"; + return $results_link; +} -determine current user +###################################################################### +###################################################################### +sub print_frames_interface { + my $r = shift; + my $basic_link = "/adm/searchcat?"."&table=".$env{'form.table'}. + "&persistent_db_id=".$env{'form.persistent_db_id'}; + my $run_search_link = $basic_link."&phase=run_search"; + my $results_link = &results_link(); + my $js = <<JS; +<script type="text/javascript"> +// <![CDATA[ +var targetwin = opener; +var queue = ''; +// ]]> +</script> +JS -=item * + my $start_page = + &Apache::loncommon::start_page('LON-CAPA Digital Library Search Results', + $js, + {'frameset' => 1, + 'add_entries' => { + 'rows' => "150,*",},}); + my $end_page = + &Apache::loncommon::end_page({'frameset' => 1}); + + my $result = <<"ENDFRAMES"; +$start_page + <frame name="statusframe" src="$run_search_link"> + <frame name="resultsframe" src="$results_link"> +$end_page +ENDFRAMES + + $r->print($result); + return; +} + +###################################################################### +###################################################################### + +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'); +} + +###################################################################### +###################################################################### + +sub display_tools { + my ($title, $jumpurl) = @_; + my $result; + # Metadata + $result.= + &Apache::loncommon::modal_link( + $jumpurl.'.meta?inhibitmenu=yes', + '<img class="LC_icon" src="/res/adm/pages/catalog.png" alt="Info" />', + 500,500,'_blank',undef,&mt('Metadata')); + # Stored Links + $result.= + ' <a href="javascript:;"'. + ' onclick="set_wishlistlink('."'$title','$jumpurl'".')" '. + 'title="'.&mt('Save a link for this resource in your personal Stored Links repository').'">'. + '<img class="LC_icon" src="/res/adm/pages/wishlist.png" '. + 'alt="Stored Links" style="width:22px;"/></a>'; + return $result; +} -see if a search invocation should be done +###################################################################### +###################################################################### -=item * +=pod -else, begin building search interface to output +=item Metadata Viewing Functions -=item * +Output is a HTML-ified string. -compute date selection boxes +Input arguments are title, author, subject, url, keywords, version, +notes, short abstract, mime, language, creation date, +last revision date, owner, copyright, hostname, and +extra custom metadata to show. -=item * +=over 4 -compute customized metadata field +=item &detailed_citation_view() -=item * +=cut -print screen +###################################################################### +###################################################################### +sub detailed_citation_view { + my ($prefix,%values) = @_; + my $result; + my $jumpurl=$values{'url'}; + $jumpurl=~s|^/ext/|http://|; + $result .= + '<b>'.$prefix. + '<img src="'.&Apache::loncommon::icon($values{'url'}).'" alt="" />'.' '. + '<a href="'.$jumpurl.'?inhibitmenu=yes" '. + 'target="preview">'.$values{'title'}."</a></b>\n". + &display_tools($values{'title'}, $jumpurl). + "<p>\n". + '<b>'.$values{'author'}.'</b>,'. + ' <i>'.$values{'owner'}.'</i><br />'; + foreach my $field + ( + { name=>'url', + translate => '<b>URL:</b> [_1]', + special => 'url link',}, + { name=>'subject', + translate => '<b>Subject:</b> [_1]',}, + { name=>'keywords', + translate => '<b>Keywords:</b> [_1]',}, + { name=>'notes', + translate => '<b>Notes:</b> [_1]',}, + { name=>'mimetag', + translate => '<b>MIME Type:</b> [_1]',}, + { name=>'standards', + translate => '<b>Standards:</b>[_1]',}, + { name=>'copyrighttag', + translate => '<b>Copyright/Distribution:</b> [_1]',}, + { name=>'count', + format => "%d", + translate => '<b>Access Count:</b> [_1]',}, + { name=>'stdno', + format => "%d", + translate => '<b>Number of Students:</b> [_1]',}, + { name=>'avetries', + format => "%.2f", + translate => '<b>Average Tries:</b> [_1]',}, + { name=>'disc', + format => "%.2f", + translate => '<b>Degree of Discrimination:</b> [_1]',}, + { name=>'difficulty', + format => "%.2f", + translate => '<b>Degree of Difficulty:</b> [_1]',}, + { name=>'clear', + format => "%.2f", + translate => '<b>Clear:</b> [_1]',}, + { name=>'depth', + format => "%.2f", + translate => '<b>Depth:</b> [_1]',}, + { name=>'helpful', + format => "%.2f", + translate => '<b>Helpful:</b> [_1]',}, + { name=>'correct', + format => "%.2f", + translate => '<b>Correct:</b> [_1]',}, + { name=>'technical', + format => "%.2f", + translate => '<b>Technical:</b> [_1]',}, + { name=>'comefrom_list', + type => 'list', + translate => 'Resources that lead up to this resource in maps',}, + { name=>'goto_list', + type => 'list', + translate => 'Resources that follow this resource in maps',}, + { name=>'sequsage_list', + type => 'list', + translate => 'Resources using or importing resource',}, + ) { + next if (! exists($values{$field->{'name'}}) || + $values{$field->{'name'}} eq ''); + if (exists($field->{'type'}) && $field->{'type'} eq 'list') { + $result .= '<b>'.&mt($field->{'translate'}).'</b>'; + foreach my $item (split(',',$values{$field->{'name'}})){ + $item = &Apache::lonnet::clutter($item); + $result .= '<br />'.&display_url($item,1).'<br />'; + } + } 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 .= '<br />'.&display_url($jumpurl,1).'<br />'; + } else { + $result.= &mt($field->{'translate'}, + $values{$field->{'name'}}); + } + $result .= "<br />\n"; + } + } + $result .= "</p>"; + if (exists($values{'extrashow'}) && $values{'extrashow'} ne '') { + $result .= '<p>'.$values{'extrashow'}.'</p>'; + } + if (exists($values{'shortabstract'}) && $values{'shortabstract'} ne '') { + $result .= '<p>'.$values{'shortabstract'}.'</p>'; + } + return $result; +} -=back +sub detailed_citation_preview { + my ($prefix,%values)=@_; + return &detailed_citation_view($prefix,%values). + '</td><td>'. + &Apache::lonindexer::showpreview($values{'url'}); +} -=head1 OTHER SUBROUTINES -=over 4 +###################################################################### +###################################################################### -=item * +=pod -get_unprocessed_cgi() : reads in critical name/value pairs that may have not -been processed and passed into %ENV by the web server +=item &summary_view() -=item * +=cut +###################################################################### +###################################################################### +sub summary_view { + my ($prefix,%values) = @_; + my $icon=&Apache::loncommon::icon($values{'url'}); + my $result=qq{$prefix<img src="$icon" alt="" />}; + if (exists($env{'form.sortfield'}) && + $env{'form.sortfield'} !~ /^(default| + author| + url| + title| + owner| + lastrevisiondate| + copyright)$/x) { + my $tmp = $values{$env{'form.sortfield'}}; + if (! defined($tmp)) { $tmp = 'undefined'; } + $result .= ' '.$tmp.' '; + } + my $jumpurl=$values{'url'}; + $jumpurl=~s|^/ext/|http://|; + my $link = '<br />'.&display_url($jumpurl,1).'<br />'; + $result .= + '<a href="'.$jumpurl.'?inhibitmenu=yes"'. + ' target="preview">'.$values{'title'}.'</a>'. + &display_tools($values{'title'}, $jumpurl).<<END; +<br /> +$link<br /> +$values{'author'}, $values{'owner'} -- $values{'lastrevisiondate'}<br /> +$values{'copyrighttag'}<br /> +$values{'extrashow'} +END + return $result; +} -make_persistent() : makes a set of hidden HTML fields to make -SQL search interface information to be persistent +sub summary_preview { + my ($prefix,%values)=@_; + return &summary_view($prefix,%values). + '</td><td>'. + &Apache::lonindexer::showpreview($values{'url'}); +} -=back +###################################################################### +###################################################################### -WEB INTERFACE COMPONENT FUNCTIONS +=pod -=over 4 +=item &compact_view() -=item * +=cut -simpletextfield(name,value) : returns HTML formatted string for simple text -field +###################################################################### +###################################################################### +sub compact_view { + my ($prefix,%values) = @_; + my $jumpurl=$values{'url'}; + $jumpurl=~s|^/ext/|http://|; -=item * + my $link = &display_url($jumpurl,1); + + my $result = + $prefix.'<img src="'.&Apache::loncommon::icon($values{'url'}).'" alt="" />'; + if (exists($env{'form.sortfield'}) && + $env{'form.sortfield'} !~ /^(default|author|url|title)$/) { + my $tmp = $values{$env{'form.sortfield'}}; + if (! defined($tmp)) { $tmp = 'undefined'; } + $result .= ' '.$tmp.' '; + } + $jumpurl = &HTML::Entities::encode($jumpurl,'<>&"'); + $result.=' <span class="LC_nobreak">'. + '<a href="'.$jumpurl.'?inhibitmenu=yes" target="preview">'. + &HTML::Entities::encode($values{'title'},'<>&"').'</a></span> '. + &display_tools($values{'title'}, $jumpurl). + $link.' <b>'.$values{'author'}.'</b> ('.$values{'domain'}.')'; + return $result; +} -simplecheckbox(name,value) : returns HTML formatted string for simple -checkbox +sub display_url { + my ($url,$skiplast) = @_; + my $link; + if ($url=~m|^/ext/|) { + $url=~s|^/ext/|http://|; + $link='<span class="LC_filename">'.$url.'</span>'; + } elsif ($url=~m{^(http://|/uploaded/)}) { + $link='<span class="LC_filename">'.$url.'</span>'; + } else { + $link=&Apache::lonhtmlcommon::crumbs( + $url, + 'preview', + '', + (($env{'form.catalogmode'} eq 'import')?'parent.statusframe.document.forms.statusform':''), + $skiplast).' '; + } + return $link; +} -=item * +###################################################################### +###################################################################### -searchphrasefield(title,name,value) : returns HTML formatted string for -a search expression phrase field +=pod -=item * +=item &fielded_format_view() -dateboxes(name, defaultmonth, defaultday, defaultyear) : returns HTML -formatted string for a calendar date +=cut -=item * +###################################################################### +###################################################################### +sub fielded_format_view { + my ($prefix,%values) = @_; + my $icon=&Apache::loncommon::icon($values{'url'}); + my %Translated = &Apache::lonmeta::fieldnames(); + my $jumpurl=$values{'url'}; + $jumpurl=~s|^/ext/|http://|; -selectbox(title,name,value,%HASH=options) : returns HTML formatted string for -a selection box field + my $result=<<END; +$prefix <img src="$icon" alt="" /> +<dl> +<dt>URL:</dt> + <dd><a href="$jumpurl?inhibitmenu=yes" + target='preview'>$values{'url'}</a> +END + $result .= + &display_tools($values{'title'}, $jumpurl).' + </dd>'; + foreach my $field ('title','author','domain','subject','keywords','notes', + 'mimetag','language','creationdate','lastrevisiondate', + 'owner','copyrighttag','hostname','abstract') { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + if (&has_stat_data(\%values)) { + foreach my $field (&statfields()) { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + } + if (&has_eval_data(\%values)) { + foreach my $field (&evalfields()) { + $result .= (' 'x4).'<dt>'.$Translated{$field}.'</dt>'."\n". + (' 'x8).'<dd>'.$values{$field}.'</dd>'."\n"; + } + } + $result .= "</dl>\n"; + $result .= $values{'extrashow'}; + return $result; +} -=back +###################################################################### +###################################################################### -SEARCH FUNCTIONS +=pod -=over 4 +=item &xml_sgml_view() -=item * +=back -advancedsearch(server reference, environment reference) : perform a complex -multi-field logical query +=cut -=item * +###################################################################### +###################################################################### +sub xml_sgml_view { + my ($prefix,%values) = @_; + my $xml = '<LonCapaResource>'."\n"; + # The usual suspects + foreach my $field ('url','title','author','subject','keywords','notes','domain') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + # + $xml .= "<mimeInfo>\n"; + foreach my $field ('mime','mimetag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</mimeInfo>\n"; + # + $xml .= "<languageInfo>\n"; + foreach my $field ('language','languagetag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</languageInfo>\n"; + # + foreach my $field ('creationdate','lastrevisiondate','owner') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + # + $xml .= "<copyrightInfo>\n"; + foreach my $field ('copyright','copyrighttag') { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</copyrightInfo>\n"; + $xml .= qq{<repositoryLocation>$values{'hostname'}</repositoryLocation>}. + "\n"; + $xml .= qq{<shortabstract>$values{'shortabstract'}</shortabstract>}."\n"; + # + if (&has_stat_data(\%values)){ + $xml .= "<problemstatistics>\n"; + foreach my $field (&statfields()) { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</problemstatistics>\n"; + } + # + if (&has_eval_data(\%values)) { + $xml .= "<evaluation>\n"; + foreach my $field (&evalfields) { + $xml .= qq{<$field>$values{$field}</$field>}."\n"; + } + $xml .= "</evaluation>\n"; + } + # + $xml .= "</LonCapaResource>\n"; + $xml = &HTML::Entities::encode($xml,'<>&'); + my $result=<<END; +$prefix +<pre> +$xml +</pre> +$values{'extrashow'} +END + return $result; +} -basicsearch(server reference, environment reference) : perform a simple -single-field logical query +###################################################################### +###################################################################### -=item * +=pod -build_SQL_query(field name, logic) : builds a SQL query string from a -logical expression with AND/OR keywords +=item &filled() see if field is filled. -=item * +=cut -build_custommetadata_query(field_name, logic_statement) : builds a perl -regular expression from a logical expression with AND/OR keywords +###################################################################### +###################################################################### +sub filled { + my ($field)=@_; + if ($field=~/\S/ && $field ne 'any') { + return 1; + } else { + return 0; + } +} -=item * +###################################################################### +###################################################################### -recursive_SQL_query_build(field name, reverse notation expression) : -builds a SQL query string from a reverse notation expression -logical expression with AND/OR keywords +=pod -=item * +=item &output_unparsed_phrase_error() -build_date_queries(cmonth1, cday1, cyear1, cmonth2, cday2, cyear2, -lmonth1, lday1, lyear1, lmonth2, lday2, lyear2) : -Builds a SQL logic query to check time/date entries. +=cut -=back +###################################################################### +###################################################################### +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 [_1]. Please modify your search.' + ,'<i>'.$env{'form.basicexp'}.'</i>'); + } else { + $errorstring = &mt('Unable to understand the search phrase [_1]: [_2]' + ,'<i>'.$field.'</i>' + ,$env{'form.'.$field}); + } + my $heading = &mt('Unparsed Field'); + my $revise = &mt('Revise search request'); + # make query information persistent to allow for subsequent revision + my $start_page = &Apache::loncommon::start_page('Search'); + my $end_page = &Apache::loncommon::end_page(); + $r->print(<<ENDPAGE); +$start_page +<form method="post" action="/adm/searchcat"> +$hidden_fields +$closebutton +<hr /> +<h2>$heading</h2> +<p class="LC_warning"> +$errorstring +</p> +<p> +<a href="/adm/searchcat?$parms&persistent_db_id=$env{'form.persistent_db_id'}">$revise</a> +</p> +$end_page +ENDPAGE +} -OUTPUTTING RESULTS FUNCTION +###################################################################### +###################################################################### -=over 4 +=pod -=item * +=item &output_blank_field_error() -output_results(output mode, server reference, environment reference, -reply list reference) : outputs results from search +Output a complete page that indicates the user has not filled in enough +information to do a search. -=back +Inputs: $r (Apache request handle), $closebutton, $parms. -DIFFERENT WAYS TO VIEW METADATA RECORDS +Returns: nothing -=over 4 +$parms is extra information to include in the 'Revise search request' link. -=item * +=cut -detailed_citation_view(ORDERED METADATA LIST FOR A RESULT OBJECT INSTANCE) : -see metadata viewing notes below +###################################################################### +###################################################################### +sub output_blank_field_error { + my ($r,$closebutton,$parms,$hidden_fields)=@_; + 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 Query'); + my $start_page = &Apache::loncommon::start_page('Search'); + my $end_page = &Apache::loncommon::end_page(); + if ($closebutton) { + $closebutton = '<p>'.$closebutton.'</p><hr />'; + } else { + &Apache::lonhtmlcommon::add_breadcrumb + ({href=>'', + text=>$heading,}); + $start_page .= &Apache::lonhtmlcommon::breadcrumbs(); + } -=item * + $r->print(<<ENDPAGE); +$start_page +<form method="post" action="/adm/searchcat"> +$hidden_fields +$closebutton +</form> +<h2>$heading</h2> +<p class="LC_warning"> +$errormsg +</p> +<p> +<a href="/adm/searchcat?$parms&persistent_db_id=$env{'form.persistent_db_id'}">$revise</a> +</p> +$end_page +ENDPAGE + return; +} -summary_view(ORDERED METADATA LIST FOR A RESULT OBJECT INSTANCE) : -see metadata viewing notes below +###################################################################### +###################################################################### -=item * +=pod -fielded_format_view(ORDERED METADATA LIST FOR A RESULT OBJECT INSTANCE) : -see metadata viewing notes below +=item &output_date_error() -=item * +Output a full html page with an error message. -xml_sgml_view(ORDERED METADATA LIST FOR A RESULT OBJECT INSTANCE) : -see metadata viewing notes below +Inputs: -=back + $r, the request pointer. + $message, the error message for the user. + $closebutton, the specialized close button needed for groupsearch. - _____________________________________________________________________ - | * Metadata viewing notes | - | Output is a HTML-ified string. | - | Input arguments are title, author, subject, url, keywords, version, | - | notes, short abstract, mime, language, creation date, | - | last revision date, owner, copyright, hostname, httphost, and | - | extra custom metadata to show. | - --------------------------------------------------------------------- +=cut -TEST CONDITIONAL FUNCTIONS +###################################################################### +###################################################################### +sub output_date_error { + my ($r,$message,$closebutton,$hidden_fields)=@_; + # make query information persistent to allow for subsequent revision + my $start_page = &Apache::loncommon::start_page('Search'); + my $end_page = &Apache::loncommon::end_page(); + my $heading = &mt('Error'); + $r->print(<<RESULTS); +$start_page +<form method="post" action="/adm/searchcat"> +$hidden_fields +<input type='button' value='Revise search request' +onclick='this.form.submit();' /> +$closebutton +</form> +<hr /> +<h3>$heading</h3> +<p class="LC_error"> +$message +</p> +$end_page +RESULTS +} -=over 4 +###################################################################### +###################################################################### -=item * +=pod -filled(field) : determines whether a given field has been filled +=item &start_fresh_session() -=back +Cleans the global %groupsearch_db by removing all fields which begin with +'pre_' or 'store'. -ERROR FUNCTIONS +=cut -=over 4 +###################################################################### +###################################################################### +sub start_fresh_session { + delete $groupsearch_db{'mode_catalog'}; + foreach (keys %groupsearch_db) { + if ($_ =~ /^pre_/) { + delete $groupsearch_db{$_}; + } + if ($_ =~ /^store/) { + delete $groupsearch_db{$_}; + } + } +} -=item * +1; -output_blank_field_error(server reference) : outputs a message saying that -more fields need to be filled in +sub cleanup { + if (tied(%groupsearch_db)) { + unless (untie(%groupsearch_db)) { + &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db'); + } + } + &Apache::lonmysql::disconnect_from_db(); + return OK; +} -=item * +__END__ -output_date_error(server reference, error message) : outputs -an error message specific to bad date format. +=pod -=back +=back =cut