File:  [LON-CAPA] / loncom / interface / lonsearchcat.pm
Revision 1.210: download - view: text, annotated - select for diffs
Mon Apr 19 17:40:00 2004 UTC (20 years, 1 month ago) by matthew
Branches: MAIN
CVS tags: HEAD
Bug 2910: negative time remaining in search.  Fixed by setting
time_remaining to 0 when it is less than zero.  Increased the frequency of
time updates (appears to work more consistently now) by updating the time
both before and after we undertake very time consuming tasks (instead of
just after).
Also checked for untitled documents since the document title is used for
linking most of the time.

    1: # The LearningOnline Network with CAPA
    2: # Search Catalog
    3: #
    4: # $Id: lonsearchcat.pm,v 1.210 2004/04/19 17:40:00 matthew Exp $
    5: #
    6: # Copyright Michigan State University Board of Trustees
    7: #
    8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
    9: #
   10: # LON-CAPA is free software; you can redistribute it and/or modify
   11: # it under the terms of the GNU General Public License as published by
   12: # the Free Software Foundation; either version 2 of the License, or
   13: # (at your option) any later version.
   14: #
   15: # LON-CAPA is distributed in the hope that it will be useful,
   16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   18: # GNU General Public License for more details.
   19: #
   20: # You should have received a copy of the GNU General Public License
   21: # along with LON-CAPA; if not, write to the Free Software
   22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   23: #
   24: # /home/httpd/html/adm/gpl.txt
   25: #
   26: # http://www.lon-capa.org/
   27: #
   28: ###############################################################################
   29: ###############################################################################
   30: 
   31: =pod 
   32: 
   33: =head1 NAME
   34: 
   35: lonsearchcat - LONCAPA Search Interface
   36: 
   37: =head1 SYNOPSIS
   38: 
   39: Search interface to LON-CAPAs digital library
   40: 
   41: =head1 DESCRIPTION
   42: 
   43: This module enables searching for a distributed browseable catalog.
   44: 
   45: This is part of the LearningOnline Network with CAPA project
   46: described at http://www.lon-capa.org.
   47: 
   48: lonsearchcat presents the user with an interface to search the LON-CAPA
   49: digital library.  lonsearchcat also initiates the execution of a search
   50: by sending the search parameters to LON-CAPA servers.  The progress of 
   51: search (on a server basis) is displayed to the user in a separate window.
   52: 
   53: =head1 Internals
   54: 
   55: =over 4
   56: 
   57: =cut
   58: 
   59: ###############################################################################
   60: ###############################################################################
   61: 
   62: package Apache::lonsearchcat;
   63: 
   64: use strict;
   65: use Apache::Constants qw(:common :http);
   66: use Apache::lonnet();
   67: use Apache::File();
   68: use CGI qw(:standard);
   69: use Text::Query;
   70: use GDBM_File;
   71: use Apache::loncommon();
   72: use Apache::lonmysql();
   73: use Apache::lonmeta;
   74: use Apache::lonhtmlcommon;
   75: use Apache::lonlocal;
   76: use LONCAPA::lonmetadata();
   77: 
   78: ######################################################################
   79: ######################################################################
   80: ##
   81: ## Global variables
   82: ##
   83: ######################################################################
   84: ######################################################################
   85: my %groupsearch_db;  # Database hash used to save values for the 
   86:                      # groupsearch RAT interface.
   87: my %persistent_db;   # gdbm hash which holds data which is supposed to
   88:                      # persist across calls to lonsearchcat.pm
   89: 
   90: # The different view modes and associated functions
   91: 
   92: my %Views = ("detailed" => \&detailed_citation_view,
   93: 	     "summary"  => \&summary_view,
   94: 	     "fielded"  => \&fielded_format_view,
   95: 	     "xml"      => \&xml_sgml_view,
   96: 	     "compact"  => \&compact_view);
   97: 
   98: ######################################################################
   99: ######################################################################
  100: sub handler {
  101:     my $r = shift;
  102: #    &set_defaults();
  103:     #
  104:     # set form defaults
  105:     #
  106:     my $hidden_fields;# Hold all the hidden fields used to keep track
  107:                       # of the search system state
  108:     my $importbutton; # button to take the selected results and go to group 
  109:                       # sorting
  110:     my $diropendb;    # The full path to the (temporary) search database file.
  111:                       # This is set and used in &handler() and is also used in 
  112:                       # &output_results().
  113:     my $bodytag;  # LON-CAPA standard body tag, gotten from 
  114:                   # &Apache::lonnet::bodytag. 
  115:                   # No title, no table, just a <body> tag.
  116: 
  117:     my $loaderror=&Apache::lonnet::overloaderror($r);
  118:     if ($loaderror) { return $loaderror; }
  119: 
  120:     my $closebutton;  # button that closes the search window 
  121:                       # This button is different for the RAT compared to
  122:                       # normal invocation.
  123:     #
  124:     &Apache::loncommon::content_type($r,'text/html');
  125:     $r->send_http_header;
  126:     return OK if $r->header_only;
  127:     ##
  128:     ## Prevent caching of the search interface window.  Hopefully this means
  129:     ## we will get the launch=1 passed in a little more.
  130:     &Apache::loncommon::no_cache($r);
  131:     ## 
  132:     ## Pick up form fields passed in the links.
  133:     ##
  134:     &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
  135:              ['catalogmode','launch','acts','mode','form','element','pause',
  136:               'phase','persistent_db_id','table','start','show',
  137:               'cleargroupsort','titleelement']);
  138:     ##
  139:     ## The following is a trick - we wait a few seconds if asked to so
  140:     ##     the daemon running the search can get ahead of the daemon
  141:     ##     printing the results.  We only need (theoretically) to do
  142:     ##     this once, so the pause indicator is deleted
  143:     ##
  144:     if (exists($ENV{'form.pause'})) {
  145:         sleep(1);
  146:         delete($ENV{'form.pause'});
  147:     }
  148:     ##
  149:     ## Initialize global variables
  150:     ##
  151:     my $domain  = $r->dir_config('lonDefDomain');
  152:     $diropendb= "/home/httpd/perl/tmp/$ENV{'user.domain'}_$ENV{'user.name'}_searchcat.db";
  153:     #
  154:     # set the name of the persistent database
  155:     #          $ENV{'form.persistent_db_id'} can only have digits in it.
  156:     if (! exists($ENV{'form.persistent_db_id'}) ||
  157:         ($ENV{'form.persistent_db_id'} =~ /\D/) ||
  158:         ($ENV{'form.launch'} eq '1')) {
  159:         $ENV{'form.persistent_db_id'} = time;
  160:     }
  161:     $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
  162:     my $persistent_db_file = "/home/httpd/perl/tmp/".
  163:         &Apache::lonnet::escape($domain).
  164:             '_'.&Apache::lonnet::escape($ENV{'user.name'}).
  165:                 '_'.$ENV{'form.persistent_db_id'}.'_persistent_search.db';
  166:     ##
  167:     &Apache::lonhtmlcommon::clear_breadcrumbs();
  168:     if (exists($ENV{'request.course.id'}) && $ENV{'request.course.id'} ne '') {
  169:         &Apache::lonhtmlcommon::add_breadcrumb
  170:              ({href=>'/adm/searchcat?'.
  171:                    'catalogmode='.$ENV{'form.catalogmode'}.
  172:                    '&launch='.$ENV{'form.launch'}.
  173:                    '&mode='.$ENV{'form.mode'},
  174:               text=>"Course and Catalog Search",
  175:               bug=>'Searching',});
  176:     } else {
  177:         &Apache::lonhtmlcommon::add_breadcrumb
  178:              ({href=>'/adm/searchcat?'.
  179:                    'catalogmode='.$ENV{'form.catalogmode'}.
  180:                    '&launch='.$ENV{'form.launch'}.
  181:                    '&mode='.$ENV{'form.mode'},
  182:               text=>"Catalog Search",
  183:               bug=>'Searching',});
  184:     }
  185:     #
  186:     if (! &get_persistent_form_data($persistent_db_file)) {
  187:         if ($ENV{'form.phase'} =~ /(run_search|results)/) {
  188:             &Apache::lonnet::logthis("lonsearchcat:Unable to recover data ".
  189:                                      "from $persistent_db_file");
  190:             $r->print(<<END);
  191: <html>
  192: <head><title>LON-CAPA Search Error</title></head>
  193: $bodytag
  194: We were unable to retrieve data describing your search.  This is a serious
  195: error and has been logged.  Please alert your LON-CAPA administrator.
  196: </body>
  197: </html>
  198: END
  199:             return OK;
  200:         }
  201:     }
  202:     ##
  203:     ## Clear out old values from groupsearch database
  204:     ##
  205:     untie %groupsearch_db if (tied(%groupsearch_db));
  206:     if (($ENV{'form.cleargroupsort'} eq '1') || 
  207:         (($ENV{'form.launch'} eq '1') && 
  208:          ($ENV{'form.catalogmode'} eq 'groupsearch'))) {
  209: 	if (tie(%groupsearch_db,'GDBM_File',$diropendb,&GDBM_WRCREAT(),0640)) {
  210: 	    &start_fresh_session();
  211: 	    untie %groupsearch_db;
  212:             delete($ENV{'form.cleargroupsort'});
  213: 	} else {
  214:             # This is a stupid error to give to the user.  
  215:             # It really tells them nothing.
  216: 	    $r->print('<html><head></head>'.$bodytag.
  217:                       'Unable to tie hash to db file</body></html>');
  218: 	    return OK;
  219: 	}
  220:     }
  221:     ##
  222:     ## Configure hidden fields
  223:     ##
  224:     $hidden_fields = '<input type="hidden" name="persistent_db_id" value="'.
  225:         $ENV{'form.persistent_db_id'}.'" />'."\n";
  226:     if (exists($ENV{'form.catalogmode'})) {
  227:         $hidden_fields .= '<input type="hidden" name="catalogmode" value="'.
  228:                 $ENV{'form.catalogmode'}.'" />'."\n";
  229:     }
  230:     if (exists($ENV{'form.form'})) {
  231:         $hidden_fields .= '<input type="hidden" name="form" value="'.
  232:                 $ENV{'form.form'}.'" />'."\n";
  233:     }
  234:     if (exists($ENV{'form.element'})) {
  235:         $hidden_fields .= '<input type="hidden" name="element" value="'.
  236:                 $ENV{'form.element'}.'" />'."\n";
  237:     }
  238:     if (exists($ENV{'form.titleelement'})) {
  239:         $hidden_fields .= '<input type="hidden" name="titleelement" value="'.
  240:                 $ENV{'form.titleelement'}.'" />'."\n";
  241:     }
  242:     if (exists($ENV{'form.mode'})) {
  243:         $hidden_fields .= '<input type="hidden" name="mode" value="'.
  244:                 $ENV{'form.mode'}.'" />'."\n";
  245:     }
  246:     ##
  247:     ## Configure dynamic components of interface
  248:     ##
  249:     if ($ENV{'form.catalogmode'} eq 'interactive') {
  250:         $closebutton="<input type='button' name='close' value='CLOSE' ";
  251:         if ($ENV{'form.phase'} =~ /(results|run_search)/) {
  252: 	    $closebutton .="onClick='parent.close()'";
  253:         } else {
  254:             $closebutton .="onClick='self.close()'";
  255:         }
  256:         $closebutton .=">\n";
  257:     } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') {
  258:         $closebutton="<input type='button' name='close' value='CLOSE' ";
  259:         if ($ENV{'form.phase'} =~ /(results|run_search)/) {
  260: 	    $closebutton .="onClick='parent.close()'";
  261:         } else {
  262:             $closebutton .="onClick='self.close()'";
  263:         }
  264:         $closebutton .= ">";
  265:         $importbutton=<<END;
  266: <input type='button' name='import' value='IMPORT'
  267: onClick='javascript:select_group()'>
  268: END
  269:     } else {
  270:         $closebutton = '';
  271:         $importbutton = '';
  272:     }
  273:     ##
  274:     ## Sanity checks on form elements
  275:     ##
  276:     if (!defined($ENV{'form.viewselect'})) {
  277:         if (($ENV{'form.catalogmode'} eq 'groupsearch') ||
  278:             ($ENV{'form.catalogmode'} eq 'interactive')) {
  279:             $ENV{'form.viewselect'} ="Compact View";
  280:         } else {
  281:             $ENV{'form.viewselect'} ="Detailed Citation View";
  282:         }
  283:     }
  284:     $ENV{'form.phase'} = 'disp_basic' if (! exists($ENV{'form.phase'}));
  285:     $ENV{'form.show'} = 20 if (! exists($ENV{'form.show'}));
  286:     #
  287:     $ENV{'form.searchmode'} = 'basic';
  288:     if ($ENV{'form.phase'} eq 'adv_search' ||
  289:         $ENV{'form.phase'} eq 'disp_adv') {
  290:         $ENV{'form.searchmode'} = 'advanced';
  291:     } elsif ($ENV{'form.phase'} eq 'course_search') {
  292:         $ENV{'form.searchmode'} = 'course_search';
  293:     }
  294:     #
  295:     if ($ENV{'form.searchmode'} eq 'advanced') {
  296:         &Apache::lonhtmlcommon::add_breadcrumb
  297:             ({href=>'/adm/searchcat?phase=disp_adv&'.
  298:                   'catalogmode='.$ENV{'form.catalogmode'}.
  299:                   '&launch='.$ENV{'form.launch'}.
  300:                   '&mode='.$ENV{'form.mode'},
  301:                   text=>"Advanced Search",
  302:                   bug=>'Searching',});
  303:     } elsif ($ENV{'form.searchmode'} eq 'course search') {
  304:         &Apache::lonhtmlcommon::add_breadcrumb
  305:             ({href=>'/adm/searchcat?phase=disp_adv&'.
  306:                   'catalogmode='.$ENV{'form.catalogmode'}.
  307:                   '&launch='.$ENV{'form.launch'}.
  308:                   '&mode='.$ENV{'form.mode'},
  309:                   text=>"Course Search",
  310:                   bug=>'Searching',});
  311:     }
  312:     ##
  313:     ## Switch on the phase
  314:     ##
  315:     if ($ENV{'form.phase'} eq 'disp_basic') {
  316:         &print_basic_search_form($r,$closebutton,$hidden_fields);
  317:     } elsif ($ENV{'form.phase'} eq 'disp_adv') {
  318:         &print_advanced_search_form($r,$closebutton,$hidden_fields);
  319:     } elsif ($ENV{'form.phase'} eq 'results') {
  320:         &display_results($r,$importbutton,$closebutton,$diropendb);
  321:     } elsif ($ENV{'form.phase'} =~ /^(sort|run_search)$/) {
  322:         my ($query,$customquery,$customshow,$libraries,$pretty_string) =
  323:             &get_persistent_data($persistent_db_file,
  324:                  ['query','customquery','customshow',
  325:                   'libraries','pretty_string']);
  326:         if ($ENV{'form.phase'} eq 'sort') {
  327:             &print_sort_form($r,$pretty_string);
  328:         } elsif ($ENV{'form.phase'} eq 'run_search') {
  329:             &run_search($r,$query,$customquery,$customshow,
  330:                         $libraries,$pretty_string);
  331:         }
  332:     } elsif ($ENV{'form.phase'} eq 'course_search') {
  333:         &course_search($r);
  334:     } elsif(($ENV{'form.phase'} eq 'basic_search') ||
  335:             ($ENV{'form.phase'} eq 'adv_search')) {
  336:         # Set up table
  337:         if (! defined(&create_results_table())) {
  338: 	    my $errorstring=&Apache::lonmysql::get_error();
  339:             $r->print(<<END);
  340: <html><head><title>Search Error</title></head>
  341: $bodytag
  342: Unable to create table in which to store search results.  
  343: The search has been aborted.
  344: <br />$errorstring
  345: </body>
  346: </html>
  347: END
  348:             return OK;
  349:         }
  350:         delete($ENV{'form.launch'});
  351:         if (! &make_form_data_persistent($r,$persistent_db_file)) {
  352:             $r->print(<<END);
  353: <html><head><title>Search Error</title></head>
  354: $bodytag
  355: Unable to properly store search information.  The search has been aborted.
  356: </body>
  357: </html>
  358: END
  359:             return OK;
  360:         }
  361:         #
  362:         # We are running a search
  363:         my ($query,$customquery,$customshow,$libraries) = 
  364:             (undef,undef,undef,undef);
  365:         my $pretty_string;
  366:         if ($ENV{'form.phase'} eq 'basic_search') {
  367:             ($query,$pretty_string,$libraries) = 
  368:                 &parse_basic_search($r,$closebutton,$hidden_fields);
  369:         } else {                      # Advanced search
  370:             ($query,$customquery,$customshow,$libraries,$pretty_string) 
  371:                 = &parse_advanced_search($r,$closebutton,$hidden_fields);
  372:             return OK if (! defined($query));
  373:         }
  374:         &make_persistent({ query => $query,
  375:                            customquery => $customquery,
  376:                            customshow => $customshow,
  377:                            libraries => $libraries,
  378:                            pretty_string => $pretty_string },
  379:                          $persistent_db_file);
  380:         ##
  381:         ## Print out the frames interface
  382:         ##
  383:         &print_frames_interface($r);
  384:     }
  385:     return OK;
  386: } 
  387: 
  388: ######################################################################
  389: ######################################################################
  390: ##
  391: ##   Course Search
  392: ##
  393: ######################################################################
  394: ######################################################################
  395: {   # Scope the course search to avoid global variables
  396: #
  397: # Variables For course search
  398: my %alreadyseen;
  399: my %hash;
  400: my $totalfound;
  401: 
  402: sub course_search {
  403:     my $r=shift;
  404:     my $bodytag=&Apache::loncommon::bodytag('Course Search').
  405: 	&Apache::loncommon::help_open_bug('Searching');
  406:     my $pretty_search_string = '<b>'.$ENV{'form.courseexp'}.'</b>';
  407:     my $search_string = $ENV{'form.courseexp'};
  408:     my @New_Words;
  409:     if ($ENV{'form.crsrelated'}) {
  410:         ($search_string,@New_Words) = &related_version($ENV{'form.courseexp'});
  411:         if (@New_Words) {
  412:             $pretty_search_string .= ' '.&mt("with related words").": <b>@New_Words</b>.";
  413:         } else {
  414:             $pretty_search_string .= ' '.&mt('with no related words').".";
  415:         }
  416:     }
  417:     my $fulltext=$ENV{'form.crsfulltext'};
  418:     my @allwords=($search_string,@New_Words);
  419:     $totalfound=0;
  420:     $r->print('<html><head><title>LON-CAPA Course Search</title></head>'.
  421: 	      $bodytag.'<hr /><center><font size="+2" face="arial">'.$pretty_search_string.'</font></center><hr />');
  422:     $r->rflush();
  423: # ======================================================= Go through the course
  424:     undef %alreadyseen;
  425:     %alreadyseen=();
  426:     my $c=$r->connection;
  427:     if (tie(%hash,'GDBM_File',$ENV{'request.course.fn'}.".db",
  428:             &GDBM_READER(),0640)) {
  429:         foreach (keys %hash) {
  430:             if ($c->aborted()) { last; }
  431:             if (($_=~/^src\_(.+)$/) && (!$alreadyseen{$hash{$_}})) {
  432:                 &checkonthis($r,$hash{$_},0,$hash{'title_'.$1},$fulltext,
  433:                              @allwords);
  434:             }
  435:         }
  436:         untie(%hash);
  437:     }
  438:     unless ($totalfound) {
  439: 	$r->print('<p>'.&mt('No resources found').'.</p>');
  440:     }
  441: # =================================================== Done going through course
  442:     $r->print('</body></html>');
  443: }
  444: 
  445: # =============================== This pulls up a resource and its dependencies
  446: 
  447: sub checkonthis {
  448:     my ($r,$url,$level,$title,$fulltext,@allwords)=@_;
  449:     $alreadyseen{$url}=1;
  450:     $r->rflush();
  451:     my $result=&Apache::lonnet::metadata($url,'title').' '.
  452:                &Apache::lonnet::metadata($url,'subject').' '.
  453:                &Apache::lonnet::metadata($url,'abstract').' '.
  454:                &Apache::lonnet::metadata($url,'keywords');
  455:     if (($url) && ($fulltext)) {
  456: 	$result.=&Apache::lonnet::ssi_body($url);
  457:     }
  458:     $result=~s/\s+/ /gs;
  459:     my $applies=0;
  460:     foreach (@allwords) {
  461:         if ($_=~/\w/) {
  462: 	   if ($result=~/$_/si) {
  463: 	      $applies++;
  464:            }
  465:        }
  466:     }
  467: # Does this resource apply?
  468:     if ($applies) {
  469:        $r->print('<br />');
  470:        for (my $i=0;$i<=$level*5;$i++) {
  471:            $r->print('&nbsp;');
  472:        }
  473:        $r->print('<a href="'.$url.'" target="cat">'.
  474: 		 ($title?$title:$url).'</a><br />');
  475:        $totalfound++;
  476:     } elsif ($fulltext) {
  477:        $r->print(' .');
  478:     }
  479:     $r->rflush();
  480: # Check also the dependencies of this one
  481:     my $dependencies=
  482:                 &Apache::lonnet::metadata($url,'dependencies');
  483:     foreach (split(/\,/,$dependencies)) {
  484:        if (($_=~/^\/res\//) && (!$alreadyseen{$_})) {
  485:           &checkonthis($r,$_,$level+1,'',$fulltext,@allwords);
  486:        }
  487:     }
  488: }
  489: 
  490: sub untiehash {
  491:     if (tied(%hash)) {
  492:         untie(%hash);
  493:     }
  494: }
  495: 
  496: } # End of course search scoping
  497: 
  498: sub search_html_header {
  499:     my $Str = <<ENDHEADER;
  500: <html>
  501: <head>
  502: <title>The LearningOnline Network with CAPA</title>
  503: <script type="text/javascript">
  504:     function openhelp(val) {
  505: 	openhelpwin=open('/adm/help/searchcat.html','helpscreen',
  506: 	     'scrollbars=1,width=600,height=300');
  507: 	openhelpwin.focus();
  508:     }
  509: </script>
  510: </head>
  511: ENDHEADER
  512:     return $Str;
  513: }
  514: 
  515: ######################################################################
  516: ######################################################################
  517: 
  518: =pod 
  519: 
  520: =item &print_basic_search_form() 
  521: 
  522: Prints the form for the basic search.  Sorry the name is so cryptic.
  523: 
  524: =cut
  525: 
  526: ######################################################################
  527: ######################################################################
  528: sub print_basic_search_form {
  529:     my ($r,$closebutton,$hidden_fields) = @_;
  530:     my $bodytag=&Apache::loncommon::bodytag('Search').
  531:         &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching','Finding_Resources',
  532:                                             undef,undef,! $ENV{'form.launch'});
  533:     my $scrout = &search_html_header().$bodytag;
  534:     if (&Apache::lonnet::allowed('bre',$ENV{'request.role.domain'})) {
  535:         my $Statement=&searchhelp();
  536:         $scrout.=(<<ENDDOCUMENT);
  537: <form name="loncapa_search" method="post" action="/adm/searchcat">
  538: <input type="hidden" name="phase" value="basic_search" />
  539: $hidden_fields
  540: <p>
  541: $Statement.
  542: </p>
  543: <p>
  544: <table>
  545: <tr><td>
  546: ENDDOCUMENT
  547:         $scrout.='&nbsp;'.
  548:             &Apache::lonhtmlcommon::textbox('basicexp',
  549:                                         $ENV{'form.basicexp'},40).
  550:                                         '&nbsp;';
  551:         my $relatedcheckbox = 
  552:             &Apache::lonhtmlcommon::checkbox('related',
  553:                                              $ENV{'form.related'});
  554:         my $domain = $r->dir_config('lonDefDomain');
  555:         my $domaincheckbox = 
  556:             &Apache::lonhtmlcommon::checkbox('domains',
  557:                                              $ENV{'form.domains'});
  558:         my $srch=&mt('Search');
  559:         my $header=&mt('Advanced Search');
  560:         my $userelatedwords=&mt('use related words');
  561:         my $onlysearchdomain=&mt('only search domain');
  562:         my $view=&viewoptions();
  563:         $scrout.=<<END;
  564: </td><td><a
  565: href="/adm/searchcat?phase=disp_adv&catalogmode=$ENV{'form.catalogmode'}&launch=$ENV{'form.launch'}&mode=$ENV{'form.mode'}"
  566: >$header</a></td></tr>
  567: <tr><td>$relatedcheckbox $userelatedwords</td>
  568:     <td>$domaincheckbox $onlysearchdomain <b>$domain</b></td></tr>
  569: </table>
  570: </p>
  571: $view
  572: <p>
  573: &nbsp;<input type="submit" name="basicsubmit" value='$srch' />&nbsp;
  574: $closebutton
  575: END
  576:     $scrout.=<<ENDDOCUMENT;
  577: </p>
  578: </form>
  579: ENDDOCUMENT
  580:     }
  581:     if ($ENV{'request.course.id'}) {
  582: 	my %lt=&Apache::lonlocal::texthash(
  583: 					   'srch' => 'Search',
  584:                                            'header' => 'Course Search',
  585: 	 'note' => 'Enter terms or phrases, then press "Search" below',
  586: 	 'use' => 'use related words',
  587: 	 'full' =>'fulltext search (time consuming)'
  588: 					   );
  589:         $scrout.=(<<ENDCOURSESEARCH);
  590: <hr />
  591: <h1>$lt{'header'}</h1>    
  592: <form name="course_search" method="post" action="/adm/searchcat">
  593: <input type="hidden" name="phase" value="course_search" />
  594: $hidden_fields
  595: <p>
  596: $lt{'note'}.
  597: </p>
  598: <p>
  599: <table>
  600: <tr><td>
  601: ENDCOURSESEARCH
  602:         $scrout.='&nbsp;'.
  603:             &Apache::lonhtmlcommon::textbox('courseexp',
  604:                                   $ENV{'form.courseexp'},40);
  605:         my $crscheckbox = 
  606:             &Apache::lonhtmlcommon::checkbox('crsfulltext',
  607:                                    $ENV{'form.crsfulltext'});
  608:         my $relcheckbox = 
  609:             &Apache::lonhtmlcommon::checkbox('crsrelated',
  610: 				   $ENV{'form.crsrelated'});
  611:         $scrout.=(<<ENDENDCOURSE);
  612: </td></tr>
  613: <tr><td>$relcheckbox $lt{'use'}</td><td></td></tr>
  614: <tr><td>$crscheckbox $lt{'full'}</td><td></td></tr>
  615: </table><p>
  616: &nbsp;<input type="submit" name="coursesubmit" value='$lt{'srch'}' />
  617: </p>
  618: ENDENDCOURSE
  619:     }
  620:     $scrout.=(<<ENDDOCUMENT);
  621: </body>
  622: </html>
  623: ENDDOCUMENT
  624:     $r->print($scrout);
  625:     return;
  626: }
  627: ######################################################################
  628: ######################################################################
  629: 
  630: =pod 
  631: 
  632: =item &advanced_search_form() 
  633: 
  634: Prints the advanced search form.
  635: 
  636: =cut
  637: 
  638: ######################################################################
  639: ######################################################################
  640: sub print_advanced_search_form{
  641:     my ($r,$closebutton,$hidden_fields) = @_;
  642:     my %lt=&Apache::lonlocal::texthash('srch' => 'Search',
  643: 				       'reset' => 'Reset',
  644: 				       'help' => 'Help');
  645:     my $advanced_buttons = <<"END";
  646: <p>
  647: <input type="submit" name="advancedsubmit" value='$lt{"srch"}' />
  648: <input type="reset" name="reset" value='$lt{"reset"}' />
  649: $closebutton
  650: <input type="button" value="$lt{'help'}" onClick="openhelp()" />
  651: END
  652:     my $bodytag=&Apache::loncommon::bodytag('Advanced Catalog Search').
  653:         &Apache::lonhtmlcommon::breadcrumbs(undef,'Searching',
  654:                                             'Finding_Resources',
  655:                                             undef,undef,
  656:                                             ! $ENV{'form.launch'});
  657:     my $searchhelp=&searchhelp();
  658:     my $scrout=&search_html_header();
  659:     $scrout .= <<"ENDHEADER";
  660: $bodytag
  661: <form method="post" action="/adm/searchcat" name="advsearch">
  662: $advanced_buttons
  663: ENDHEADER
  664:     $scrout.=('&nbsp;'x2).&viewoptions().'</p>'.$hidden_fields. 
  665:         '<input type="hidden" name="phase" value="adv_search" />';
  666:     my %fields=&Apache::lonmeta::fieldnames();
  667:     #
  668:     $scrout.= '<p>'.$searchhelp.'</p>'.
  669:         "<table>\n";
  670:     my %related_word_search = 
  671:         ('title'=>1,
  672:          'author'=>0,
  673:          'owner'=>0,
  674:          'authorspace'=>0,
  675:          'modifyinguser'=>0,
  676:          'keywords'=>1,
  677:          'notes'=>1,
  678:          'abstract'=>1,
  679:          'standards'=>1,
  680:          'mime'=>1,
  681:          );
  682:     #
  683:     foreach my $field ('title','author','owner','authorspace','modifyinguser',
  684: 	     'keywords','notes','abstract','standards','mime') {
  685: 	$scrout.='<tr><td align="right">'.&titlefield($fields{$field}).'</td><td>'.
  686: 	    &Apache::lonmeta::prettyinput($field,
  687:                                           $ENV{'form.'.$field},
  688:                                           $field,
  689:                                           'advsearch',
  690: 					  $related_word_search{$field},
  691:                                           '</td><td align="left">',
  692:                                           $ENV{'form.'.$field.'_related'},
  693:                                           50);
  694:         if ($related_word_search{$field}) {
  695:             $scrout .= 'related words';
  696:         } else {
  697:             $scrout .= '</td><td>&nbsp;';
  698:         }
  699:         $scrout .= '</td></tr>'.$/;
  700:     }
  701:     foreach my $field ('lowestgradelevel','highestgradelevel') {
  702: 	$scrout.='<tr>'.
  703:             '<td align="right">'.&titlefield($fields{$field}).'</td>'.
  704:             '<td colspan="2">'.
  705: 	    &Apache::lonmeta::prettyinput($field,
  706:                                           $ENV{'form.'.$field},
  707:                                           $field,
  708:                                           'advsearch',
  709: 					  0).
  710:                                           '</td></tr>'.$/;
  711:     }
  712:     $scrout.='<tr><td align="right">'.
  713: 	&titlefield(&mt('MIME Type Category')).'</td><td colspan="2">'. 
  714: 	    &Apache::loncommon::filecategoryselect('category',
  715: 						   $ENV{'form.category'}).
  716: 	    '</td></tr>'.$/;
  717:     $scrout.='<tr><td align="right" valign="top">'.
  718: 	&titlefield(&mt('Domains')).'</td><td colspan="2">'. 
  719: 	    &Apache::loncommon::domain_select('domains',
  720: 						   $ENV{'form.domains'},1).
  721: 	    '</td></tr>'.$/;
  722:     $scrout .= "</table>\n<br />\n<table>\n";
  723:     my %dates=&Apache::lonlocal::texthash
  724:         ('creationdatestart'     => 'Creation Date After',
  725:          'creationdateend'       => 'Creation Date Before',
  726:          'lastrevisiondatestart' => 'Last Revision Date After',
  727:          'lastrevisiondateend'   => 'Last Revision Date Before');
  728:     foreach my $field (sort keys %dates) {
  729: 	$scrout.='<tr>'.
  730:             '<td align="right">'.&titlefield($dates{$field}).'</td><td>'. 
  731: 	    &Apache::lonhtmlcommon::date_setter('advsearch',$field,0,'',1).
  732: 	    '</td></tr>'.$/;
  733:     }
  734:     $scrout.="</table>\n";
  735:     $scrout.=<<ENDDOCUMENT;
  736: $advanced_buttons
  737: </form>
  738: </body>
  739: </html>
  740: ENDDOCUMENT
  741:     $r->print($scrout);
  742:     return;
  743: }
  744: 
  745: ######################################################################
  746: ######################################################################
  747: 
  748: =pod 
  749: 
  750: =item &titlefield()
  751: 
  752: Inputs: title text
  753: 
  754: Outputs: titletext with font wrapper
  755: 
  756: =cut
  757: 
  758: ######################################################################
  759: ######################################################################
  760: sub titlefield {
  761:     my $title=shift;
  762:     return $title;
  763: }
  764: 
  765: ######################################################################
  766: ######################################################################
  767: 
  768: =pod 
  769: 
  770: =item viewoptiontext()
  771: 
  772: Inputs: codename for view option
  773: 
  774: Outputs: displayed text
  775: 
  776: =cut
  777: 
  778: ######################################################################
  779: ######################################################################
  780: sub viewoptiontext {
  781:     my $code=shift;
  782:     my %desc=&Apache::lonlocal::texthash
  783:         ('detailed' => "Detailed Citation View",
  784:          'xml' => 'XML/SGML',
  785:          'compact' => 'Compact View',
  786:          'fielded' => 'Fielded Format',
  787:          'summary' => 'Summary View');
  788:     return $desc{$code};
  789: }
  790: 
  791: ######################################################################
  792: ######################################################################
  793: 
  794: =pod 
  795: 
  796: =item viewoptions()
  797: 
  798: Inputs: none
  799: 
  800: Outputs: text for box with view options
  801: 
  802: =cut
  803: 
  804: ######################################################################
  805: ######################################################################
  806: sub viewoptions {
  807:     my $scrout="\n".'<nobr>';
  808:     if (! defined($ENV{'form.viewselect'})) { 
  809:         $ENV{'form.viewselect'}='detailed'; 
  810:     }
  811:     $scrout.=&Apache::lonmeta::selectbox('viewselect',
  812: 			$ENV{'form.viewselect'},
  813: 			\&viewoptiontext,
  814: 			sort(keys(%Views)));
  815:     $scrout.= '&nbsp;&nbsp;';
  816:     $scrout.=&Apache::lonmeta::selectbox('show',
  817: 			$ENV{'form.show'},
  818: 			undef,
  819: 			(10,20,50,100,1000,10000));
  820:     $scrout .= ('&nbsp;'x2).&mt('Records per Page').'</nobr>'.$/;
  821:     return $scrout;
  822: }
  823: 
  824: ######################################################################
  825: ######################################################################
  826: 
  827: =pod 
  828: 
  829: =item searchhelp()
  830: 
  831: Inputs: none
  832: 
  833: Outputs: return little blurb on how to enter searches
  834: 
  835: =cut
  836: 
  837: ######################################################################
  838: ######################################################################
  839: sub searchhelp {
  840:     return &mt('Enter terms or phrases separated by AND, OR, or NOT');
  841: }
  842: 
  843: ######################################################################
  844: ######################################################################
  845: 
  846: =pod 
  847: 
  848: =item &get_persistent_form_data()
  849: 
  850: Inputs: filename of database
  851: 
  852: Outputs: returns undef on database errors.
  853: 
  854: This function is the reverse of &make_persistent() for form data.
  855: Retrieve persistent data from %persistent_db.  Retrieved items will have their
  856: values unescaped.  If a form value already exists in $ENV, it will not be
  857: overwritten.  Form values that are array references may have values appended
  858: to them.
  859: 
  860: =cut
  861: 
  862: ######################################################################
  863: ######################################################################
  864: sub get_persistent_form_data {
  865:     my $filename = shift;
  866:     return 0 if (! -e $filename);
  867:     return undef if (! tie(%persistent_db,'GDBM_File',$filename,
  868:                            &GDBM_READER(),0640));
  869:     #
  870:     # These make sure we do not get array references printed out as 'values'.
  871:     my %arrays_allowed = ('form.domains'=>1);
  872:     #
  873:     # Loop through the keys, looking for 'form.'
  874:     foreach my $name (keys(%persistent_db)) {
  875:         next if ($name !~ /^form./);
  876:         # Kludgification begins!
  877:         if ($name eq 'form.domains' && 
  878:             $ENV{'form.searchmode'} eq 'basic' &&
  879:             $ENV{'form.phase'} ne 'disp_basic') {
  880:             next;
  881:         }
  882:         # End kludge (hopefully)
  883:         next if (exists($ENV{$name}));
  884:         my @values = map { 
  885:             &Apache::lonnet::unescape($_);
  886:         } split(',',$persistent_db{$name});
  887:         next if (@values <1);
  888:         if ($arrays_allowed{$name}) {
  889:             $ENV{$name} = [@values];
  890:         } else {
  891:             $ENV{$name} = $values[0] if ($values[0]);
  892:         }
  893:     }
  894:     untie (%persistent_db);
  895:     return 1;
  896: }
  897: 
  898: ######################################################################
  899: ######################################################################
  900: 
  901: =pod 
  902: 
  903: =item &get_persistent_data()
  904: 
  905: Inputs: filename of database, ref to array of values to recover.
  906: 
  907: Outputs: array of values.  Returns undef on error.
  908: 
  909: This function is the reverse of &make_persistent();
  910: Retrieve persistent data from %persistent_db.  Retrieved items will have their
  911: values unescaped.  If the item contains commas (before unescaping), the
  912: returned value will be an array pointer. 
  913: 
  914: =cut
  915: 
  916: ######################################################################
  917: ######################################################################
  918: sub get_persistent_data {
  919:     my $filename = shift;
  920:     my @Vars = @{shift()};
  921:     my @Values;   # Return array
  922:     return undef if (! -e $filename);
  923:     return undef if (! tie(%persistent_db,'GDBM_File',$filename,
  924:                            &GDBM_READER(),0640));
  925:     foreach my $name (@Vars) {
  926:         if (! exists($persistent_db{$name})) {
  927:             push @Values, undef;
  928:             next;
  929:         }
  930:         my @values = map { 
  931:             &Apache::lonnet::unescape($_);
  932:         } split(',',$persistent_db{$name});
  933:         if (@values <= 1) {
  934:             push @Values,$values[0];
  935:         } else {
  936:             push @Values,\@values;
  937:         }
  938:     }
  939:     untie (%persistent_db);
  940:     return @Values;
  941: }
  942: 
  943: ######################################################################
  944: ######################################################################
  945: 
  946: =pod 
  947: 
  948: =item &make_persistent() 
  949: 
  950: Inputs: Hash of values to save, filename of persistent database.
  951: 
  952: Store variables away to the %persistent_db.
  953: Values will be escaped.  Values that are array pointers will have their
  954: elements escaped and concatenated in a comma separated string.  
  955: 
  956: =cut
  957: 
  958: ######################################################################
  959: ######################################################################
  960: sub make_persistent {
  961:     my %save = %{shift()};
  962:     my $filename = shift;
  963:     return undef if (! tie(%persistent_db,'GDBM_File',
  964:                            $filename,&GDBM_WRCREAT(),0640));
  965:     foreach my $name (keys(%save)) {
  966:         my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name}));
  967:         # We handle array references, but not recursively.
  968:         my $store = join(',', map { &Apache::lonnet::escape($_); } @values );
  969:         $persistent_db{$name} = $store;
  970:     }
  971:     untie(%persistent_db);
  972:     return 1;
  973: }
  974: 
  975: ######################################################################
  976: ######################################################################
  977: 
  978: =pod 
  979: 
  980: =item &make_form_data_persistent() 
  981: 
  982: Inputs: filename of persistent database.
  983: 
  984: Store most form variables away to the %persistent_db.
  985: Values will be escaped.  Values that are array pointers will have their
  986: elements escaped and concatenated in a comma separated string.  
  987: 
  988: =cut
  989: 
  990: ######################################################################
  991: ######################################################################
  992: sub make_form_data_persistent {
  993:     my $r = shift;
  994:     my $filename = shift;
  995:     my %save;
  996:     foreach (keys(%ENV)) {
  997:         next if (!/^form/ || /submit/);
  998:         $save{$_} = $ENV{$_};
  999:     }
 1000:     return &make_persistent(\%save,$filename);
 1001: }
 1002: 
 1003: ######################################################################
 1004: ######################################################################
 1005: 
 1006: =pod 
 1007: 
 1008: =item &parse_advanced_search()
 1009: 
 1010: Parse advanced search form and return the following:
 1011: 
 1012: =over 4
 1013: 
 1014: =item $query Scalar containing an SQL query.
 1015: 
 1016: =item $customquery Scalar containing a custom query.
 1017: 
 1018: =item $customshow Scalar containing commands to show custom metadata.
 1019: 
 1020: =item $libraries_to_query Reference to array of domains to search.
 1021: 
 1022: =back
 1023: 
 1024: =cut
 1025: 
 1026: ######################################################################
 1027: ######################################################################
 1028: sub parse_advanced_search {
 1029:     my ($r,$closebutton,$hidden_fields)=@_;
 1030:     my $fillflag=0;
 1031:     my $pretty_search_string = "<br />\n";
 1032:     # Clean up fields for safety
 1033:     for my $field ('title','author','subject','keywords','url','version',
 1034: 		   'creationdatestart_month','creationdatestart_day',
 1035: 		   'creationdatestart_year','creationdateend_month',
 1036: 		   'creationdateend_day','creationdateend_year',
 1037: 		   'lastrevisiondatestart_month','lastrevisiondatestart_day',
 1038: 		   'lastrevisiondatestart_year','lastrevisiondateend_month',
 1039: 		   'lastrevisiondateend_day','lastrevisiondateend_year',
 1040: 		   'notes','abstract','extension','language','owner',
 1041: 		   'custommetadata','customshow','category') {
 1042: 	$ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
 1043:     }
 1044:     foreach ('mode','form','element') {
 1045: 	# is this required?  Hmmm.
 1046: 	next unless (exists($ENV{"form.$_"}));
 1047: 	$ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
 1048: 	$ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
 1049:     }
 1050:     # Preprocess the category form element.
 1051:     $ENV{'form.category'} = 'any' if (! defined($ENV{'form.category'}) ||
 1052:                                       ref($ENV{'form.category'}));
 1053:     #
 1054:     # Check to see if enough information was filled in
 1055:     for my $field ('title','author','subject','keywords','url','version',
 1056: 		   'notes','abstract','category','extension','language',
 1057:                    'owner','custommetadata') {
 1058: 	if (&filled($ENV{"form.$field"})) {
 1059: 	    $fillflag++;
 1060: 	}
 1061:     }
 1062:     unless ($fillflag) {
 1063: 	&output_blank_field_error($r,$closebutton,
 1064:                                   'phase=disp_adv',$hidden_fields);
 1065: 	return ;
 1066:     }
 1067:     # Turn the form input into a SQL-based query
 1068:     my $query='';
 1069:     my @queries;
 1070:     my $font = '<font color="#800000" face="helvetica">';
 1071:     # Evaluate logical expression AND/OR/NOT phrase fields.
 1072:     foreach my $field ('title','author','subject','notes','abstract','url',
 1073: 		       'keywords','version','owner','standards') {
 1074: 	if ($ENV{'form.'.$field}) {
 1075:             my $searchphrase = $ENV{'form.'.$field};
 1076:             $pretty_search_string .= $font."$field</font> contains <b>".
 1077:                 $searchphrase."</b>";
 1078:             if ($ENV{'form.'.$field.'_related'}) {
 1079:                 my @New_Words;
 1080:                 ($searchphrase,@New_Words) = &related_version($searchphrase);
 1081:                 if (@New_Words) {
 1082:                     $pretty_search_string .= " with related words: ".
 1083:                         "<b>@New_Words</b>.";
 1084:                 } else {
 1085:                     $pretty_search_string .= " with no related words.";
 1086:                 }
 1087:             }
 1088:             $pretty_search_string .= "<br />\n";
 1089: 	    push @queries,&build_SQL_query($field,$searchphrase);
 1090:         }
 1091:     }
 1092:     #
 1093:     # Make the 'mime' from 'form.category' and 'form.extension'
 1094:     #
 1095:     my $searchphrase;
 1096:     if (exists($ENV{'form.category'})    && 
 1097:         $ENV{'form.category'} !~ /^\s*$/ &&
 1098:         $ENV{'form.category'} ne 'any')     {
 1099:         my @extensions = &Apache::loncommon::filecategorytypes
 1100:                                                    ($ENV{'form.category'});
 1101:         if (scalar(@extensions) > 0) {
 1102:             $searchphrase = join(' OR ',@extensions);
 1103:         }
 1104:     }
 1105:     if (exists($ENV{'form.extension'}) && $ENV{'form.extension'} !~ /^\s*$/) {
 1106:         $searchphrase .= ' OR ' if (defined($searchphrase));
 1107:         my @extensions = split(/,/,$ENV{'form.extension'});
 1108:         $searchphrase .= join(' OR ',@extensions);
 1109:     }
 1110:     if (defined($searchphrase)) {
 1111:         push @queries,&build_SQL_query('mime',$searchphrase);
 1112:         $pretty_search_string .=$font.'mime</font> contains <b>'.
 1113:             $searchphrase.'</b><br />';
 1114:     }
 1115:     #
 1116:     # Evaluate option lists
 1117:     if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') {
 1118: 	push @queries,"(language like \"$ENV{'form.language'}\")";
 1119:         $pretty_search_string.=$font."language</font>= ".
 1120:             &Apache::loncommon::languagedescription($ENV{'form.language'}).
 1121:                 "<br />\n";
 1122:     }
 1123:     if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') {
 1124: 	push @queries,"(copyright like \"$ENV{'form.copyright'}\")";
 1125:         $pretty_search_string.=$font."copyright</font> = ".
 1126:             &Apache::loncommon::copyrightdescription($ENV{'form.copyright'}).
 1127:                 "<br \>\n";
 1128:     }
 1129:     #
 1130:     # Evaluate date windows
 1131:     my $datequery=&build_date_queries(
 1132: 			$ENV{'form.creationdatestart_month'},
 1133: 			$ENV{'form.creationdatestart_day'},
 1134: 			$ENV{'form.creationdatestart_year'},
 1135: 			$ENV{'form.creationdateend_month'},
 1136: 			$ENV{'form.creationdateend_day'},
 1137: 			$ENV{'form.creationdateend_year'},
 1138: 			$ENV{'form.lastrevisiondatestart_month'},
 1139: 			$ENV{'form.lastrevisiondatestart_day'},
 1140: 			$ENV{'form.lastrevisiondatestart_year'},
 1141: 			$ENV{'form.lastrevisiondateend_month'},
 1142: 			$ENV{'form.lastrevisiondateend_day'},
 1143: 			$ENV{'form.lastrevisiondateend_year'},
 1144: 			);
 1145:     # Test to see if date windows are legitimate
 1146:     if ($datequery=~/^Incorrect/) {
 1147: 	&output_date_error($r,$datequery,$closebutton,$hidden_fields);
 1148: 	return ;
 1149:     } elsif ($datequery) {
 1150:         # Here is where you would set up pretty_search_string to output
 1151:         # date query information.
 1152: 	push @queries,$datequery;
 1153:     }
 1154:     #
 1155:     # Process form information for custom metadata querying
 1156:     my $customquery=undef;
 1157:     ##
 1158:     ## The custom metadata search was removed q long time ago mostly 
 1159:     ## because I was unable to figureout exactly how it worked and could
 1160:     ## not imagine people actually using it.  MH
 1161:     ##
 1162:     # if ($ENV{'form.custommetadata'}) {
 1163:     #    $pretty_search_string .=$font."Custom Metadata Search</font>: <b>".
 1164:     #    $ENV{'form.custommetadata'}."</b><br />\n";
 1165:     #    $customquery=&build_custommetadata_query('custommetadata',
 1166:     #                                             $ENV{'form.custommetadata'});
 1167:     # }
 1168:     my $customshow=undef;
 1169:     # if ($ENV{'form.customshow'}) {
 1170:     # $pretty_search_string .=$font."Custom Metadata Display</font>: <b>".
 1171:     #                         $ENV{'form.customshow'}."</b><br />\n";
 1172:     #    $customshow=$ENV{'form.customshow'};
 1173:     #    $customshow=~s/[^\w\s]//g;
 1174:     #    my @fields=split(/\s+/,$customshow);
 1175:     #    $customshow=join(" ",@fields);
 1176:     # }
 1177:     ##
 1178:     ## Deal with restrictions to given domains
 1179:     ## 
 1180:     my ($libraries_to_query,$pretty_domains_string) = 
 1181:         &parse_domain_restrictions();
 1182:     $pretty_search_string .= $pretty_domains_string."<br />\n";
 1183:     #
 1184:     if (@queries) {
 1185: 	$query=join(" AND ",@queries);
 1186: 	$query="select * from metadata where $query";
 1187:     } elsif ($customquery) {
 1188:         $query = '';
 1189:     }
 1190:     return ($query,$customquery,$customshow,$libraries_to_query,
 1191:             $pretty_search_string);
 1192: }
 1193: 
 1194: sub parse_domain_restrictions {
 1195:     my $libraries_to_query = undef;
 1196:     # $ENV{'form.domains'} can be either a scalar or an array reference.
 1197:     # We need an array.
 1198:     if (! exists($ENV{'form.domains'})) {
 1199:         return (undef,'');
 1200:     }
 1201:     my @allowed_domains;
 1202:     if (ref($ENV{'form.domains'})) {
 1203:         @allowed_domains =  @{$ENV{'form.domains'}};
 1204:     } else {
 1205:         @allowed_domains = ($ENV{'form.domains'});
 1206:     }
 1207:     #
 1208:     my %domain_hash = ();
 1209:     my $pretty_domains_string;
 1210:     foreach (@allowed_domains) {
 1211:         $domain_hash{$_}++;
 1212:     }
 1213:     if ($domain_hash{'any'}) {
 1214:         $pretty_domains_string = "In all LON-CAPA domains.";
 1215:     } else {
 1216:         if (@allowed_domains > 1) {
 1217:             $pretty_domains_string = "In LON-CAPA domains:";
 1218:         } else {
 1219:             $pretty_domains_string = "In LON-CAPA domain ";
 1220:         }
 1221:         foreach (sort @allowed_domains) {
 1222:             $pretty_domains_string .= "<b>".$_."</b> ";
 1223:         }
 1224:         foreach (keys(%Apache::lonnet::libserv)) {
 1225:             if (exists($domain_hash{$Apache::lonnet::hostdom{$_}})) {
 1226:                 push @$libraries_to_query,$_;
 1227:             }
 1228:         }
 1229:     }
 1230:     return ($libraries_to_query,$pretty_domains_string);
 1231: }
 1232: 
 1233: ######################################################################
 1234: ######################################################################
 1235: 
 1236: =pod 
 1237: 
 1238: =item &parse_basic_search() 
 1239: 
 1240: Parse the basic search form and return a scalar containing an sql query.
 1241: 
 1242: =cut
 1243: 
 1244: ######################################################################
 1245: ######################################################################
 1246: sub parse_basic_search {
 1247:     my ($r,$closebutton)=@_;
 1248:     #
 1249:     # Clean up fields for safety
 1250:     for my $field ('basicexp') {
 1251: 	$ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g;
 1252:     }
 1253:     foreach ('mode','form','element') {
 1254: 	# is this required?  Hmmm.
 1255: 	next unless (exists($ENV{"form.$_"}));
 1256: 	$ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
 1257: 	$ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
 1258:     }
 1259:     my ($libraries_to_query,$pretty_domains_string) = 
 1260:         &parse_domain_restrictions();
 1261:     #
 1262:     # Check to see if enough of a query is filled in
 1263:     unless (&filled($ENV{'form.basicexp'})) {
 1264: 	&output_blank_field_error($r,$closebutton,'phase=disp_basic');
 1265: 	return OK;
 1266:     }
 1267:     my $pretty_search_string = '<b>'.$ENV{'form.basicexp'}.'</b>';
 1268:     my $search_string = $ENV{'form.basicexp'};
 1269:     if ($ENV{'form.related'}) {
 1270:         my @New_Words;
 1271:         ($search_string,@New_Words) = &related_version($ENV{'form.basicexp'});
 1272:         if (@New_Words) {
 1273:             $pretty_search_string .= " with related words: <b>@New_Words</b>.";
 1274:         } else {
 1275:             $pretty_search_string .= " with no related words.";
 1276:         }
 1277:     }
 1278:     #
 1279:     # Build SQL query string based on form page
 1280:     my $query='';
 1281:     my $concatarg=join(',',
 1282: 		       ('title', 'author', 'subject', 'notes', 'abstract',
 1283:                         'keywords'));
 1284:     $concatarg='title' if $ENV{'form.titleonly'};
 1285:     $query=&build_SQL_query('concat_ws(" ",'.$concatarg.')',$search_string);
 1286:     if (defined($pretty_domains_string) && $pretty_domains_string ne '') {
 1287:         $pretty_search_string .= ' '.$pretty_domains_string;
 1288:     }
 1289:     $pretty_search_string .= "<br />\n";
 1290:     my $final_query = 'SELECT * FROM metadata WHERE '.$query;
 1291:     # &Apache::lonnet::logthis($final_query);
 1292:     return ($final_query,$pretty_search_string,
 1293:             $libraries_to_query);
 1294: }
 1295: 
 1296: ######################################################################
 1297: ######################################################################
 1298: 
 1299: =pod 
 1300: 
 1301: =item &related_version()
 1302: 
 1303: Modifies an input string to include related words.  Words in the string
 1304: are replaced with parenthesized lists of 'OR'd words.  For example
 1305: "torque" is replaced with "(torque OR word1 OR word2 OR ...)".  
 1306: 
 1307: Note: Using this twice on a string is probably silly.
 1308: 
 1309: =cut
 1310: 
 1311: ######################################################################
 1312: ######################################################################
 1313: sub related_version {
 1314:     my $search_string = shift;
 1315:     my $result = $search_string;
 1316:     my %New_Words = ();
 1317:     while ($search_string =~ /(\w+)/cg) {
 1318:         my $word = $1;
 1319:         next if (lc($word) =~ /\b(or|and|not)\b/);
 1320:         my @Words = &Apache::loncommon::get_related_words($word);
 1321:         @Words = ($#Words>4? @Words[0..4] : @Words);
 1322:         foreach (@Words) { $New_Words{$_}++;}
 1323:         my $replacement = join " OR ", ($word,@Words);
 1324:         $result =~ s/(\b)$word(\b)/$1($replacement)$2/g;
 1325:     }
 1326:     return $result,sort(keys(%New_Words));
 1327: }
 1328: 
 1329: ######################################################################
 1330: ######################################################################
 1331: 
 1332: =pod 
 1333: 
 1334: =item &build_SQL_query() 
 1335: 
 1336: Builds a SQL query string from a logical expression with AND/OR keywords
 1337: using Text::Query and &recursive_SQL_query_builder()
 1338: 
 1339: =cut
 1340: 
 1341: ######################################################################
 1342: ######################################################################
 1343: sub build_SQL_query {
 1344:     my ($field_name,$logic_statement)=@_;
 1345:     my $q=new Text::Query('abc',
 1346: 			  -parse => 'Text::Query::ParseAdvanced',
 1347: 			  -build => 'Text::Query::Build');
 1348:     $q->prepare($logic_statement);
 1349:     my $matchexp=${$q}{'matchexp'}; chomp $matchexp;
 1350:     my $sql_query=&recursive_SQL_query_build($field_name,$matchexp);
 1351:     return $sql_query;
 1352: }
 1353: 
 1354: ######################################################################
 1355: ######################################################################
 1356: 
 1357: =pod 
 1358: 
 1359: =item &build_custommetadata_query() 
 1360: 
 1361: Constructs a custom metadata query using a rather heinous regular
 1362: expression.
 1363: 
 1364: =cut
 1365: 
 1366: ######################################################################
 1367: ######################################################################
 1368: sub build_custommetadata_query {
 1369:     my ($field_name,$logic_statement)=@_;
 1370:     my $q=new Text::Query('abc',
 1371: 			  -parse => 'Text::Query::ParseAdvanced',
 1372: 			  -build => 'Text::Query::BuildAdvancedString');
 1373:     $q->prepare($logic_statement);
 1374:     my $matchexp=${$q}{'-parse'}{'-build'}{'matchstring'};
 1375:     # quick fix to change literal into xml tag-matching
 1376:     # will eventually have to write a separate builder module
 1377:     # wordone=wordtwo becomes\<wordone\>[^\<] *wordtwo[^\<]*\<\/wordone\>
 1378:     $matchexp =~ s/(\w+)\\=([\w\\\+]+)?# wordone=wordtwo is changed to 
 1379:                  /\\<$1\\>?#           \<wordone\>
 1380:                    \[\^\\<\]?#        [^\<]         
 1381:                    \*$2\[\^\\<\]?#           *wordtwo[^\<]
 1382:                    \*\\<\\\/$1\\>?#                        *\<\/wordone\>
 1383:                    /g;
 1384:     return $matchexp;
 1385: }
 1386: 
 1387: ######################################################################
 1388: ######################################################################
 1389: 
 1390: =pod 
 1391: 
 1392: =item &recursive_SQL_query_build() 
 1393: 
 1394: Recursively constructs an SQL query.  Takes as input $dkey and $pattern.
 1395: 
 1396: =cut
 1397: 
 1398: ######################################################################
 1399: ######################################################################
 1400: sub recursive_SQL_query_build {
 1401:     my ($dkey,$pattern)=@_;
 1402:     my @matches=($pattern=~/(\[[^\]|\[]*\])/g);
 1403:     return $pattern unless @matches;
 1404:     foreach my $match (@matches) {
 1405:         $match=~/\[ (\w+)\s(.*) \]/;
 1406:         my ($key,$value)=($1,$2);
 1407:         my $replacement='';
 1408:         if ($key eq 'literal') {
 1409:             $replacement="($dkey LIKE \"\%$value\%\")";
 1410:         } elsif (lc($key) eq 'not') {
 1411:             $value=~s/LIKE/NOT LIKE/;
 1412: #          $replacement="($dkey not like $value)";
 1413:             $replacement="$value";
 1414:         } elsif ($key eq 'and') {
 1415:             $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
 1416:             $replacement="($1 AND $2)";
 1417: 	} elsif ($key eq 'or') {
 1418:             $value=~/(.*[\"|\)]) ([|\(|\^].*)/;
 1419:             $replacement="($1 OR $2)";
 1420: 	}
 1421: 	substr($pattern,
 1422:                index($pattern,$match),
 1423:                length($match),
 1424:                $replacement);
 1425:     }
 1426:     &recursive_SQL_query_build($dkey,$pattern);
 1427: }
 1428: 
 1429: ######################################################################
 1430: ######################################################################
 1431: 
 1432: =pod 
 1433: 
 1434: =item &build_date_queries() 
 1435: 
 1436: Builds a SQL logic query to check time/date entries.
 1437: Also reports errors (check for /^Incorrect/).
 1438: 
 1439: =cut
 1440: 
 1441: ######################################################################
 1442: ######################################################################
 1443: sub build_date_queries {
 1444:     my ($cmonth1,$cday1,$cyear1,$cmonth2,$cday2,$cyear2,
 1445: 	$lmonth1,$lday1,$lyear1,$lmonth2,$lday2,$lyear2)=@_;
 1446:     my @queries;
 1447:     if ($cmonth1 or $cday1 or $cyear1 or $cmonth2 or $cday2 or $cyear2) {
 1448: 	unless ($cmonth1 and $cday1 and $cyear1 and
 1449: 		$cmonth2 and $cday2 and $cyear2) {
 1450: 	    return "Incorrect entry for the creation date.  You must specify ".
 1451: 		   "a starting month, day, and year and an ending month, ".
 1452: 		   "day, and year.";
 1453: 	}
 1454: 	my $cnumeric1=sprintf("%d%2d%2d",$cyear1,$cmonth1,$cday1);
 1455: 	$cnumeric1+=0;
 1456: 	my $cnumeric2=sprintf("%d%2d%2d",$cyear2,$cmonth2,$cday2);
 1457: 	$cnumeric2+=0;
 1458: 	if ($cnumeric1>$cnumeric2) {
 1459: 	    return "Incorrect entry for the creation date.  The starting ".
 1460: 		   "date must occur before the ending date.";
 1461: 	}
 1462: 	my $cquery="(creationdate BETWEEN '$cyear1-$cmonth1-$cday1' AND '".
 1463: 	           "$cyear2-$cmonth2-$cday2 23:59:59')";
 1464: 	push @queries,$cquery;
 1465:     }
 1466:     if ($lmonth1 or $lday1 or $lyear1 or $lmonth2 or $lday2 or $lyear2) {
 1467: 	unless ($lmonth1 and $lday1 and $lyear1 and
 1468: 		$lmonth2 and $lday2 and $lyear2) {
 1469: 	    return "Incorrect entry for the last revision date.  You must ".
 1470: 		   "specify a starting month, day, and year and an ending ".
 1471: 		   "month, day, and year.";
 1472: 	}
 1473: 	my $lnumeric1=sprintf("%d%2d%2d",$lyear1,$lmonth1,$lday1);
 1474: 	$lnumeric1+=0;
 1475: 	my $lnumeric2=sprintf("%d%2d%2d",$lyear2,$lmonth2,$lday2);
 1476: 	$lnumeric2+=0;
 1477: 	if ($lnumeric1>$lnumeric2) {
 1478: 	    return "Incorrect entry for the last revision date.  The ".
 1479: 		   "starting date must occur before the ending date.";
 1480: 	}
 1481: 	my $lquery="(lastrevisiondate BETWEEN '$lyear1-$lmonth1-$lday1' AND '".
 1482: 	           "$lyear2-$lmonth2-$lday2 23:59:59')";
 1483: 	push @queries,$lquery;
 1484:     }
 1485:     if (@queries) {
 1486: 	return join(" AND ",@queries);
 1487:     }
 1488:     return '';
 1489: }
 1490: 
 1491: ######################################################################
 1492: ######################################################################
 1493: 
 1494: =pod
 1495: 
 1496: =item &copyright_check()
 1497: 
 1498: Inputs: $Metadata, a hash pointer of metadata for a resource.
 1499: 
 1500: Returns: 1 if the resource is available to the user making the query, 
 1501:          0 otherwise.
 1502: 
 1503: =cut
 1504: 
 1505: ######################################################################
 1506: ######################################################################
 1507: sub copyright_check {
 1508:     my $Metadata = shift;
 1509:     # Check copyright tags and skip results the user cannot use
 1510:     my (undef,undef,$resdom,$resname) = split('/',
 1511:                                               $Metadata->{'url'});
 1512:     # Check for priv
 1513:     if (($Metadata->{'copyright'} eq 'priv') && 
 1514:         (($ENV{'user.name'} ne $resname) &&
 1515:          ($ENV{'user.domain'} ne $resdom))) {
 1516:         return 0;
 1517:     }
 1518:     # Check for domain
 1519:     if (($Metadata->{'copyright'} eq 'domain') &&
 1520:         ($ENV{'user.domain'} ne $resdom)) {
 1521:         return 0;
 1522:     }
 1523:     return 1;
 1524: }
 1525: 
 1526: ######################################################################
 1527: ######################################################################
 1528: 
 1529: =pod
 1530: 
 1531: =item &ensure_db_and_table()
 1532: 
 1533: Ensure we can get lonmysql to connect to the database and the table we
 1534: need exists.
 1535: 
 1536: Inputs: $r, table id
 1537: 
 1538: Returns: undef on error, 1 if the table exists.
 1539: 
 1540: =cut
 1541: 
 1542: ######################################################################
 1543: ######################################################################
 1544: sub ensure_db_and_table {
 1545:     my ($r,$table) = @_;
 1546:     ##
 1547:     ## Sanity check the table id.
 1548:     ##
 1549:     if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
 1550:         $r->print("Unable to retrieve search results.  ".
 1551:                   "Unable to determine the table results were stored in.  ".
 1552:                   "</body></html>");
 1553:         return undef;
 1554:     }
 1555:     ##
 1556:     ## Make sure we can connect and the table exists.
 1557:     ##
 1558:     my $connection_result = &Apache::lonmysql::connect_to_db();
 1559:     if (!defined($connection_result)) {
 1560:         $r->print("Unable to connect to the MySQL database where your results".
 1561:                   " are stored. </body></html>");
 1562:         &Apache::lonnet::logthis("lonsearchcat: unable to get lonmysql to".
 1563:                                  " connect to database.");
 1564:         &Apache::lonnet::logthis(&Apache::lonmysql::get_error());
 1565:         return undef;
 1566:     }
 1567:     my $table_check = &Apache::lonmysql::check_table($table);
 1568:     if (! defined($table_check)) {
 1569:         $r->print("A MySQL error has occurred.</form></body></html>");
 1570:         &Apache::lonnet::logthis("lonmysql was unable to determine the status".
 1571:                                  " of table ".$table);
 1572:         return undef;
 1573:     } elsif (! $table_check) {
 1574:         $r->print("The table of results could not be found.");
 1575:         &Apache::lonnet::logthis("The user requested a table, ".$table.
 1576:                                  ", that could not be found.");
 1577:         return undef;
 1578:     }
 1579:     return 1;
 1580: }
 1581: 
 1582: ######################################################################
 1583: ######################################################################
 1584: 
 1585: =pod
 1586: 
 1587: =item &print_sort_form()
 1588: 
 1589: The sort feature is not implemented at this time.  This form just prints 
 1590: a link to change the search query.
 1591: 
 1592: =cut
 1593: 
 1594: ######################################################################
 1595: ######################################################################
 1596: sub print_sort_form {
 1597:     my ($r,$pretty_query_string) = @_;
 1598:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 1599:     ##
 1600:     my %SortableFields=&Apache::lonlocal::texthash( 
 1601:          id        => 'Default',
 1602:          title     => 'Title',
 1603:          author    => 'Author',
 1604:          subject   => 'Subject',
 1605:          url       => 'URL',
 1606:          version   => 'Version Number',
 1607:          mime      => 'Mime type',
 1608:          lang      => 'Language',
 1609:          owner     => 'Owner/Publisher',
 1610:          copyright => 'Copyright',
 1611:          hostname  => 'Host',
 1612:          creationdate     => 'Creation Date',
 1613:          lastrevisiondate => 'Revision Date'
 1614:      );
 1615:     ##
 1616:     my $table = $ENV{'form.table'};
 1617:     return if (! &ensure_db_and_table($r,$table));
 1618:     ##
 1619:     ## Get the number of results 
 1620:     ##
 1621:     my $total_results = &Apache::lonmysql::number_of_rows($table);
 1622:     if (! defined($total_results)) {
 1623:         $r->print("A MySQL error has occurred.</form></body></html>");
 1624:         &Apache::lonnet::logthis("lonmysql was unable to determine the number".
 1625:                                  " of rows in table ".$table);
 1626:         &Apache::lonnet::logthis(&Apache::lonmysql::get_error());
 1627:         return;
 1628:     }
 1629:     my $result;
 1630:     $result.=<<END;
 1631: <html>
 1632: <head>
 1633: <script>
 1634:     function change_sort() {
 1635:         var newloc = "/adm/searchcat?phase=results";
 1636:         newloc += "&persistent_db_id=$ENV{'form.persistent_db_id'}";
 1637:         newloc += "&sortby=";
 1638:         newloc += document.forms.statusform.elements.sortby.value;
 1639:         parent.resultsframe.location= newloc;
 1640:     }
 1641: </script>
 1642: <title>Results</title>
 1643: </head>
 1644: $bodytag
 1645: <form name="statusform" action="" method="post">
 1646: <input type="hidden" name="Queue" value="" />
 1647: END
 1648: 
 1649: #<h2>Sort Results</h2>
 1650: #Sort by: <select size="1" name="sortby" onchange="javascript:change_sort();">
 1651: #    $ENV{'form.sortby'} = 'id' if (! defined($ENV{'form.sortby'}));
 1652: #    foreach (keys(%SortableFields)) {
 1653: #        $result.="<option name=\"$_\"";
 1654: #        if ($_ eq $ENV{'form.sortby'}) {
 1655: #            $result.=" selected ";
 1656: #        }
 1657: #        $result.=" >$SortableFields{$_}</option>\n";
 1658: #    }
 1659: #    $result.="</select>\n";
 1660:     my $revise = &revise_button();
 1661:     $result.=<<END;
 1662: <p>
 1663: There are $total_results matches to your query. $revise
 1664: </p><p>
 1665: Search:$pretty_query_string
 1666: </p>
 1667: </form>
 1668: </body>
 1669: </html>
 1670: END
 1671:     $r->print($result);
 1672:     return;
 1673: }
 1674: 
 1675: #####################################################################
 1676: #####################################################################
 1677: 
 1678: =pod
 1679: 
 1680: =item MySQL Table Description
 1681: 
 1682: MySQL table creation requires a precise description of the data to be
 1683: stored.  The use of the correct types to hold data is vital to efficient
 1684: storage and quick retrieval of records.  The columns must be described in
 1685: the following format:
 1686: 
 1687: =cut
 1688: 
 1689: #####################################################################
 1690: #####################################################################
 1691: #
 1692: # These should probably be scoped but I don't have time right now...
 1693: #
 1694: my @Datatypes;
 1695: my @Fullindicies;
 1696:     
 1697: ######################################################################
 1698: ######################################################################
 1699: 
 1700: =pod
 1701: 
 1702: =item &create_results_table()
 1703: 
 1704: Creates the table of search results by calling lonmysql.  Stores the
 1705: table id in $ENV{'form.table'}
 1706: 
 1707: Inputs: none.
 1708: 
 1709: Returns: the identifier of the table on success, undef on error.
 1710: 
 1711: =cut
 1712: 
 1713: ######################################################################
 1714: ######################################################################
 1715: sub set_up_table_structure {
 1716:     my ($datatypes,$fullindicies) = 
 1717:         &LONCAPA::lonmetadata::describe_metadata_storage();
 1718:     unshift(@$datatypes,{name => 'id', 
 1719:         type => 'MEDIUMINT',
 1720:         restrictions => 'UNSIGNED NOT NULL',
 1721:         primary_key  => 'yes',
 1722:         auto_inc     => 'yes' });
 1723:     @Datatypes = @{$datatypes};
 1724:     @Fullindicies = @{$fullindicies};
 1725:     return;
 1726: }
 1727: 
 1728: sub create_results_table {
 1729:     &set_up_table_structure();
 1730:     my $table = &Apache::lonmysql::create_table
 1731:         ( { columns => \@Datatypes,
 1732:             FULLTEXT => [{'columns' => \@Fullindicies},],
 1733:         } );
 1734:     if (defined($table)) {
 1735:         $ENV{'form.table'} = $table;
 1736:         return $table;
 1737:     } 
 1738:     return undef; # Error...
 1739: }
 1740: 
 1741: ######################################################################
 1742: ######################################################################
 1743: 
 1744: =pod
 1745: 
 1746: =item Search Status update functions
 1747: 
 1748: Each of the following functions changes the values of one of the
 1749: input fields used to display the search status to the user.  The names
 1750: should be explanatory.
 1751: 
 1752: Inputs: Apache request handler ($r), text to display.
 1753: 
 1754: Returns: Nothing.
 1755: 
 1756: =over 4
 1757: 
 1758: =item &update_count_status()
 1759: 
 1760: =item &update_status()
 1761: 
 1762: =item &update_seconds()
 1763: 
 1764: =back
 1765: 
 1766: =cut
 1767: 
 1768: ######################################################################
 1769: ######################################################################
 1770: sub update_count_status {
 1771:     my ($r,$text) = @_;
 1772:     $text =~ s/\'/\\\'/g;
 1773:     $r->print
 1774:         ("<script>document.statusform.count.value = ' $text'</script>\n");
 1775:     $r->rflush();
 1776: }
 1777: 
 1778: sub update_status {
 1779:     my ($r,$text) = @_;
 1780:     $text =~ s/\'/\\\'/g;
 1781:     $r->print
 1782:         ("<script>document.statusform.status.value = ' $text'</script>\n");
 1783:     $r->rflush();
 1784: }
 1785: 
 1786: sub update_seconds {
 1787:     my ($r,$text) = @_;
 1788:     $text =~ s/\'/\\\'/g;
 1789:     $r->print
 1790:         ("<script>document.statusform.seconds.value = ' $text'</script>\n");
 1791:     $r->rflush();
 1792: }
 1793: 
 1794: ######################################################################
 1795: ######################################################################
 1796: 
 1797: =pod
 1798: 
 1799: =item &revise_button()
 1800: 
 1801: Inputs: None
 1802: 
 1803: Returns: html string for a 'revise search' button.
 1804: 
 1805: =cut
 1806: 
 1807: ######################################################################
 1808: ######################################################################
 1809: sub revise_button {
 1810:     my $revise_phase = 'disp_basic';
 1811:     $revise_phase = 'disp_adv' if ($ENV{'form.searchmode'} eq 'advanced');
 1812:     my $newloc = '/adm/searchcat'.
 1813:         '?persistent_db_id='.$ENV{'form.persistent_db_id'}.
 1814:             '&cleargroupsort=1'.
 1815:             '&phase='.$revise_phase;
 1816:     my $result = qq{<input type="button" value="Revise search" name="revise"} .
 1817:         qq{ onClick="parent.location='$newloc';" /> };
 1818:     return $result;
 1819: }
 1820: 
 1821: ######################################################################
 1822: ######################################################################
 1823: 
 1824: =pod
 1825: 
 1826: =item &run_search()
 1827: 
 1828: Executes a search query by sending it the the other servers and putting the
 1829: results into MySQL.
 1830: 
 1831: =cut
 1832: 
 1833: ######################################################################
 1834: ######################################################################
 1835: sub run_search {
 1836:     my ($r,$query,$customquery,$customshow,$serverlist,$pretty_string) = @_;
 1837:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 1838:     my $connection = $r->connection;
 1839:     #
 1840:     # Timing variables
 1841:     #
 1842:     my $starttime = time;
 1843:     my $max_time  = 30;  # seconds for the search to complete
 1844:     #
 1845:     # Print run_search header
 1846:     #
 1847:     $r->print(<<END);
 1848: <html>
 1849: <head><title>Search Status</title></head>
 1850: $bodytag
 1851: <form name="statusform" action="" method="post">
 1852: <input type="hidden" name="Queue" value="" />
 1853: END
 1854:     # Check to see if $pretty_string has more than one carriage return.
 1855:     # Assume \n s are following <br /> s and truncate the value.
 1856:     # (there is probably a better way)...
 1857:     my @Lines = split /<br \/>/,$pretty_string;
 1858:     if (@Lines > 2) {
 1859:         $pretty_string = join '<br \>',(@Lines[0..2],'....<br />');
 1860:     }
 1861:     $r->print(&mt("Search").": ".$pretty_string);
 1862:     $r->rflush();
 1863:     #
 1864:     # Determine the servers we need to contact.
 1865:     my @Servers_to_contact;
 1866:     if (defined($serverlist)) {
 1867:         if (ref($serverlist) eq 'ARRAY') {
 1868:             @Servers_to_contact = @$serverlist;
 1869:         } else {
 1870:             @Servers_to_contact = ($serverlist);
 1871:         }
 1872:     } else {
 1873:         @Servers_to_contact = sort(keys(%Apache::lonnet::libserv));
 1874:     }
 1875:     my %Server_status;
 1876:     my $table =$ENV{'form.table'};
 1877:     if (! defined($table) || $table eq '' || $table =~ /\D/ ) {
 1878:         $r->print("Unable to determine table id to store search results in.".
 1879:                   "The search has been aborted.</body></html>");
 1880:         return;
 1881:     }
 1882:     my $table_status = &Apache::lonmysql::check_table($table);
 1883:     if (! defined($table_status)) {
 1884:         $r->print("Unable to determine status of table.</body></html>");
 1885:         &Apache::lonnet::logthis("Bogus table id of $table for ".
 1886:                                  "$ENV{'user.name'} @ $ENV{'user.domain'}");
 1887:         &Apache::lonnet::logthis("lonmysql error = ".
 1888:                                  &Apache::lonmysql::get_error());
 1889:         return;
 1890:     }
 1891:     if (! $table_status) {
 1892:         &Apache::lonnet::logthis("lonmysql error = ".
 1893:                                  &Apache::lonmysql::get_error());
 1894:         &Apache::lonnet::logthis("lonmysql debug = ".
 1895:                                  &Apache::lonmysql::get_debug());
 1896:         &Apache::lonnet::logthis('table status = "'.$table_status.'"');
 1897:         $r->print("The table id,$table, we tried to use is invalid.".
 1898:                   "The search has been aborted.</body></html>");
 1899:         return;
 1900:     }
 1901:     ##
 1902:     ## Prepare for the big loop.
 1903:     my $hitcountsum;
 1904:     my $server; 
 1905:     my $status;
 1906:     my $revise = &revise_button();
 1907:     $r->print(<<END);
 1908: <table>
 1909: <tr><th>Status</th><th>Total Matches</th><th>Time Remaining</th><th></th></tr>
 1910: <tr>
 1911: <td><input type="text" name="status"  value="" size="30" /></td>
 1912: <td><input type="text" name="count"   value="" size="10" /></td>
 1913: <td><input type="text" name="seconds" value="" size="8" /></td>
 1914: <td>$revise</td>
 1915: </tr>
 1916: </table>
 1917: </form>
 1918: END
 1919:     $r->rflush();
 1920:     my $time_remaining = $max_time - (time - $starttime) ;
 1921:     $time_remaining = 0 if ($time_remaining <0);
 1922:     my $last_time = $time_remaining;
 1923:     &update_seconds($r,$time_remaining);
 1924:     &update_status($r,'contacting '.$Servers_to_contact[0]);
 1925:     while (($time_remaining > 0) &&
 1926:            ((@Servers_to_contact) || keys(%Server_status))) {
 1927:         &update_seconds($r,$time_remaining);
 1928:         # Send out a search request if it needs to be done.
 1929:         if (@Servers_to_contact) {
 1930:             # Contact one server
 1931:             my $server = shift(@Servers_to_contact);
 1932:             &update_status($r,&mt('contacting').' '.$server);
 1933:             my $reply=&Apache::lonnet::metadata_query($query,$customquery,
 1934:                                                       $customshow,[$server]);
 1935:             ($server) = keys(%$reply);
 1936:             $Server_status{$server} = $reply->{$server};
 1937:         } else {
 1938:             # wait a sec. to give time for files to be written
 1939:             # This sleep statement is here instead of outside the else 
 1940:             # block because we do not want to pause if we have servers
 1941:             # left to contact.  
 1942:             if (scalar (keys(%Server_status))) {
 1943:                 &update_status($r,
 1944:                        &mt('waiting on').' '.(join(' ',keys(%Server_status))));
 1945:             }
 1946:             sleep(1); 
 1947:         }
 1948:         #
 1949:         # Loop through the servers we have contacted but do not
 1950:         # have results from yet, looking for results.
 1951:         while (my ($server,$status) = each(%Server_status)) {
 1952:             last if ($connection->aborted());
 1953:             $time_remaining = $max_time - (time - $starttime) ;
 1954:             $time_remaining = 0 if ($time_remaining < 0);
 1955:             if ($last_time - $time_remaining > 0) {
 1956:                 $last_time = $time_remaining;
 1957:                 &update_seconds($r,$time_remaining);
 1958:             }
 1959:             if ($status eq 'con_lost') {
 1960:                 delete ($Server_status{$server});
 1961:                 next;
 1962:             }
 1963:             $status=~/^([\.\w]+)$/; 
 1964:        	    my $datafile=$r->dir_config('lonDaemons').'/tmp/'.$1;
 1965:             if (-e $datafile && ! -e "$datafile.end") {
 1966:                 &update_status($r,&mt('Receiving results from').' '.$server);
 1967:                 next;
 1968:             }
 1969:             last if ($connection->aborted());
 1970:             if (-e "$datafile.end") {
 1971:                 &update_status($r,&mt('Reading results from').' '.$server);
 1972:                 if (-z "$datafile") {
 1973:                     delete($Server_status{$server});
 1974:                     next;
 1975:                 }
 1976:                 my $fh;
 1977:                 if (!($fh=Apache::File->new($datafile))) { 
 1978:                     $r->print("Unable to open search results file for ".
 1979:                                   "server $server.  Omitting from search");
 1980:                     delete($Server_status{$server}); 
 1981:                    next;
 1982:                 }
 1983:                 # Read in the whole file.
 1984:                 while (my $result = <$fh>) {
 1985:                     last if ($connection->aborted());
 1986:                     # handle custom fields?  Someday we will!
 1987:                     chomp($result);
 1988:                     next unless $result;
 1989:                     # Parse the result.
 1990:                     my %Fields = &parse_raw_result($result,$server);
 1991:                     $Fields{'hostname'} = $server;
 1992:                     next if (! &copyright_check(\%Fields));
 1993:                     # Store the result in the mysql database
 1994:                     my $result = &Apache::lonmysql::store_row($table,\%Fields);
 1995:                     if (! defined($result)) {
 1996:                         $r->print(&Apache::lonmysql::get_error());
 1997:                     }
 1998:                     # $r->print(&Apache::lonmysql::get_debug());
 1999:                     $hitcountsum ++;
 2000:                     $time_remaining = $max_time - (time - $starttime) ;
 2001:                     $time_remaining = 0 if ($time_remaining < 0);
 2002:                     if ($last_time - $time_remaining > 0) {
 2003:                         &update_seconds($r,$time_remaining);
 2004:                         $last_time = $time_remaining;
 2005:                     }
 2006:                     if ($hitcountsum % 50 == 0) {
 2007:                         &update_count_status($r,$hitcountsum);
 2008:                     }
 2009:                 } # End of foreach (@results)
 2010:                 $fh->close();
 2011:                 # $server is only deleted if the results file has been 
 2012:                 # found and (successfully) opened.  This may be a bad idea.
 2013:                 delete($Server_status{$server});
 2014:             }
 2015:             last if ($connection->aborted());
 2016:             &update_count_status($r,$hitcountsum);
 2017:         }
 2018:         last if ($connection->aborted());
 2019:         # Finished looping through the servers
 2020:         $starttime = time if (@Servers_to_contact);
 2021:         $time_remaining = $max_time - (time - $starttime) ;
 2022:         if ($last_time - $time_remaining > 0) {
 2023:             $last_time = $time_remaining;
 2024:             &update_seconds($r,$time_remaining);
 2025:         }
 2026:     }
 2027:     &update_status($r,&mt('Search Complete').$server);
 2028:     &update_seconds($r,0);
 2029:     #
 2030:     &Apache::lonmysql::disconnect_from_db();
 2031:     #
 2032:     # We have run out of time or run out of servers to talk to and
 2033:     # results to get.  
 2034:     $r->print("</body></html>");
 2035:     if ($ENV{'form.catalogmode'} ne 'groupsearch') {
 2036:         $r->print("<script>".
 2037:                       "window.location='/adm/searchcat?".
 2038:                       "phase=sort&".
 2039:                       "persistent_db_id=$ENV{'form.persistent_db_id'}';".
 2040:                   "</script>");
 2041:     }
 2042:     return;
 2043: }
 2044: 
 2045: ######################################################################
 2046: ######################################################################
 2047: 
 2048: =pod
 2049: 
 2050: =item &prev_next_buttons()
 2051: 
 2052: Returns html for the previous and next buttons on the search results page.
 2053: 
 2054: =cut
 2055: 
 2056: ######################################################################
 2057: ######################################################################
 2058: sub prev_next_buttons {
 2059:     my ($current_min,$show,$total,$parms) = @_;
 2060:     return '' if ($show eq 'all'); # No links if you get them all at once.
 2061:     my $links;
 2062:     ##
 2063:     ## Prev
 2064:     my $prev_min = $current_min - $show;
 2065:     $prev_min = 1 if $prev_min < 1;
 2066:     if ($prev_min < $current_min) {
 2067:         $links .= 
 2068:             qq{<a href="/adm/searchcat?$parms&start=$prev_min&show=$show">}.
 2069:             &mt('prev').'</a>';
 2070:     } else {
 2071:         $links .= &mt('prev');
 2072:     }
 2073:     ##
 2074:     ## Pages.... Someday.
 2075:     ##
 2076:     $links .= 
 2077:         qq{&nbsp;
 2078:            <a href="/adm/searchcat?$parms&start=$current_min&$show=$show">}.
 2079:                 &mt('reload').'</a>';
 2080:     ##
 2081:     ## Next
 2082:     my $next_min = $current_min + $show;
 2083:     $next_min = $current_min if ($next_min > $total);
 2084:     if ($next_min != $current_min) {
 2085:         $links .= 
 2086:             qq{&nbsp;
 2087:                <a href="/adm/searchcat?$parms&start=$next_min&show=$show">}.
 2088:                     &mt('next').'</a>';
 2089:     } else {
 2090:         $links .= '&nbsp;'.&mt('next');
 2091:     }
 2092:     return $links;
 2093: }
 2094: 
 2095: ######################################################################
 2096: ######################################################################
 2097: 
 2098: =pod
 2099: 
 2100: =item &display_results()
 2101: 
 2102: Prints the results out for selection and perusal.
 2103: 
 2104: =cut
 2105: 
 2106: ######################################################################
 2107: ######################################################################
 2108: sub display_results {
 2109:     my ($r,$importbutton,$closebutton,$diropendb) = @_;
 2110:     my $connection = $r->connection;
 2111:     $r->print(&search_results_header($importbutton,$closebutton));
 2112:     ##
 2113:     ## Set viewing function
 2114:     ##
 2115:     my $viewfunction = $Views{$ENV{'form.viewselect'}};
 2116:     if (!defined($viewfunction)) {
 2117:         $r->print("Internal Error - Bad view selected.\n");
 2118:         $r->rflush();
 2119:         return;
 2120:     }
 2121:     ##
 2122:     ## $checkbox_num is a count of the number of checkboxes output on the 
 2123:     ## page this is used only during catalogmode=groupsearch.
 2124:     my $checkbox_num = 0;
 2125:     ##
 2126:     ## Get the catalog controls setup
 2127:     ##
 2128:     my $action = "/adm/searchcat?phase=results";
 2129:     ##
 2130:     ## Deal with groupsearch by opening the groupsearch db file.
 2131:     if ($ENV{'form.catalogmode'} eq 'groupsearch') {
 2132:         if (! tie(%groupsearch_db,'GDBM_File',$diropendb,
 2133:                   &GDBM_WRCREAT(),0640)) {
 2134:             $r->print('Unable to store import results.</form></body></html>');
 2135:             $r->rflush();
 2136:             return;
 2137:         } 
 2138:     }
 2139:     ##
 2140:     ## Prepare the table for querying
 2141:     my $table = $ENV{'form.table'};
 2142:     return if (! &ensure_db_and_table($r,$table));
 2143:     ##
 2144:     ## Get the number of results 
 2145:     my $total_results = &Apache::lonmysql::number_of_rows($table);
 2146:     if (! defined($total_results)) {
 2147:         $r->print("A MySQL error has occurred.</form></body></html>");
 2148:         &Apache::lonnet::logthis("lonmysql was unable to determine the number".
 2149:                                  " of rows in table ".$table);
 2150:         &Apache::lonnet::logthis(&Apache::lonmysql::get_error());
 2151:         return;
 2152:     }
 2153:     ##
 2154:     ## Determine how many results we need to get
 2155:     $ENV{'form.start'} = 1      if (! exists($ENV{'form.start'}));
 2156:     $ENV{'form.show'}  = 'all'  if (! exists($ENV{'form.show'}));
 2157:     my $min = $ENV{'form.start'};
 2158:     my $max;
 2159:     if ($ENV{'form.show'} eq 'all') {
 2160:         $max = $total_results ;
 2161:     } else {
 2162:         $max = $min + $ENV{'form.show'} - 1;
 2163:         $max = $total_results if ($max > $total_results);
 2164:     }
 2165:     ##
 2166:     ## Output links (if necessary) for 'prev' and 'next' pages.
 2167:     $r->print
 2168:         ('<center>'.
 2169:          &prev_next_buttons($min,$ENV{'form.show'},$total_results,
 2170:                             "table=".$ENV{'form.table'}.
 2171:                             "&phase=results".
 2172:                             "&persistent_db_id=".$ENV{'form.persistent_db_id'})
 2173:          ."</center>\n"
 2174:          );
 2175:     if ($total_results == 0) {
 2176:         $r->print('<meta HTTP-EQUIV="Refresh" CONTENT="1">'.
 2177:                   '<h3>'.&mt('There are currently no results').'.</h3>'.
 2178:                   "</form></body></html>");
 2179:         return;
 2180:     } else {
 2181:         $r->print
 2182:             ("<center>Results $min to $max out of $total_results</center>\n");
 2183:     }
 2184:     ##
 2185:     ## Get results from MySQL table
 2186:     my @Results = &Apache::lonmysql::get_rows($table,
 2187:                                               'id>='.$min.' AND id<='.$max);
 2188:     ##
 2189:     ## Loop through the results and output them.
 2190:     foreach my $row (@Results) {
 2191:         if ($connection->aborted()) {
 2192:             &cleanup();
 2193:             return;
 2194:         }
 2195:         my %Fields = %{&parse_row(@$row)};
 2196:         my $output="<p>\n";
 2197:         if (! defined($Fields{'title'}) || $Fields{'title'} eq '') {
 2198:             $Fields{'title'} = 'Untitled';
 2199:         }
 2200:         my $prefix=&catalogmode_output($Fields{'title'},$Fields{'url'},
 2201:                                        $Fields{'id'},$checkbox_num++);
 2202:         # Render the result into html
 2203:         $output.= &$viewfunction($prefix,%Fields);
 2204:         # Print them out as they come in.
 2205:         $r->print($output);
 2206:         $r->rflush();
 2207:     }
 2208:     if (@Results < 1) {
 2209:         $r->print(&mt("There were no results matching your query"));
 2210:     } else {
 2211:         $r->print
 2212:             ('<center>'.
 2213:              &prev_next_buttons($min,$ENV{'form.show'},$total_results,
 2214:                                 "table=".$ENV{'form.table'}.
 2215:                                 "&phase=results".
 2216:                                 "&persistent_db_id=".
 2217:                                 $ENV{'form.persistent_db_id'})
 2218:              ."</center>\n"
 2219:              );
 2220:     }
 2221:     $r->print("</form></body></html>");
 2222:     $r->rflush();
 2223:     untie %groupsearch_db if (tied(%groupsearch_db));
 2224:     return;
 2225: }
 2226: 
 2227: ######################################################################
 2228: ######################################################################
 2229: 
 2230: =pod
 2231: 
 2232: =item &catalogmode_output($title,$url,$fnum,$checkbox_num)
 2233: 
 2234: Returns html needed for the various catalog modes.  Gets inputs from
 2235: $ENV{'form.catalogmode'}.  Stores data in %groupsearch_db.
 2236: 
 2237: =cut
 2238: 
 2239: ######################################################################
 2240: ######################################################################
 2241: sub catalogmode_output {
 2242:     my $output = '';
 2243:     my ($title,$url,$fnum,$checkbox_num) = @_;
 2244:     if ($ENV{'form.catalogmode'} eq 'interactive') {
 2245:         $title=~ s/\'/\\\'/g;
 2246:         if ($ENV{'form.catalogmode'} eq 'interactive') {
 2247:             $output.=<<END 
 2248: <font size='-1'><INPUT TYPE="button" NAME="returnvalues" VALUE="SELECT"
 2249: onClick="javascript:select_data('$title','$url')">
 2250: </font>
 2251: END
 2252:         }
 2253:     } elsif ($ENV{'form.catalogmode'} eq 'groupsearch') {
 2254:         $groupsearch_db{"pre_${fnum}_link"}=$url;
 2255:         $groupsearch_db{"pre_${fnum}_title"}=$title;
 2256:         $output.=<<END;
 2257: <font size='-1'>
 2258: <input type="checkbox" name="returnvalues" value="SELECT"
 2259: onClick="javascript:queue($checkbox_num,$fnum)" />
 2260: </font>
 2261: END
 2262:     }
 2263:     return $output;
 2264: }
 2265: ######################################################################
 2266: ######################################################################
 2267: 
 2268: =pod
 2269: 
 2270: =item &parse_row()
 2271: 
 2272: Parse a row returned from the database.
 2273: 
 2274: =cut
 2275: 
 2276: ######################################################################
 2277: ######################################################################
 2278: sub parse_row {
 2279:     my @Row = @_;
 2280:     my %Fields;
 2281:     if (! scalar(@Datatypes)) {
 2282:         &set_up_table_structure();
 2283:     }
 2284:     for (my $i=0;$i<=$#Row;$i++) {
 2285:         $Fields{$Datatypes[$i]->{'name'}}=&Apache::lonnet::unescape($Row[$i]);
 2286:     }
 2287:     $Fields{'language'} = 
 2288:         &Apache::loncommon::languagedescription($Fields{'language'});
 2289:     $Fields{'copyrighttag'} =
 2290:         &Apache::loncommon::copyrightdescription($Fields{'copyright'});
 2291:     $Fields{'mimetag'} =
 2292:         &Apache::loncommon::filedescription($Fields{'mime'});
 2293:     return \%Fields;
 2294: }
 2295: 
 2296: ###########################################################
 2297: ###########################################################
 2298: 
 2299: =pod
 2300: 
 2301: =item &parse_raw_result()
 2302: 
 2303: Takes a line from the file of results and parse it.  Returns a hash 
 2304: with keys according to column labels
 2305: 
 2306: In addition, the following tags are set by calling the appropriate 
 2307: lonnet function: 'language', 'copyrighttag', 'mimetag'.
 2308: 
 2309: The 'title' field is set to "Untitled" if the title field is blank.
 2310: 
 2311: 'abstract' and 'keywords' are truncated to 200 characters.
 2312: 
 2313: =cut
 2314: 
 2315: ###########################################################
 2316: ###########################################################
 2317: sub parse_raw_result {
 2318:     my ($result,$hostname) = @_;
 2319:     # conclude from self to others regarding fields
 2320:     my %Fields=&LONCAPA::lonmetadata::metadata_col_to_hash
 2321:         (map {
 2322:             &Apache::lonnet::unescape($_);
 2323:         } (split(/\,/,$result)) );
 2324:     return %Fields;
 2325: }
 2326: 
 2327: ###########################################################
 2328: ###########################################################
 2329: 
 2330: =pod
 2331: 
 2332: =item &handle_custom_fields()
 2333: 
 2334: =cut
 2335: 
 2336: ###########################################################
 2337: ###########################################################
 2338: sub handle_custom_fields {
 2339:     my @results = @{shift()};
 2340:     my $customshow='';
 2341:     my $extrashow='';
 2342:     my @customfields;
 2343:     if ($ENV{'form.customshow'}) {
 2344:         $customshow=$ENV{'form.customshow'};
 2345:         $customshow=~s/[^\w\s]//g;
 2346:         my @fields=map {
 2347:             "<font color=\"#008000\">$_:</font><!-- $_ -->";
 2348:         } split(/\s+/,$customshow);
 2349:         @customfields=split(/\s+/,$customshow);
 2350:         if ($customshow) {
 2351:             $extrashow="<ul><li>".join("</li><li>",@fields)."</li></ul>\n";
 2352:         }
 2353:     }
 2354:     my $customdata='';
 2355:     my %customhash;
 2356:     foreach my $result (@results) {
 2357:         if ($result=~/^(custom\=.*)$/) { # grab all custom metadata
 2358:             my $tmp=$result;
 2359:             $tmp=~s/^custom\=//;
 2360:             my ($k,$v)=map {&Apache::lonnet::unescape($_);
 2361:                         } split(/\,/,$tmp);
 2362:             $customhash{$k}=$v;
 2363:         }
 2364:     }
 2365:     return ($extrashow,\@customfields,\%customhash);
 2366: }
 2367: 
 2368: ######################################################################
 2369: ######################################################################
 2370: 
 2371: =pod
 2372: 
 2373: =item &search_results_header()
 2374: 
 2375: Output the proper html headers and javascript code to deal with different 
 2376: calling modes.
 2377: 
 2378: Takes most inputs directly from %ENV, except $mode.  
 2379: 
 2380: =over 4
 2381: 
 2382: =item $mode is either (at this writing) 'Basic' or 'Advanced'
 2383: 
 2384: =back
 2385: 
 2386: The following environment variables are checked:
 2387: 
 2388: =over 4
 2389: 
 2390: =item 'form.catalogmode' 
 2391: 
 2392: Checked for 'interactive' and 'groupsearch'.
 2393: 
 2394: =item 'form.mode'
 2395: 
 2396: Checked for existance & 'edit' mode.
 2397: 
 2398: =item 'form.form'
 2399: 
 2400: Contains the name of the form that has the input fields to set
 2401: 
 2402: =item 'form.element'
 2403: 
 2404: the name of the input field to put the URL into
 2405: 
 2406: =item 'form.titleelement'
 2407: 
 2408: the name of the input field to put the title into
 2409: 
 2410: =back
 2411: 
 2412: =cut
 2413: 
 2414: ######################################################################
 2415: ######################################################################
 2416: sub search_results_header {
 2417:     my ($importbutton,$closebutton) = @_;
 2418:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 2419:     my $result = '';
 2420:     # output beginning of search page
 2421:     # conditional output of script functions dependent on the mode in
 2422:     # which the search was invoked
 2423:     if ($ENV{'form.catalogmode'} eq 'interactive'){
 2424: 	if (! exists($ENV{'form.mode'}) || $ENV{'form.mode'} ne 'edit') {
 2425:             $result.=<<SCRIPT;
 2426: <script type="text/javascript">
 2427:     function select_data(title,url) {
 2428: 	changeTitle(title);
 2429: 	changeURL(url);
 2430: 	parent.close();
 2431:     }
 2432:     function changeTitle(val) {
 2433: 	if (parent.opener.inf.document.forms.resinfo.elements.t) {
 2434: 	    parent.opener.inf.document.forms.resinfo.elements.t.value=val;
 2435: 	}
 2436:     }
 2437:     function changeURL(val) {
 2438: 	if (parent.opener.inf.document.forms.resinfo.elements.u) {
 2439: 	    parent.opener.inf.document.forms.resinfo.elements.u.value=val;
 2440: 	}
 2441:     }
 2442: </script>
 2443: SCRIPT
 2444:         } elsif ($ENV{'form.mode'} eq 'edit') {
 2445:             my $form = $ENV{'form.form'};
 2446:             my $element = $ENV{'form.element'};
 2447:             my $titleelement = $ENV{'form.titleelement'};
 2448: 	    my $changetitle;
 2449: 	    if (!$titleelement) {
 2450: 		$changetitle='function changeTitle(val) {}';
 2451: 	    } else {
 2452: 		    $changetitle=<<END;
 2453: function changeTitle(val) {
 2454:     if (parent.targetwin.document) {
 2455:         parent.targetwin.document.forms["$form"].elements["$titleelement"].value=val;
 2456:     } else {
 2457: 	var url = 'forms[\"$form\"].elements[\"$titleelement\"].value';
 2458:         alert("Unable to transfer data to "+url);
 2459:     }
 2460: }
 2461: END
 2462:             }
 2463: 
 2464:             $result.=<<SCRIPT;
 2465: <script type="text/javascript">
 2466: function select_data(title,url) {
 2467:     changeURL(url);
 2468:     changeTitle(title);
 2469:     parent.close();
 2470: }
 2471: $changetitle
 2472: function changeURL(val) {
 2473:     if (parent.targetwin.document) {
 2474:         parent.targetwin.document.forms["$form"].elements["$element"].value=val;
 2475:     } else {
 2476: 	var url = 'forms[\"$form\"].elements[\"$element\"].value';
 2477:         alert("Unable to transfer data to "+url);
 2478:     }
 2479: }
 2480: </script>
 2481: SCRIPT
 2482:         }
 2483:     }
 2484:     $result.=<<SCRIPT if $ENV{'form.catalogmode'} eq 'groupsearch';
 2485: <script type="text/javascript">
 2486:     function queue(checkbox_num,val) {
 2487:         if (document.forms.results.returnvalues.length != "undefined" &&
 2488:             typeof(document.forms.results.returnvalues.length) == "number") {
 2489:             if (document.forms.results.returnvalues[checkbox_num].checked) {
 2490:                 parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b';
 2491:             } else {
 2492:                 parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b';
 2493:             }
 2494:         } else {
 2495:             if (document.forms.results.returnvalues.checked) {
 2496:                 parent.statusframe.document.forms.statusform.elements.Queue.value +='1a'+val+'b';
 2497:             } else {
 2498:                 parent.statusframe.document.forms.statusform.elements.Queue.value +='0a'+val+'b';
 2499:             }
 2500:         }
 2501:     }
 2502:     function select_group() {
 2503: 	parent.window.location=
 2504:     "/adm/groupsort?mode=$ENV{'form.mode'}&catalogmode=groupsearch&acts="+
 2505: 	    parent.statusframe.document.forms.statusform.elements.Queue.value;
 2506:     }
 2507: </script>
 2508: SCRIPT
 2509:     $result.=<<END;
 2510: </head>
 2511: $bodytag
 2512: <form name="results" method="post" action="" >
 2513: <input type="hidden" name="Queue" value="" />
 2514: $importbutton
 2515: END
 2516:     return $result;
 2517: }
 2518: 
 2519: ######################################################################
 2520: ######################################################################
 2521: sub search_status_header {
 2522:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 2523:     return <<ENDSTATUS;
 2524: <html><head><title>Search Status</title></head>
 2525: $bodytag
 2526: <h3>Search Status</h3>
 2527: Sending search request to LON-CAPA servers.<br />
 2528: ENDSTATUS
 2529: }
 2530: 
 2531: sub results_link {
 2532:     my $basic_link   = "/adm/searchcat?"."&table=".$ENV{'form.table'}.
 2533:         "&persistent_db_id=".$ENV{'form.persistent_db_id'};
 2534:     my $results_link = $basic_link."&phase=results".
 2535:         "&pause=1"."&start=1";
 2536:     return $results_link;
 2537: }
 2538: 
 2539: ######################################################################
 2540: ######################################################################
 2541: sub print_frames_interface {
 2542:     my $r = shift;
 2543:     my $basic_link = "/adm/searchcat?"."&table=".$ENV{'form.table'}.
 2544:         "&persistent_db_id=".$ENV{'form.persistent_db_id'};
 2545:     my $run_search_link = $basic_link."&phase=run_search";
 2546:     my $results_link = &results_link();
 2547:     my $result = <<"ENDFRAMES";
 2548: <html>
 2549: <head>
 2550: <script>
 2551: var targetwin = opener;
 2552: var queue = '';
 2553: </script>
 2554: <title>LON-CAPA Digital Library Search Results</title>
 2555: </head>
 2556: <frameset rows="150,*">
 2557:     <frame name="statusframe"  src="$run_search_link">
 2558:     <frame name="resultsframe" src="$results_link">
 2559: </frameset>
 2560: </html>
 2561: ENDFRAMES
 2562: 
 2563:     $r->print($result);
 2564:     return;
 2565: }
 2566: 
 2567: ######################################################################
 2568: ######################################################################
 2569: 
 2570: =pod 
 2571: 
 2572: =item Metadata Viewing Functions
 2573: 
 2574: Output is a HTML-ified string.
 2575: 
 2576: Input arguments are title, author, subject, url, keywords, version,
 2577: notes, short abstract, mime, language, creation date,
 2578: last revision date, owner, copyright, hostname, and
 2579: extra custom metadata to show.
 2580: 
 2581: =over 4
 2582: 
 2583: =item &detailed_citation_view() 
 2584: 
 2585: =cut
 2586: 
 2587: ######################################################################
 2588: ######################################################################
 2589: sub detailed_citation_view {
 2590:     my ($prefix,%values) = @_;
 2591:     my $icon=&Apache::loncommon::icon($values{'url'});
 2592:     my $result=<<END;
 2593: <b>$prefix<img src="$icon" /><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 
 2594:     target='search_preview'>$values{'title'}</a></b>
 2595: <p>
 2596: <b>$values{'author'}</b>, <i>$values{'owner'}</i><br />
 2597: 
 2598: <b>Subject:       </b> $values{'subject'}<br />
 2599: <b>Keyword(s):    </b> $values{'keywords'}<br />
 2600: <b>Notes:         </b> $values{'notes'}<br />
 2601: <b>MIME Type:     </b> $values{'mimetag'}<br />
 2602: <b>Language:      </b> $values{'language'}<br />
 2603: <b>Copyright/Distribution:</b> $values{'copyrighttag'}<br />
 2604: </p>
 2605: $values{'extrashow'}
 2606: <p>
 2607: $values{'shortabstract'}
 2608: </p>
 2609: <hr align='left' width='200' noshade />
 2610: END
 2611:     return $result;
 2612: }
 2613: 
 2614: ######################################################################
 2615: ######################################################################
 2616: 
 2617: =pod 
 2618: 
 2619: =item &summary_view() 
 2620: 
 2621: =cut
 2622: ######################################################################
 2623: ######################################################################
 2624: sub summary_view {
 2625:     my ($prefix,%values) = @_;
 2626:     my $icon=&Apache::loncommon::icon($values{'url'});
 2627:     my $result=<<END;
 2628: $prefix<img src="$icon" /><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 
 2629:    target='search_preview'>$values{'author'}</a><br />
 2630: $values{'title'}<br />
 2631: $values{'owner'} -- $values{'lastrevisiondate'}<br />
 2632: $values{'copyrighttag'}<br />
 2633: $values{'extrashow'}
 2634: </p>
 2635: <hr align='left' width='200' noshade />
 2636: END
 2637:     return $result;
 2638: }
 2639: 
 2640: ######################################################################
 2641: ######################################################################
 2642: 
 2643: =pod 
 2644: 
 2645: =item &compact_view() 
 2646: 
 2647: =cut
 2648: 
 2649: ######################################################################
 2650: ######################################################################
 2651: sub compact_view {
 2652:     my ($prefix,%values) = @_;
 2653:     my $icon=&Apache::loncommon::icon($values{'url'});
 2654:     my $result=<<END;
 2655: $prefix <img src="$icon" /> <a href="http://$ENV{'HTTP_HOST'}$values{'url'}"  target='search_preview'>
 2656: $values{'title'}</a>
 2657: <b>$values{'author'}</b><br />
 2658: END
 2659:     return $result;
 2660: }
 2661: 
 2662: 
 2663: ######################################################################
 2664: ######################################################################
 2665: 
 2666: =pod 
 2667: 
 2668: =item &fielded_format_view() 
 2669: 
 2670: =cut
 2671: 
 2672: ######################################################################
 2673: ######################################################################
 2674: sub fielded_format_view {
 2675:     my ($prefix,%values) = @_;
 2676:     my $icon=&Apache::loncommon::icon($values{'url'});
 2677:     my $result=<<END;
 2678: $prefix <img src="$icon" />
 2679: <b>URL: </b> <a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 
 2680:               target='search_preview'>$values{'url'}</a>
 2681: <br />
 2682: <b>Title:</b> $values{'title'}<br />
 2683: <b>Author(s):</b> $values{'author'}<br />
 2684: <b>Subject:</b> $values{'subject'}<br />
 2685: <b>Keyword(s):</b> $values{'keywords'}<br />
 2686: <b>Notes:</b> $values{'notes'}<br />
 2687: <b>MIME Type:</b> $values{'mimetag'}<br />
 2688: <b>Language:</b> $values{'language'}<br />
 2689: <b>Creation Date:</b> $values{'creationdate'}<br />
 2690: <b>Last Revision Date:</b> $values{'lastrevisiondate'}<br />
 2691: <b>Publisher/Owner:</b> $values{'owner'}<br />
 2692: <b>Copyright/Distribution:</b> $values{'copyrighttag'}<br />
 2693: <b>Repository Location:</b> $values{'hostname'}<br />
 2694: <b>Abstract:</b> $values{'shortabstract'}<br />
 2695: $values{'extrashow'}
 2696: </p>
 2697: <hr align='left' width='200' noshade />
 2698: END
 2699:     return $result;
 2700: }
 2701: 
 2702: ######################################################################
 2703: ######################################################################
 2704: 
 2705: =pod 
 2706: 
 2707: =item &xml_sgml_view() 
 2708: 
 2709: =back 
 2710: 
 2711: =cut
 2712: 
 2713: ######################################################################
 2714: ######################################################################
 2715: sub xml_sgml_view {
 2716:     my ($prefix,%values) = @_;
 2717:     my $result=<<END;
 2718: $prefix
 2719: <pre>
 2720: &lt;LonCapaResource&gt;
 2721: &lt;url&gt;$values{'url'}&lt;/url&gt;
 2722: &lt;title&gt;$values{'title'}&lt;/title&gt;
 2723: &lt;author&gt;$values{'author'}&lt;/author&gt;
 2724: &lt;subject&gt;$values{'subject'}&lt;/subject&gt;
 2725: &lt;keywords&gt;$values{'keywords'}&lt;/keywords&gt;
 2726: &lt;notes&gt;$values{'notes'}&lt;/notes&gt;
 2727: &lt;mimeInfo&gt;
 2728: &lt;mime&gt;$values{'mime'}&lt;/mime&gt;
 2729: &lt;mimetag&gt;$values{'mimetag'}&lt;/mimetag&gt;
 2730: &lt;/mimeInfo&gt;
 2731: &lt;languageInfo&gt;
 2732: &lt;language&gt;$values{'language'}&lt;/language&gt;
 2733: &lt;languagetag&gt;$values{'languagetag'}&lt;/languagetag&gt;
 2734: &lt;/languageInfo&gt;
 2735: &lt;creationdate&gt;$values{'creationdate'}&lt;/creationdate&gt;
 2736: &lt;lastrevisiondate&gt;$values{'lastrevisiondate'}&lt;/lastrevisiondate&gt;
 2737: &lt;owner&gt;$values{'owner'}&lt;/owner&gt;
 2738: &lt;copyrightInfo&gt;
 2739: &lt;copyright&gt;$values{'copyright'}&lt;/copyright&gt;
 2740: &lt;copyrighttag&gt;$values{'copyrighttag'}&lt;/copyrighttag&gt;
 2741: &lt;/copyrightInfo&gt;
 2742: &lt;repositoryLocation&gt;$values{'hostname'}&lt;/repositoryLocation&gt;
 2743: &lt;shortabstract&gt;$values{'shortabstract'}&lt;/shortabstract&gt;
 2744: &lt;/LonCapaResource&gt;
 2745: </pre>
 2746: $values{'extrashow'}
 2747: <hr align='left' width='200' noshade />
 2748: END
 2749:     return $result;
 2750: }
 2751: 
 2752: ######################################################################
 2753: ######################################################################
 2754: 
 2755: =pod 
 2756: 
 2757: =item &filled() see if field is filled.
 2758: 
 2759: =cut
 2760: 
 2761: ######################################################################
 2762: ######################################################################
 2763: sub filled {
 2764:     my ($field)=@_;
 2765:     if ($field=~/\S/ && $field ne 'any') {
 2766:         return 1;
 2767:     } else {
 2768:         return 0;
 2769:     }
 2770: }
 2771: 
 2772: ######################################################################
 2773: ######################################################################
 2774: 
 2775: =pod 
 2776: 
 2777: =item &output_blank_field_error()
 2778: 
 2779: Output a complete page that indicates the user has not filled in enough
 2780: information to do a search.
 2781: 
 2782: Inputs: $r (Apache request handle), $closebutton, $parms.
 2783: 
 2784: Returns: nothing
 2785: 
 2786: $parms is extra information to include in the 'Revise search request' link.
 2787: 
 2788: =cut
 2789: 
 2790: ######################################################################
 2791: ######################################################################
 2792: sub output_blank_field_error {
 2793:     my ($r,$closebutton,$parms,$hidden_fields)=@_;
 2794:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 2795:     # make query information persistent to allow for subsequent revision
 2796:     $r->print(<<BEGINNING);
 2797: <html>
 2798: <head>
 2799: <title>The LearningOnline Network with CAPA</title>
 2800: BEGINNING
 2801:     $r->print(<<RESULTS);
 2802: </head>
 2803: $bodytag
 2804: <img align='right' src='/adm/lonIcons/lonlogos.gif' />
 2805: <h1>Search Catalog</h1>
 2806: <form method="post" action="/adm/searchcat">
 2807: $hidden_fields
 2808: <a href="/adm/searchcat?$parms&persistent_db_id=$ENV{'form.persistent_db_id'}"
 2809: >Revise search request</a>&nbsp;
 2810: $closebutton
 2811: <hr />
 2812: <h3>Unactionable search query.</h3>
 2813: <p>
 2814: You did not fill in enough information for the search to be started.
 2815: You need to fill in relevant fields on the search page in order 
 2816: for a query to be processed.
 2817: </p>
 2818: </body>
 2819: </html>
 2820: RESULTS
 2821: }
 2822: 
 2823: ######################################################################
 2824: ######################################################################
 2825: 
 2826: =pod 
 2827: 
 2828: =item &output_date_error()
 2829: 
 2830: Output a full html page with an error message.
 2831: 
 2832: Inputs: 
 2833: 
 2834:     $r, the request pointer.
 2835:     $message, the error message for the user.
 2836:     $closebutton, the specialized close button needed for groupsearch.
 2837: 
 2838: =cut
 2839: 
 2840: ######################################################################
 2841: ######################################################################
 2842: sub output_date_error {
 2843:     my ($r,$message,$closebutton,$hidden_fields)=@_;
 2844:     # make query information persistent to allow for subsequent revision
 2845:     my $bodytag=&Apache::loncommon::bodytag(undef,undef,undef,1);
 2846:     $r->print(<<RESULTS);
 2847: <html>
 2848: <head>
 2849: <title>The LearningOnline Network with CAPA</title>
 2850: </head>
 2851: $bodytag
 2852: <img align='right' src='/adm/lonIcons/lonlogos.gif' />
 2853: <h1>Search Catalog</h1>
 2854: <form method="post" action="/adm/searchcat">
 2855: $hidden_fields
 2856: <input type='button' value='Revise search request'
 2857: onClick='this.form.submit();' />
 2858: $closebutton
 2859: <hr />
 2860: <h3>Error</h3>
 2861: <p>
 2862: $message
 2863: </p>
 2864: </body>
 2865: </html>
 2866: RESULTS
 2867: }
 2868: 
 2869: ######################################################################
 2870: ######################################################################
 2871: 
 2872: =pod 
 2873: 
 2874: =item &start_fresh_session()
 2875: 
 2876: Cleans the global %groupsearch_db by removing all fields which begin with
 2877: 'pre_' or 'store'.
 2878: 
 2879: =cut
 2880: 
 2881: ######################################################################
 2882: ######################################################################
 2883: sub start_fresh_session {
 2884:     delete $groupsearch_db{'mode_catalog'};
 2885:     foreach (keys %groupsearch_db) {
 2886:         if ($_ =~ /^pre_/) {
 2887:             delete $groupsearch_db{$_};
 2888:         }
 2889:         if ($_ =~ /^store/) {
 2890: 	    delete $groupsearch_db{$_};
 2891: 	}
 2892:     }
 2893: }
 2894: 
 2895: 1;
 2896: 
 2897: sub cleanup {
 2898:     if (tied(%groupsearch_db)) {
 2899:         unless (untie(%groupsearch_db)) {
 2900: 	  &Apache::lonnet::logthis('Failed cleanup searchcat: groupsearch_db');
 2901:         }
 2902:     }
 2903:     &untiehash();
 2904:     &Apache::lonmysql::disconnect_from_db();
 2905: }
 2906: 
 2907: __END__
 2908: 
 2909: =pod
 2910: 
 2911: =back 
 2912: 
 2913: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>