Diff for /loncom/interface/lonsearchcat.pm between versions 1.126 and 1.139

version 1.126, 2002/06/24 15:09:52 version 1.139, 2002/07/08 20:35:36
Line 67  search (on a server basis) is displayed Line 67  search (on a server basis) is displayed
 ###############################################################################  ###############################################################################
 ###############################################################################  ###############################################################################
   
   ###############################################################################
 ##                                                                           ##  ##                                                                           ##
 ## ORGANIZATION OF THIS PERL MODULE                                          ##  ## ORGANIZATION OF THIS PERL MODULE                                          ##
 ##                                                                           ##  ##                                                                           ##
 ## 1. Modules used by this module                                            ##  ## 1. Modules used by this module                                            ##
 ## 2. Choices for different output views (detailed, summary, xml, etc)       ##  ## 2. Variables used throughout the module                                   ##
 ## 3. BEGIN block (to be run once after compilation)                         ##  ## 3. handler subroutine called via Apache and mod_perl                      ##
 ## 4. Handling routine called via Apache and mod_perl                        ##  ## 4. Other subroutines                                                      ##
 ## 5. Other subroutines                                                      ##  
 ##                                                                           ##  ##                                                                           ##
 ###############################################################################  ###############################################################################
   
Line 101  use Apache::loncommon(); Line 101  use Apache::loncommon();
   
 =over 4  =over 4
   
 =item %hostdomains  
   
 matches host name to host domain  
   
 =item %hostips  
   
 matches host name to host ip  
   
 =item %hitcount  
   
 stores number of hits per host  
   
 =item $closebutton  =item $closebutton
   
 button that closes the search window  button that closes the search window
   
 =item $importbutton  =item $importbutton
   
 button to take the selecte results and go to group sorting  button to take the select results and go to group sorting
   
 =item %hash     =item %hash   
   
Line 130  The ubiquitous database hash Line 118  The ubiquitous database hash
 The full path to the (temporary) search database file.  This is set and  The full path to the (temporary) search database file.  This is set and
 used in &handler() and is also used in &output_results().  used in &handler() and is also used in &output_results().
   
   =item %Views
   
   Hash which associates an output view description with the function
   that produces it.  Adding a new view type should be as easy as
   adding a line to the definition of this hash and making sure the function
   takes the proper parameters.
   
 =back   =back 
   
 =cut  =cut
Line 137  used in &handler() and is also used in & Line 132  used in &handler() and is also used in &
 ######################################################################  ######################################################################
 ######################################################################  ######################################################################
   
 # -- information holders  
 my %hostdomains; # matches host name to host domain  
 my %hostips;     # matches host name to host ip  
 my %hitcount;    # stores number of hits per host  
   
 # -- dynamically rendered interface components  # -- dynamically rendered interface components
 my $closebutton;  # button that closes the search window  my $closebutton;  # button that closes the search window
 my $importbutton; # button to take the selected results and go to group sorting  my $importbutton; # button to take the selected results and go to group sorting
   
 # -- miscellaneous variables  # -- miscellaneous variables
 my $yourself; # allows for quickly limiting to oneself  
 my %hash;     # database hash  my %hash;     # database hash
   my $diropendb = "";    # db file
   
 # ------------------------------------------ choices for different output views  #             View Description           Function Pointer
 # Detailed Citation View ---> sub detailed_citation_view  my %Views = ("Detailed Citation View" => \&detailed_citation_view,
 # Summary View ---> sub summary_view               "Summary View"           => \&summary_view,
 # Fielded Format ---> sub fielded_format_view               "Fielded Format"         => \&fielded_format_view,
 # XML/SGML ---> sub xml_sgml_view               "XML/SGML"               => \&xml_sgml_view );
   
 #------------------------------------------------------------- global variables  
 my $diropendb = "";  
 my $domain = "";  
   
 # ----------------------------------------------------------------------- BEGIN  
   
 =pod  
   
 =item BEGIN block  
   
 Load %hostdomains and %hostips with data from lonnet.pm.  Only library  
 servers are considered.  
   
 =cut  
   
 BEGIN {  
     foreach (keys (%Apache::lonnet::libserv)) {  
         $hostdomains{$_}=$Apache::lonnet::hostdom{$_};  
         $hostips{$_}=$Apache::lonnet::hostip{$_};  
     }  
 }  
   
 ######################################################################  ######################################################################
 ######################################################################  ######################################################################
Line 257  END Line 225  END
 onClick='javascript:select_group()'>  onClick='javascript:select_group()'>
 END  END
     }      }
     $hidden .= <<END;      $hidden .= &make_persistent({ "form.mode"    => $ENV{'form.mode'},
 <input type='hidden' name='mode'    value='$ENV{'form.mode'}'>                                    "form.form"    => $ENV{'form.form'},
 <input type='hidden' name='form'    value='$ENV{'form.form'}'>                                    "form.element" => $ENV{'form.element'},
 <input type='hidden' name='element' value='$ENV{'form.element'}'>                                    "form.date"    => 2 });
 <input type='hidden' name='date' value='2'>  
 END  
     ##      ##
     ##  What are we doing?      ##  What are we doing?
     ##      ##
     if ($ENV{'form.basicsubmit'} eq 'SEARCH') {      my $searchtype;
         # Perform basic search and give results      $searchtype = 'Basic'    if ($ENV{'form.basicsubmit'}    eq 'SEARCH');
  return &basicsearch($r,\%ENV,$hidden);      $searchtype = 'Advanced' if ($ENV{'form.advancedsubmit'} eq 'SEARCH');
     } elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') {      if ($searchtype) {
         # Perform advanced search and give results          # We are running a search
  return &advancedsearch($r,\%ENV,$hidden);          my ($query,$customquery,$customshow,$libraries) = 
     } elsif ($ENV{'form.reqinterface'} eq 'advanced') {              (undef,undef,undef,undef);
           if ($searchtype eq 'Basic') {
               $query = &parse_basic_search($r);
           } elsif ($ENV{'form.advancedsubmit'} eq 'SEARCH') {
               ($query,$customquery,$customshow,$libraries) 
                   = &parse_advanced_search($r);
               return OK if (! defined($query));
           }
           # Send query statements over the network to be processed by 
           # either the SQL database or a recursive scheme of 'grep'-like 
           # actions (for custom metadata).
           $r->rflush();
           my $reply=&Apache::lonnet::metadata_query($query,$customquery,
                                                  $customshow,$libraries);
           &output_results($searchtype,$r,$reply,$hidden);
       } else {
           #
           # We need to get information to search on
           #
           # Set the default view if it is not already set.
           if (!defined($ENV{'form.viewselect'})) {
               $ENV{'form.viewselect'} ="Detailed Citation View";
           }
         # Output the advanced interface          # Output the advanced interface
         $r->print(&advanced_search_form($closebutton,$hidden));          if ($ENV{'form.reqinterface'} eq 'advanced') {
         return OK;              $r->print(&advanced_search_form($closebutton,$hidden));
     } else {           } else { 
         # Output normal search interface              # Output normal search interface
         $r->print(&basic_search_form($closebutton,$hidden));              $r->print(&basic_search_form($closebutton,$hidden));
           }
     }      }
     return OK;      return OK;
 }   } 
Line 318  sub basic_search_form{ Line 307  sub basic_search_form{
 $hidden  $hidden
 <h3>Basic Search</h3>  <h3>Basic Search</h3>
 <p>  <p>
 Enter terms or phrases separated by AND, OR, or NOT then press SEARCH below.  Enter terms or phrases separated by AND, OR, or NOT 
   then press SEARCH below.
 </p>  </p>
 <p>  <p>
 <table>  <table>
Line 328  ENDDOCUMENT Line 318  ENDDOCUMENT
         '&nbsp;';          '&nbsp;';
 #    $scrout.=&simplecheckbox('allversions',$ENV{'form.allversions'});  #    $scrout.=&simplecheckbox('allversions',$ENV{'form.allversions'});
 #    $scrout.='<font color="#800000">Search historic archives</font>';  #    $scrout.='<font color="#800000">Search historic archives</font>';
     $scrout.=<<ENDDOCUMENT;      $scrout.=<<END;
 </td><td><a href="/adm/searchcat?reqinterface=advanced">Advanced Search</a></td></tr></table>  </td><td><a href="/adm/searchcat?reqinterface=advanced">Advanced Search</a></td></tr></table>
 </p>  </p>
 <p>  <p>
 &nbsp;<input type="submit" name="basicsubmit" value='SEARCH' />&nbsp;  &nbsp;<input type="submit" name="basicsubmit" value='SEARCH' />&nbsp;
 $closebutton  $closebutton
 <!-- basic view selection -->  END
 <select name='basicviewselect'>      $scrout.=&selectbox(undef,'viewselect',
 <option value='Detailed Citation View' selected="true">   $ENV{'form.viewselect'},
 Detailed Citation View</option>   undef,undef,undef,
 <option value='Summary View'>Summary View</option>   sort(keys(%Views)));
 <option value='Fielded Format'>Fielded Format</option>      $scrout.=<<ENDDOCUMENT;
 <option value='XML/SGML'>XML/SGML</option>  
 </select>  
 <!-- end of basic view selection -->  
 <input type="button" value="HELP" onClick="openhelp()" />  <input type="button" value="HELP" onClick="openhelp()" />
 </p>  </p>
 </form>  </form>
Line 367  Returns a scalar which holds html for th Line 354  Returns a scalar which holds html for th
   
 sub advanced_search_form{  sub advanced_search_form{
     my ($closebutton,$hidden) = @_;      my ($closebutton,$hidden) = @_;
       my $advanced_buttons = <<"END";
   <p>
   <input type="submit" name="advancedsubmit" value='SEARCH' />
   <input type="reset" name="reset" value='RESET' />
   $closebutton
   <input type="button" value="HELP" onClick="openhelp()" />
   </p>
   END
       if (!defined($ENV{'form.viewselect'})) {
           $ENV{'form.viewselect'} ="Detailed Citation View";
       }
     my $scrout=<<"ENDHEADER";      my $scrout=<<"ENDHEADER";
 <html>  <html>
 <head>  <head>
Line 381  sub advanced_search_form{ Line 379  sub advanced_search_form{
 </head>  </head>
 <body bgcolor="#FFFFFF">  <body bgcolor="#FFFFFF">
 <img align='right' src='/adm/lonIcons/lonlogos.gif' />  <img align='right' src='/adm/lonIcons/lonlogos.gif' />
 <h1>Search Catalog</h1>  <h1>Advanced Catalog Search</h1>
   <hr />
   Enter terms or phrases separated by search operators 
   such as AND, OR, or NOT.<br />
 <form method="post" action="/adm/searchcat">  <form method="post" action="/adm/searchcat">
   $advanced_buttons
 $hidden  $hidden
 <hr />  <table>
 <h3>Advanced Search</h3>  <tr><td><font color="#800000" face="helvetica"><b>VIEW:</b></font></td>
   <td>
 ENDHEADER  ENDHEADER
     $scrout.=&searchphrasefield('Limit by title','title',      $scrout.=&selectbox(undef,'viewselect',
  $ENV{'form.title'});   $ENV{'form.viewselect'},
     $scrout.=&searchphrasefield('Limit by author','author',   undef,undef,undef,
  $ENV{'form.author'});   sort(keys(%Views)));
     $scrout.=&searchphrasefield('Limit by subject','subject',      $scrout.="</td></tr>\n";
  $ENV{'form.subject'});      $scrout.=&searchphrasefield('title',   'title'   ,$ENV{'form.title'});
     $scrout.=&searchphrasefield('Limit by keywords','keywords',      $scrout.=&searchphrasefield('author',  'author'  ,$ENV{'form.author'});
  $ENV{'form.keywords'});      $scrout.=&searchphrasefield('subject', 'subject' ,$ENV{'form.subject'});
     $scrout.=&searchphrasefield('Limit by URL','url',      $scrout.=&searchphrasefield('keywords','keywords',$ENV{'form.keywords'});
  $ENV{'form.url'});      $scrout.=&searchphrasefield('URL',     'url'     ,$ENV{'form.url'});
 #    $scrout.=&searchphrasefield('Limit by version','version',      $scrout.=&searchphrasefield('notes',   'notes'   ,$ENV{'form.notes'});
 # $ENV{'form.version'});      $scrout.=&searchphrasefield('abstract','abstract',$ENV{'form.abstract'});
     $scrout.=&searchphrasefield('Limit by notes','notes',      # Hack - an empty table row.
  $ENV{'form.notes'});      $scrout.="<tr><td>&nbsp;</td><td>&nbsp;</td></tr>\n";
     $scrout.=&searchphrasefield('Limit by abstract','abstract',      $scrout.=&searchphrasefield('file<br />extension','mime',
  $ENV{'form.abstract'});                          $ENV{'form.mime'});
     $ENV{'form.mime'}='any' unless length($ENV{'form.mime'});      $scrout.="<tr><td>&nbsp;</td><td>&nbsp;</td></tr>\n";
     $scrout.=&selectbox('Limit by MIME type','mime',      $scrout.=&searchphrasefield('publisher<br />owner','owner',
  $ENV{'form.mime'},   $ENV{'form.owner'});
  'any','Any type',      $scrout.="</table>\n";
  \&{Apache::loncommon::filedescriptionex},      $ENV{'form.category'}='any' unless length($ENV{'form.category'});
  (&Apache::loncommon::fileextensions));      $scrout.=&selectbox('File Category','category',
    $ENV{'form.category'},
    'any','Any category',
    undef,
    (&Apache::loncommon::filecategories()));
     $ENV{'form.language'}='any' unless length($ENV{'form.language'});      $ENV{'form.language'}='any' unless length($ENV{'form.language'});
       #----------------------------------------------------------------
       # Allow restriction to multiple domains.
       #   I make the crazy assumption that there will never be a domain 'any'.
       #
       $ENV{'form.domains'} = 'any' if (! exists($ENV{'form.domains'}));
       my @allowed_domains = (ref($ENV{'form.domains'}) ? @{$ENV{'form.domains'}} 
                              :  ($ENV{'form.domains'}) );
       my %domain_hash = ();
       foreach (@allowed_domains) {
           $domain_hash{$_}++;
       }
       my @domains =&Apache::loncommon::get_domains();
       # adjust the size of the select box
       my $size = 4;
       my $size = (scalar @domains < ($size - 1) ? scalar @domains + 1 : $size);
       # standalone machines do not get to choose a domain to search.
       if ((scalar @domains) == 1) {
           $scrout .='<input type="hidden" name="domains" value="any" />'."\n";
       } else {
           $scrout.="\n".'<font color="#800000" face="helvetica"><b>'.
               'DOMAINS</b></font><br />'.
                   '<select name="domains" size="'.$size.'" multiple>'."\n".
                       '<option name="any" value="any" '.
                           ($domain_hash{'any'}? 'selected ' :'').
                           '>all domains</option>'."\n";
           foreach my $dom (sort @domains) {
               $scrout.="<option name=\"$dom\" ".
                   ($domain_hash{$dom} ? 'selected ' :'').">$dom</option>\n";
           }
           $scrout.="</select>\n";
       }
       #----------------------------------------------------------------
     $scrout.=&selectbox('Limit by language','language',      $scrout.=&selectbox('Limit by language','language',
  $ENV{'form.language'},'any','Any Language',   $ENV{'form.language'},'any','Any Language',
  \&{Apache::loncommon::languagedescription},   \&{Apache::loncommon::languagedescription},
Line 455  LASTREVISIONDATEEND Line 494  LASTREVISIONDATEEND
  $ENV{'form.lastrevisiondateend_year'},   $ENV{'form.lastrevisiondateend_year'},
  );   );
     $scrout.='</p>';      $scrout.='</p>';
     $scrout.=&searchphrasefield('Limit by publisher/owner','owner',  
  $ENV{'form.owner'});  
     $ENV{'form.copyright'}='any' unless length($ENV{'form.copyright'});      $ENV{'form.copyright'}='any' unless length($ENV{'form.copyright'});
     $scrout.=&selectbox('Limit by copyright/distribution','copyright',      $scrout.=&selectbox('Limit by copyright/distribution','copyright',
  $ENV{'form.copyright'},   $ENV{'form.copyright'},
Line 485  in a fielded listing for each record res Line 522  in a fielded listing for each record res
 CUSTOMSHOW  CUSTOMSHOW
     $scrout.=&simpletextfield('customshow',$ENV{'form.customshow'});      $scrout.=&simpletextfield('customshow',$ENV{'form.customshow'});
     $scrout.=<<ENDDOCUMENT;      $scrout.=<<ENDDOCUMENT;
 <p>  $advanced_buttons
 <input type="submit" name="advancedsubmit" value='SEARCH' />  
 <input type="reset" name="reset" value='RESET' />  
 $closebutton  
 <!-- advance view select -->  
 <select name='advancedviewselect'>  
 <option value='Detailed Citation View' selected="true">  
 Detailed Citation View</option>  
 <option value='Summary View'>Summary View</option>  
 <option value='Fielded Format'>Fielded Format</option>  
 <option value='XML/SGML'>XML/SGML</option>  
 </select>  
 <!-- end of advanced view select -->  
 <input type="button" value="HELP" onClick="openhelp()" />  
 </p>  
 </form>  </form>
 </body>  </body>
 </html>  </html>
Line 524  to be somewhat persistent. Line 547  to be somewhat persistent.
 ######################################################################  ######################################################################
   
 sub make_persistent {  sub make_persistent {
       my %save = %{shift()};
     my $persistent='';      my $persistent='';
     foreach (keys %ENV) {      foreach (keys %save) {
  if (/^form\./ && !/submit/) {   if (/^form\./ && !/submit/) {
     my $name=$_;      my $name=$_;
     my $key=$name;              my @values = (ref($save{$name}) ? @{$save{$name}} : ($save{$name}));
     $ENV{$key}=~s/\'//g; # do not mess with html field syntax  
     $name=~s/^form\.//;      $name=~s/^form\.//;
     $persistent.=<<END;              foreach (@values) {
 <input type="hidden" name="$name" value="$ENV{$key}" />                  s/\"/\'/g; # do not mess with html field syntax
                   $persistent.=<<END;
   <input type="hidden" name="$name" value="$_" />
 END  END
               }
         }          }
     }      }
     return $persistent;      return $persistent;
Line 579  the day, month, and year. Line 605  the day, month, and year.
   
 =item &selectbox()  =item &selectbox()
   
 Returns html selection form.  Returns a scalar containing an html <select> form.  
   
   Inputs: 
   
   =over 4
   
   =item $title 
   
   Printed above the select box, in uppercase.  If undefined, only a select
   box will be returned, with no additional html.
   
   =item $name 
   
   The name element of the <select> tag.
   
   =item $default 
   
   The default value of the form.  Can be $anyvalue, or in @idlist.
   
   =item $anyvalue 
   
   The <option value="..."> used to indicate a default of 
   none of the values.  Can be undef.
   
   =item $anytag 
   
   The text associate with $anyvalue above.
   
   =item $functionref 
   
   Each element in @idlist will be passed as a parameter 
   to the function referenced here.  The return value of the function should
   be a scalar description of the items.  If this value is undefined the 
   description of each item in @idlist will be the item name.
   
   =item @idlist 
   
   The items to be selected from.  One of these or $anyvalue will be the 
   value returned by the form element, $ENV{form.$name}.
   
   =back
   
 =back   =back 
   
Line 598  sub simpletextfield { Line 664  sub simpletextfield {
 sub simplecheckbox {  sub simplecheckbox {
     my ($name,$value)=@_;      my ($name,$value)=@_;
     my $checked='';      my $checked='';
     $checked="CHECKED" if $value eq 'on';      $checked="checked" if $value eq 'on';
     return '<input type="checkbox" name="'.$name.'" '. $checked . ' />';      return '<input type="checkbox" name="'.$name.'" '. $checked . ' />';
 }  }
   
 sub searchphrasefield {  sub searchphrasefield {
     my ($title,$name,$value)=@_;      my ($title,$name,$value)=@_;
     my $instruction=<<END;  
 Enter terms or phrases separated by search operators such as AND, OR, or NOT.  
 END  
     my $uctitle=uc($title);      my $uctitle=uc($title);
     return "\n".      return '<tr><td><font color="#800000" face="helvetica">'.
         '<p><font color="#800000" face="helvetica"><b>'.$uctitle.':</b>'.          '<b>'.$uctitle.':&nbsp;</b></font></td><td>'.
         "</FONT> $instruction<br />".&simpletextfield($name,$value,80);                  &simpletextfield($name,$value,50)."</td></tr>\n";
 }  }
   
 sub dateboxes {  sub dateboxes {
Line 658  END Line 721  END
 }  }
   
 sub selectbox {  sub selectbox {
     my ($title,$name,$value,$anyvalue,$anytag,$functionref,@idlist)=@_;      my ($title,$name,$default,$anyvalue,$anytag,$functionref,@idlist)=@_;
     my $uctitle=uc($title);      if (! defined($functionref)) { $functionref = sub { $_[0]}; }
     my $selout="\n".'<p><font color="#800000" face="helvetica">'.      my $selout='';
         '<b>'.$uctitle.':</b></font><br /><select name="'.$name.'">';      if (defined($title)) {
     foreach ($anyvalue,@idlist) {          my $uctitle=uc($title);
           $selout="\n".'<p><font color="#800000" face="helvetica">'.
               '<b>'.$uctitle.': </b></font>';
       }
       $selout .= '<select name="'.$name.'">';
       unshift @idlist,$anyvalue if (defined($anyvalue));
       foreach (@idlist) {
         $selout.='<option value="'.$_.'"';          $selout.='<option value="'.$_.'"';
         if ($_ eq $value and !/^any$/) {          if ($_ eq $default and !/^any$/) {
     $selout.=' selected >'.&{$functionref}($_).'</option>';      $selout.=' selected >'.&{$functionref}($_).'</option>';
  }   }
  elsif ($_ eq $value and /^$anyvalue$/) {   elsif ($_ eq $default and /^$anyvalue$/) {
     $selout.=' selected >'.$anytag.'</option>';      $selout.=' selected >'.$anytag.'</option>';
  }   }
         else {$selout.='>'.&{$functionref}($_).'</option>';}          else {$selout.='>'.&{$functionref}($_).'</option>';}
     }      }
     return $selout.'</select>';      return $selout.'</select>'.(defined($title)?'</p>':' ');
 }  }
   
 ######################################################################  ######################################################################
Line 680  sub selectbox { Line 749  sub selectbox {
   
 =pod   =pod 
   
 =item &advancedsearch()  =item &parse_advanced_search()
   
   Parse advanced search form and return the following:
   
   =over 4
   
   =item $query Scalar containing an SQL query.
   
   =item $customquery Scalar containing a custom query.
   
 Parse advanced search results.  =item $customshow Scalar containing commands to show custom metadata.
   
   =item $libraries_to_query Reference to array of domains to search.
   
   =back
   
 =cut  =cut
   
 ######################################################################  ######################################################################
 ######################################################################  ######################################################################
 sub advancedsearch {  sub parse_advanced_search {
     my ($r,$envhash,$hidden)=@_;      my ($r)=@_;
     my %ENV=%{$envhash};  
     my $fillflag=0;      my $fillflag=0;
     # Clean up fields for safety      # Clean up fields for safety
     for my $field ('title','author','subject','keywords','url','version',      for my $field ('title','author','subject','keywords','url','version',
Line 701  sub advancedsearch { Line 781  sub advancedsearch {
    'lastrevisiondatestart_year','lastrevisiondateend_month',     'lastrevisiondatestart_year','lastrevisiondateend_month',
    'lastrevisiondateend_day','lastrevisiondateend_year',     'lastrevisiondateend_day','lastrevisiondateend_year',
    'notes','abstract','mime','language','owner',     'notes','abstract','mime','language','owner',
    'custommetadata','customshow') {     'custommetadata','customshow','category') {
  $ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g;   $ENV{"form.$field"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
     }      }
     foreach ('mode','form','element') {      foreach ('mode','form','element') {
Line 710  sub advancedsearch { Line 790  sub advancedsearch {
  $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});   $ENV{"form.$_"}=&Apache::lonnet::unescape($ENV{"form.$_"});
  $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;   $ENV{"form.$_"}=~s/[^\w\/\s\(\)\=\-\"\']//g;
     }      }
       # Preprocess the category form element.
       if ($ENV{'form.category'} ne 'any') {
           my @extensions = &Apache::loncommon::filecategorytypes
               ($ENV{'form.category'});
           $ENV{'form.mime'} = join ' OR ',@extensions;
       }
     # Check to see if enough information was filled in      # Check to see if enough information was filled in
     for my $field ('title','author','subject','keywords','url','version',      for my $field ('title','author','subject','keywords','url','version',
    'notes','abstract','mime','language','owner',     'notes','abstract','mime','language','owner',
Line 720  sub advancedsearch { Line 806  sub advancedsearch {
     }      }
     unless ($fillflag) {      unless ($fillflag) {
  &output_blank_field_error($r);   &output_blank_field_error($r);
  return OK;   return ;
     }      }
     # Turn the form input into a SQL-based query      # Turn the form input into a SQL-based query
     my $query='';      my $query='';
     my @queries;      my @queries;
     # Evaluate logical expression AND/OR/NOT phrase fields.      # Evaluate logical expression AND/OR/NOT phrase fields.
     foreach my $field ('title','author','subject','notes','abstract','url',      foreach my $field ('title','author','subject','notes','abstract','url',
        'keywords','version','owner') {         'keywords','version','owner','mime') {
  if ($ENV{'form.'.$field}) {   if ($ENV{'form.'.$field}) {
     push @queries,&build_SQL_query($field,$ENV{'form.'.$field});      push @queries,&build_SQL_query($field,$ENV{'form.'.$field});
  }          }
       }
       # I dislike the hack below.
       if ($ENV{'form.category'}) {
           $ENV{'form.mime'}='';
     }      }
     # Evaluate option lists      # Evaluate option lists
     if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') {      if ($ENV{'form.language'} and $ENV{'form.language'} ne 'any') {
  push @queries,"(language like \"$ENV{'form.language'}\")";   push @queries,"(language like \"$ENV{'form.language'}\")";
     }      }
     if ($ENV{'form.mime'} and $ENV{'form.mime'} ne 'any') {  
  push @queries,"(mime like \"$ENV{'form.mime'}\")";  
     }  
     if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') {      if ($ENV{'form.copyright'} and $ENV{'form.copyright'} ne 'any') {
  push @queries,"(copyright like \"$ENV{'form.copyright'}\")";   push @queries,"(copyright like \"$ENV{'form.copyright'}\")";
     }      }
Line 760  sub advancedsearch { Line 847  sub advancedsearch {
     # Test to see if date windows are legitimate      # Test to see if date windows are legitimate
     if ($datequery=~/^Incorrect/) {      if ($datequery=~/^Incorrect/) {
  &output_date_error($r,$datequery);   &output_date_error($r,$datequery);
  return OK;   return ;
     }      }
     elsif ($datequery) {      elsif ($datequery) {
  push @queries,$datequery;   push @queries,$datequery;
     }      }
     # Process form information for custom metadata querying      # Process form information for custom metadata querying
     my $customquery='';      my $customquery=undef;
     if ($ENV{'form.custommetadata'}) {      if ($ENV{'form.custommetadata'}) {
  $customquery=&build_custommetadata_query('custommetadata',   $customquery=&build_custommetadata_query('custommetadata',
       $ENV{'form.custommetadata'});        $ENV{'form.custommetadata'});
     }      }
     my $customshow='';      my $customshow=undef;
     if ($ENV{'form.customshow'}) {      if ($ENV{'form.customshow'}) {
  $customshow=$ENV{'form.customshow'};   $customshow=$ENV{'form.customshow'};
  $customshow=~s/[^\w\s]//g;   $customshow=~s/[^\w\s]//g;
  my @fields=split(/\s+/,$customshow);   my @fields=split(/\s+/,$customshow);
  $customshow=join(" ",@fields);   $customshow=join(" ",@fields);
     }      }
     # Send query statements over the network to be processed by either the SQL      ## ---------------------------------------------------------------
     # database or a recursive scheme of 'grep'-like actions (for custom      ## Deal with restrictions to given domains
     # metadata).      ## 
       my $libraries_to_query = undef;
       # $ENV{'form.domains'} can be either a scalar or an array reference.
       # We need an array.
       my @allowed_domains = (ref($ENV{'form.domains'}) ? @{$ENV{'form.domains'}} 
                              :  ($ENV{'form.domains'}) );
       my %domain_hash = ();
       foreach (@allowed_domains) {
           $domain_hash{$_}++;
       }
       foreach (keys(%Apache::lonnet::libserv)) {
           if ($_ eq 'any') {
               $libraries_to_query = undef;
               last;
           }
           if (exists($domain_hash{$Apache::lonnet::hostdom{$_}})) {
               push @$libraries_to_query,$_;
           }
       }
       #
     if (@queries) {      if (@queries) {
  $query=join(" AND ",@queries);   $query=join(" AND ",@queries);
  $query="select * from metadata where $query";   $query="select * from metadata where $query";
  my $reply; # reply hash reference  
  unless ($customquery or $customshow) {  
     $reply=&Apache::lonnet::metadata_query($query);  
  }  
  else {  
     $reply=&Apache::lonnet::metadata_query($query,  
    $customquery,$customshow);  
  }  
  &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden);  
         return OK;  
     } elsif ($customquery) {      } elsif ($customquery) {
  my $reply; # reply hash reference          $query = '';
  $reply=&Apache::lonnet::metadata_query('',  
        $customquery,$customshow);  
  &output_results('Advanced',$r,$envhash,$customquery,$reply,$hidden);  
         return OK;  
     }      }
     # should not get to this point      return ($query,$customquery,$customshow,$libraries_to_query);
     return 'Error.  Should not have gone to this point.';  
 }  }
   
 ######################################################################  ######################################################################
Line 810  sub advancedsearch { Line 901  sub advancedsearch {
   
 =pod   =pod 
   
 =item &basicsearch()   =item &parse_basic_search() 
   
 Parse basic search form.  Parse the basic search form and return a scalar containing an sql query.
   
 =cut  =cut
   
 ######################################################################  ######################################################################
 ######################################################################  ######################################################################
 sub basicsearch {  sub parse_basic_search {
     my ($r,$envhash,$hidden)=@_;      my ($r)=@_;
     my %ENV=%{$envhash};  
     # Clean up fields for safety      # Clean up fields for safety
     for my $field ('basicexp') {      for my $field ('basicexp') {
  $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g;   $ENV{"form.$field"}=~s/[^\w\s\(\)\-]//g;
Line 846  sub basicsearch { Line 936  sub basicsearch {
     $concatarg='title' if $ENV{'form.titleonly'};      $concatarg='title' if $ENV{'form.titleonly'};
   
     $query=&build_SQL_query('concat('.$concatarg.')',$ENV{'form.'.'basicexp'});      $query=&build_SQL_query('concat('.$concatarg.')',$ENV{'form.'.'basicexp'});
       return 'select * from metadata where '.$query;
     # Get reply (either a hash reference to filehandles or bad connection)  
 #    &Apache::lonnet::logthis("metadata query started:".time);  
     my $reply=&Apache::lonnet::metadata_query('select * from metadata where '.$query);  
 #    &Apache::lonnet::logthis("metadata query finished:".time);  
     # Output search results  
   
     &output_results('Basic',$r,$envhash,$query,$reply,$hidden);  
   
     return OK;  
 }  }
   
   
Line 1045  contacted, etc.) Line 1126  contacted, etc.)
 sub output_results {  sub output_results {
 #    &Apache::lonnet::logthis("output_results:".time);  #    &Apache::lonnet::logthis("output_results:".time);
     my $fnum; # search result counter      my $fnum; # search result counter
     my ($mode,$r,$envhash,$query,$replyref,$hidden)=@_;      my ($mode,$r,$replyref,$hidden)=@_;
     my %ENV=%{$envhash};  
     my %rhash=%{$replyref};      my %rhash=%{$replyref};
     my $compiledresult='';      my $compiledresult='';
     my $timeremain=300; # (seconds)      my $timeremain=300; # (seconds)
     my $elapsetime=0;      my $elapsetime=0;
     my $resultflag=0;      my $resultflag=0;
     my $tflag=1;      my $tflag=1;
       ##
       ## Set viewing function
       ##
       my $viewfunction = $Views{$ENV{'form.viewselect'}};
       if (!defined($viewfunction)) {
           $r->print("Internal Error - Bad view selected.\n");
           $r->rflush();
           return;
       }
     #      #
     # make query information persistent to allow for subsequent revision      # make query information persistent to allow for subsequent revision
     my $persistent=&make_persistent();      my $persistent=&make_persistent(\%ENV);
     # spit out the generic header      #
     $r->print(&search_results_header());      # Begin producing output
       $r->print(&search_results_header($mode));
     $r->rflush();      $r->rflush();
       #
     # begin showing the cataloged results      # begin showing the cataloged results
     $r->print(<<CATALOGBEGIN);      my $action = "/adm/searchcat";
 </head>      if ($mode eq 'Basic') { 
 <body bgcolor="#ffffff">          $action .= "?reqinterface=basic";
 <img align=right src=/adm/lonIcons/lonlogos.gif>      } elsif ($mode eq 'Advanced') {
 <h1>Search Catalog</h1>          $action .= "?reqinterface=advanced";
 CATALOGBEGIN      }
         $r->print(<<CATALOGCONTROLS);      $r->print(<<CATALOGCONTROLS);
 <form name='results' method="post" action="/adm/searchcat">  <form name='results' method="post" action="$action">
 $hidden  $hidden
 <input type='hidden' name='acts' value='' />  <input type='hidden' name='acts' value='' />
 <input type='button' value='Revise search request'  <input type='button' value='Revise search request'
Line 1076  $importbutton Line 1167  $importbutton
 $closebutton  $closebutton
 $persistent  $persistent
 <hr />  <hr />
 <h3>Search Query</h3>  
 CATALOGCONTROLS  CATALOGCONTROLS
     #      #
     # Remind them what they searched for  
     #  
     if ($mode eq 'Basic') {  
  $r->print('<p><b>Basic search:</b> '.$ENV{'form.basicexp'}.'</p>');  
     } elsif ($mode eq 'Advanced') {  
  $r->print('<p><b>Advanced search</b> '.$query.'</p>');  
     }  
     $r->print('<h3>Search Results</h3>');  
     $r->rflush();  
     #  
     # make the pop-up window for status      # make the pop-up window for status
     #  
     $r->print(&make_popwin(%rhash));      $r->print(&make_popwin(%rhash));
     $r->rflush();      $r->rflush();
     ##      ##
Line 1143  CATALOGCONTROLS Line 1222  CATALOGCONTROLS
                             return OK;                              return OK;
                         }                          }
                         @results=<$fh> if $fh;                          @results=<$fh> if $fh;
                         $hitcount{$rkey}=@results+0;                          my $hits =@results;
                         &popwin_js($r,'popwin.hc["'.$rkey.'"]='.                          &popwin_js($r,'popwin.hc["'.$rkey.'"]='.
                                    $hitcount{$rkey}.';');                                     $hits.';');
                         $hitcountsum+=$hitcount{$rkey};                          $hitcountsum+=$hits;
                         &popwin_js($r,'popwin.document.forms.popremain.'.                          &popwin_js($r,'popwin.document.forms.popremain.'.
                                    'numhits.value='.$hitcountsum.';');                                     'numhits.value='.$hitcountsum.';');
                     } else {                      } else {
Line 1167  CATALOGCONTROLS Line 1246  CATALOGCONTROLS
  } # end of if ($reply eq 'con_lost') else statement   } # end of if ($reply eq 'con_lost') else statement
         my %Fields = undef;     # Holds the data to be sent to the various           my %Fields = undef;     # Holds the data to be sent to the various 
                                 # *_view routines.                                  # *_view routines.
         my ($extrashow,$customfields,$customhash) = &handle_custom_fields(\@results);          my ($extrashow,$customfields,$customhash) = 
                                       &handle_custom_fields(\@results);
         my @customfields = @$customfields;          my @customfields = @$customfields;
         my %customhash   = %$customhash;          my %customhash   = %$customhash;
  untie %hash if (keys %hash);   untie %hash if (keys %hash);
Line 1184  CATALOGCONTROLS Line 1264  CATALOGCONTROLS
  chomp $result;   chomp $result;
  next unless $result;   next unless $result;
                 %Fields = &parse_raw_result($result,$rkey);                  %Fields = &parse_raw_result($result,$rkey);
                   #
                   # Check copyright tags and skip results the user cannot use
                   my (undef,undef,$resdom,$resname) = split('/',$Fields{'url'});
                   # Check for priv
                   if (($Fields{'copyright'} eq 'priv') && 
                       (($ENV{'user.name'} ne $resname) &&
                        ($ENV{'user.domain'} ne $resdom))) {
                       next;
                   }
                   # Check for domain
                   if (($Fields{'copyright'} eq 'domain') &&
                       ($ENV{'user.domain'} ne $resdom)) {
                       next;
                   }
                   #
  $Fields{'extrashow'}=$extrashow;   $Fields{'extrashow'}=$extrashow;
  if ($extrashow) {   if ($extrashow) {
     foreach my $field (@customfields) {      foreach my $field (@customfields) {
Line 1192  CATALOGCONTROLS Line 1287  CATALOGCONTROLS
                         $Fields{'extrashow'}=~s/\<\!\-\- $field \-\-\>/ $value/g;                          $Fields{'extrashow'}=~s/\<\!\-\- $field \-\-\>/ $value/g;
                     }                      }
                 }                  }
                 if ($compiledresult or $servercount!=$servernum) {  
                     $compiledresult.="<hr align='left' width='200' noshade />";  
                 }  
                 $compiledresult.="\n<p>\n";                  $compiledresult.="\n<p>\n";
                 if ($ENV{'form.catalogmode'} eq 'interactive') {                  if ($ENV{'form.catalogmode'} eq 'interactive') {
                     my $titleesc=$Fields{'title'};                      my $titleesc=$Fields{'title'};
Line 1221  END Line 1313  END
 # <input type="hidden" name="url$fnum" value="$url" />  # <input type="hidden" name="url$fnum" value="$url" />
                     $fnum++;                      $fnum++;
  }   }
         my $viewselect;                  # Render the result into html
         if ($mode eq 'Basic') {                  $compiledresult.= &$viewfunction(%Fields, hostname => $rkey );
     $viewselect=$ENV{'form.basicviewselect'};                  if ($compiledresult or $servercount!=$servernum) {
  }                      $compiledresult.="<hr align='left' width='200' noshade />";
         elsif ($mode eq 'Advanced') {                  }
     $viewselect=$ENV{'form.advancedviewselect'};  
  }  
         if ($viewselect eq 'Detailed Citation View') {  
     $compiledresult.=&detailed_citation_view  
                         (%Fields, hostname => $rkey );  
  }  
                 elsif ($viewselect eq 'Summary View') {  
     $compiledresult.=&summary_view  
                         (%Fields, hostname => $rkey );  
         }  
                 elsif ($viewselect eq 'Fielded Format') {  
     $compiledresult.=&fielded_format_view  
                         (%Fields, hostname => $rkey );  
         }  
                 elsif ($viewselect eq 'XML/SGML') {  
     $compiledresult.=&xml_sgml_view  
                         (%Fields, hostname => $rkey );  
  }  
             }              }
             untie %hash;              untie %hash;
         }          }
Line 1251  END Line 1325  END
     $resultflag=1;      $resultflag=1;
             $r->print($compiledresult);              $r->print($compiledresult);
  }   }
         my $percent=sprintf('%3.0f',($servercount/$servernum*100));  
       } # End of foreach loop over servers remaining        } # End of foreach loop over servers remaining
     }   # End of big loop - while($serversleft && $timeremain)      }   # End of big loop - while($serversleft && $timeremain)
     unless ($resultflag) {      unless ($resultflag) {
         $r->print("\nThere were no results that matched your query\n");          $r->print("\nThere were no results that matched your query\n");
     }      }
 #    $r->print('<script type="text/javascript">'.'popwin.close()</script>'."\n"); $r->rflush();       $r->print('<script type="text/javascript">'.'popwin.close()</script>'.
                 "\n"); 
     $r->print("</body>\n</html>\n");      $r->print("</body>\n</html>\n");
       $r->rflush(); 
     return;      return;
 }  }
   
Line 1322  sub parse_raw_result { Line 1397  sub parse_raw_result {
         &Apache::loncommon::copyrightdescription($Fields{'copyright'});          &Apache::loncommon::copyrightdescription($Fields{'copyright'});
     $Fields{'mimetag'} =      $Fields{'mimetag'} =
         &Apache::loncommon::filedescription($Fields{'mime'});          &Apache::loncommon::filedescription($Fields{'mime'});
       if ($Fields{'author'}=~/^(\s*|error)$/) {
           $Fields{'author'}="Unknown Author";
       }
     # Put spaces in the keyword list, if needed.      # Put spaces in the keyword list, if needed.
     $Fields{'keywords'}=~ s/,([A-z])/, $1/g;       $Fields{'keywords'}=~ s/,([A-z])/, $1/g; 
     if ($Fields{'title'}=~ /^\s*$/ ) {       if ($Fields{'title'}=~ /^\s*$/ ) { 
Line 1392  sub handle_custom_fields { Line 1470  sub handle_custom_fields {
   
 =item &search_results_header  =item &search_results_header
   
 Output the proper javascript code to deal with different calling modes.  Output the proper html headers and javascript code to deal with different 
   calling modes.
   
   Takes most inputs directly from %ENV, except $mode.  
   
   =over 4
   
   =item $mode is either (at this writing) 'Basic' or 'Advanced'
   
 Takes inputs directly from from %ENV.  The following environment variables  =back
 are checked:  
   The following environment variables are checked:
   
 =over 4  =over 4
   
Line 1418  Checked for existance & 'edit' mode. Line 1504  Checked for existance & 'edit' mode.
 ######################################################################  ######################################################################
 ######################################################################  ######################################################################
 sub search_results_header {  sub search_results_header {
       my ($mode) = @_;
       $mode = lc($mode);
       my $title;
       if ($mode eq 'advanced') {
           $title = "Advanced Search Results";
       } elsif ($mode eq 'basic') {
           $title = "Basic Search Results";
       }
     my $result = '';      my $result = '';
     # output beginning of search page      # output beginning of search page
     $result.=<<BEGINNING;      $result.=<<BEGINNING;
 <html>  <html>
 <head>  <head>
 <title>The LearningOnline Network with CAPA</title>  <title>$title</title>
 BEGINNING  BEGINNING
     # conditional output of script functions dependent on the mode in      # conditional output of script functions dependent on the mode in
     # which the search was invoked      # which the search was invoked
Line 1506  SCRIPT Line 1600  SCRIPT
     }      }
 </script>  </script>
 SCRIPT  SCRIPT
       $result.=<<END;
   </head>
   <body bgcolor="#ffffff">
   <img align=right src=/adm/lonIcons/lonlogos.gif>
   <h1>$title</h1>
   END
     return $result;      return $result;
 }  }
   
Line 1531  sub make_popwin { Line 1631  sub make_popwin {
     # rows of 10 each.  No longer used to index images.      # rows of 10 each.  No longer used to index images.
     my $sn=1;      my $sn=1;
     foreach my $sk (sort keys %rhash) {      foreach my $sk (sort keys %rhash) {
  # '<a href="  
  $grid.="'<a href=\"";   $grid.="'<a href=\"";
  # javascript:displayinfo('+  
  $grid.="javascript:opener.displayinfo('+";   $grid.="javascript:opener.displayinfo('+";
  # "'"+'key  
  $grid.="\"'\"+'";   $grid.="\"'\"+'";
  $grid.=$sk;   $grid.=$sk;
  my $hc;   my $hc;
  if ($rhash{$sk} eq 'con_lost') {   if ($rhash{$sk} eq 'con_lost') {
     $hc="BAD CONNECTION, CONTACT SYSTEM ADMINISTRATOR ";      $hc="BAD CONNECTION ";
  }   }
  else {   else {
     $hc="'+\"'\"+\"+hc['$sk']+\"+\"'\"+'";      $hc="'+\"'\"+\"+hc['$sk']+\"+\"'\"+'";
     $hcinit.="hc[\"$sk\"]=\"not yet connected...\";";      $hcinit.="hc[\"$sk\"]=\"not yet connected...\";";
  }   }
  $grid.=" hitcount=".$hc;   $grid.=" hitcount=".$hc;
  $grid.=" domain=".$hostdomains{$sk};   $grid.=" domain=".$Apache::lonnet::hostdom{$sk};
  $grid.=" IP=".$hostips{$sk};   $grid.=" IP=".$Apache::lonnet::hostip{$sk};
  # '+"'"+'">'+   # '+"'"+'">'+
  $grid.="'+\"'\"+')\">'+";   $grid.="'+\"'\"+')\">'+";
  $grid.="\n";   $grid.="\n";
  $grid.="'<img border=\"0\" name=\"img_".$hostdomains{$sk}.'_'.$sk."\"".   $grid.="'<img border=\"0\" name=\"img_".$Apache::lonnet::hostdom{$sk}.
     " src=\"/adm/lonIcons/srvnull.gif\" alt=\"".$sk."\" /></a>'+\n";              '_'.$sk."\" src=\"/adm/lonIcons/srvnull.gif\" alt=\"".$sk.
                   "\" /></a>'+\n";
  $grid.="'<br />'+\n" unless $sn%10;   $grid.="'<br />'+\n" unless $sn%10;
         $sn++;          $sn++;
     }      }
Line 1628  extra custom metadata to show. Line 1726  extra custom metadata to show.
 sub detailed_citation_view {  sub detailed_citation_view {
     my %values = @_;      my %values = @_;
     my $result=<<END;      my $result=<<END;
 <i>$values{'owner'}</i>, last revised $values{'lastrevisiondate'}  
 <h3><a href="http://$ENV{'HTTP_HOST'}$values{'url'}"   <h3><a href="http://$ENV{'HTTP_HOST'}$values{'url'}" 
     target='search_preview'>$values{'title'}</a></h3>      target='search_preview'>$values{'title'}</a></h3>
 <h3>$values{'author'}</h3>  
 </p>  
 <p>  <p>
 <b>Subject:</b> $values{'subject'}<br />  <b>$values{'author'}</b>, <i>$values{'owner'}</i><br />
 <b>Keyword(s):</b> $values{'keywords'}<br />  
 <b>Notes:</b> $values{'notes'}<br />  <b>Subject:       </b> $values{'subject'}<br />
 <b>MIME Type:</b>  <b>Keyword(s):    </b> $values{'keywords'}<br />
 END  <b>Notes:         </b> $values{'notes'}<br />
     $result.=&Apache::loncommon::filedescription($values{'mime'});  <b>MIME Type:     </b> $values{'mimetag'}<br />
     $result.=<<END;  <b>Language:      </b> $values{'language'}<br />
 <br />  <b>Copyright/Distribution:</b> $values{'cprtag'}<br />
 <b>Language:</b>   
 END  
     $result.=&Apache::loncommon::languagedescription($values{'lang'});  
     $result.=<<END;  
 <br />  
 <b>Copyright/Distribution:</b>   
 END  
     $result.=&Apache::loncommon::copyrightdescription($values{'copyright'});  
     $result.=<<END;  
 <br />  
 </p>  </p>
 $values{'extrashow'}  $values{'extrashow'}
 <p>  <p>
Line 1805  sub filled { Line 1890  sub filled {
 sub output_blank_field_error {  sub output_blank_field_error {
     my ($r)=@_;      my ($r)=@_;
     # make query information persistent to allow for subsequent revision      # make query information persistent to allow for subsequent revision
     my $persistent=&make_persistent();      my $persistent=&make_persistent(\%ENV);
   
     $r->print(<<BEGINNING);      $r->print(<<BEGINNING);
 <html>  <html>
Line 1851  Output a full html page with an error me Line 1936  Output a full html page with an error me
 sub output_date_error {  sub output_date_error {
     my ($r,$message)=@_;      my ($r,$message)=@_;
     # make query information persistent to allow for subsequent revision      # make query information persistent to allow for subsequent revision
     my $persistent=&make_persistent();      my $persistent=&make_persistent(\%ENV);
   
     $r->print(<<RESULTS);      $r->print(<<RESULTS);
 <html>  <html>
Line 1943  input $r, loncapa server id, and an icon Line 2028  input $r, loncapa server id, and an icon
 ######################################################################  ######################################################################
 sub popwin_imgupdate {  sub popwin_imgupdate {
     my ($r,$server,$icon) = @_;      my ($r,$server,$icon) = @_;
     &popwin_js($r,'popwin.document.img_'.$hostdomains{$server}.'_'.$server.'.'.      &popwin_js($r,'popwin.document.img_'.$Apache::lonnet::hostdom{$server}.
        'src="/adm/lonIcons/'.$icon.'";');                 '_'.$server.'.'.'src="/adm/lonIcons/'.$icon.'";');
 }      }    
   
 1;  1;

Removed from v.1.126  
changed lines
  Added in v.1.139


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