home *** CD-ROM | disk | FTP | other *** search
/ rtsi.com / 2014.01.www.rtsi.com.tar / www.rtsi.com / OS9 / FAQ / discus_admin_1357211388 / source / search.pl < prev    next >
Text File  |  2009-11-06  |  34KB  |  994 lines

  1. # FILE: search.pl
  2. # DESCRIPTION: Keyword and New Message searching
  3. #-------------------------------------------------------------------------------
  4. # DISCUS COPYRIGHT NOTICE
  5. #
  6. # Discus is copyright (c) 2002 by DiscusWare, LLC, all rights reserved.
  7. # The use of Discus is governed by the Discus License Agreement which is
  8. # available from the Discus WWW site at:
  9. #    http://www.discusware.com/discus/license
  10. #
  11. # Pursuant to the Discus License Agreement, this copyright notice may not be
  12. # removed or altered in any way.
  13. #-------------------------------------------------------------------------------
  14.  
  15. use strict;
  16. use vars qw($GLOBAL_OPTIONS $DCONF $PARAMS);
  17.  
  18. ###
  19. ### search_control
  20. ###
  21. ### Controls search engine functions
  22. ###
  23.  
  24. sub search_control {
  25.     my $FORMref = defined $_[0] ? $_[0] : parse_form($ENV{'QUERY_STRING'}, $ENV{'CONTENT_LENGTH'});
  26.     my $cookie_str = $_[1];
  27.     dreq("template", "fcn-logs");
  28.     my $sparam = search_form_prepare($FORMref, $cookie_str);
  29.     my ($s_out, $sparam2, $tree) = search_perform($sparam);
  30.     $sparam = $sparam2 if defined $sparam2;
  31.     search_output($s_out, $sparam, $FORMref, $tree) if defined $s_out;
  32.     search_first($FORMref, $sparam);
  33. }
  34.  
  35. ###
  36. ### search_perform
  37. ###
  38. ### Performs a search on the posting logs and/or keyword searching logs,
  39. ### returning the results in appropriate reference
  40. ###
  41.  
  42. sub search_perform {
  43.     my ($sparam) = @_;
  44.     return undef if ! defined $sparam->{thing};
  45.     my $s = undef;
  46.     my $t = undef;
  47.     return ( { cache => $sparam->{thing}->{scache} }) if $sparam->{thing}->{scache};
  48.     return search_keyword_cache($sparam) if $sparam->{thing}->{cache};
  49.     ($s, $t) = search_keyword($sparam) if $sparam->{thing}->{search};
  50.     ($s, $t) = search_logs($sparam) if $sparam->{thing}->{'log'};
  51.     ($s, $t) = search_tree($sparam) if $sparam->{thing}->{tree};
  52.     return ($s, undef, $t);
  53. }
  54.  
  55. ###
  56. ### search_output
  57. ###
  58. ### Outputs the results of a keyword or new message search
  59. ###
  60.  
  61. sub search_output {
  62.     my ($o, $sparam, $FORMref, $tree) = @_;
  63.     search_output_array($o, $sparam, $FORMref, $tree) if ref $o eq "ARRAY";
  64.     search_output_tree($o, $sparam, $FORMref, $tree) if ref $o eq "HASH";
  65. }
  66.  
  67. ###
  68. ### search_tree_info
  69. ###
  70. ### Gets information needed from the tree file for search results
  71. ###
  72.  
  73. sub search_tree_info {
  74.     my ($topic, $tree_in) = @_;
  75.     my $tree = defined $tree_in ? $tree_in : read_tree($topic, { no_lock => 1, no_unlock => 1 });
  76.     my $u = {};
  77.     foreach my $t (@{ $tree }) {
  78.         $u->{$t->{page}} = $t;
  79.     }
  80.     $u->{secure} = -e "$DCONF->{message_dir}/$topic" ? 0 : 1;
  81.     return $u;
  82. }
  83.  
  84. ###
  85. ### search_tree_build_parents
  86. ###
  87. ### Goes backward to build up the parents of a particular page
  88. ###
  89.  
  90. sub search_tree_build_parents {
  91.     my ($page, $tree) = @_;
  92.     my $t = $tree->{$page};
  93.     my @p = ($t);
  94.     until ($t->{parent} == $t->{page} || $t->{parent} == 0) {
  95.         push @p, $tree->{ $t->{parent} };
  96.         $t = $tree->{$t->{parent}};
  97.     }
  98.     @p = reverse @p;
  99.     return \@p;
  100. }
  101.  
  102. ###
  103. ### search_output_array
  104. ###
  105. ### Keyword search output
  106. ###
  107.  
  108. sub search_output_array {
  109.     my ($arr, $sparam, $FORMref, $tree) = @_;
  110.     my $subst = {};
  111.     my $results_per_page = defined $GLOBAL_OPTIONS->{search_results_per_page} ? $GLOBAL_OPTIONS->{search_results_per_page} : 15;
  112.     $results_per_page = $FORMref->{rpp} if ($FORMref->{rpp} > 0 && $GLOBAL_OPTIONS->{search_results_per_page_chooser});
  113.     my $s = $FORMref->{start} == 0 ? 1 : $FORMref->{start};
  114.     my @arr = @{ $arr };
  115.     $subst->{general}->{matches} = scalar(@arr);
  116.     if (scalar(@arr) == 0 && $sparam->{query}->{tree} == 0) {
  117.         my $l = search_stop_word_file();
  118.         my @q = ();
  119.         foreach my $k (keys(%{ $l })) { push @q, quotemeta($k); }
  120.         my $x = join("|", @q);
  121.         my $sstr = $sparam->{query_string};
  122.         $sstr =~ s/(^|\b)($x)(\b|$)//gio;
  123.         $subst->{general}->{too_general} = 1 if $sstr !~ /\S/;
  124.     }
  125.     my $maxscore = $arr[0]->{score} == 0 ? 1 : $arr[0]->{score};
  126.     $subst->{general}->{cache} = $arr[0]->{cache} ? $arr[0]->{cache} : $sparam->{cache};
  127.     $subst->{general}->{query_string} = $sparam->{query_string};
  128.     $subst->{general}->{perpage} = $results_per_page;
  129.     $subst->{general}->{lower} = int($s);
  130.     $subst->{general}->{lower} = 1 if $subst->{general}->{lower} < 1;
  131.     $subst->{general}->{start} = $s;
  132.     $subst->{general}->{prev} = $s-$results_per_page;
  133.     $subst->{general}->{next} = $s+$results_per_page;
  134.     $subst->{general}->{prev} = 1 if $subst->{general}->{prev} <= 1;
  135.     $subst->{general}->{upper} = $subst->{general}->{lower} + $results_per_page - 1;
  136.     $subst->{general}->{upper} = $subst->{general}->{matches} if $subst->{general}->{matches} < $subst->{general}->{upper};
  137.     my @arout = ();
  138.     my $ctr = $subst->{general}->{lower};
  139.     my @u = ();
  140.     if ($GLOBAL_OPTIONS->{search_results_page_limit} ne "0") {
  141.         @u = splice (@arr, ($subst->{general}->{lower} - 1), (1+$subst->{general}->{upper}-$subst->{general}->{lower}));
  142.     } else {
  143.         @u = @arr;
  144.         $subst->{general}->{lower} = 1;
  145.         $subst->{general}->{start} = 1;
  146.         $subst->{general}->{upper} = $subst->{general}->{matches};
  147.         $subst->{general}->{perpage} = $subst->{general}->{matches};
  148.         $subst->{general}->{prev} = 0;
  149.         $subst->{general}->{next} = $subst->{general}->{matches}+1;
  150.     }
  151.     my $tinfo = {};
  152.     foreach my $r (@u) {
  153.         next if $r->{topic} == 0;
  154.         my $i = {};
  155.         $i->{number} = $ctr++;
  156.         $tinfo->{$r->{topic}} = search_tree_info($r->{topic}, $tree->{$r->{topic}}) if ! defined $tinfo->{$r->{topic}};
  157.         $i->{parents} = search_tree_build_parents($r->{page}, $tinfo->{$r->{topic}});
  158.         $i->{topic} = $r->{topic};
  159.         $i->{page} = $r->{page};
  160.         $i->{secure} = $tinfo->{$r->{topic}}->{secure};
  161.         if ($r->{url} ne "" && $r->{param} =~ m|^LINK:(.*?)$|) {
  162.             $i->{url} = $r->{url};
  163.             $i->{target} = $1;
  164.         }
  165.         my @j = ();
  166.         if ($r->{match}) {
  167.             foreach my $q (@{ $r->{match} }) {
  168.                 my $i2 = {};
  169.                 $i2->{postindex} = $q->{postindex};
  170.                 $i2->{'time'} = $q->{'time'};
  171.                 $i2->{text} = search_keyword_result_line($q->{text}, $sparam);
  172.                 push @j, $i2;
  173.             }
  174.             $i->{recent} = (sort { $b->{'time'} <=> $a->{'time'} } @{$r->{match}})[0]->{'time'};
  175.             $i->{matchnum} = scalar(@{ $r->{match} });
  176.             $i->{last_poster} = $r->{last_poster};
  177.         } elsif ($r->{lastmod}) {
  178.             $i->{lastmod} = $r->{lastmod};
  179.             $i->{post_count} = 0 + scalar(split(/,/, $r->{post_list}));
  180.             $i->{last_poster} = $r->{last_poster};
  181.         } elsif ($r->{'time'}) {
  182.             $i->{recent} = $r->{'time'};
  183.         }
  184.         $i->{matches} = \@j;
  185.         $i->{score} = int(100*$r->{score}/$maxscore);
  186.         push @arout, $i;
  187.     }
  188.     $subst->{array} = \@arout;
  189.     screen_out("srchres1", $subst, $sparam->{stuff}->{cookie_str});
  190. }
  191.  
  192. ###
  193. ### search_keyword_result_line
  194. ###
  195. ### Finds the words from your query that matched and presents an output string
  196. ###
  197.  
  198. sub search_keyword_result_line {
  199.     my ($text, $o) = @_;
  200.     my @r = search_prepare_query($o, 1);
  201.     my @q = @{ $r[0] };
  202.     push @q, @{ $r[2] };
  203.     my $str = join("|", @q);
  204.     $str = join("", "(", $str, ")");
  205.     $str = join($str, '(^|\b)', '(\b|$)') if $o->{search_opt}->{whole};
  206.     my $k = $o->{search_opt}->{sensitive} ? "" : "(?i)";
  207.     my $u = ""; my $c = 0;
  208.     while ($text =~ /$k(^|\b)(.{0,20})($str)(.{0,20})(\b|$)/) {
  209.         $text = $'; $u .= join("", $&, " ... ");
  210.         $c++; last if $c >= 2;
  211.     }
  212.     $u =~ s/$k$str/\%open$&\%close/g;
  213.     $u =~ s/\s+\.\.\.\s+$//;
  214.     return $u;
  215. }
  216.  
  217. ###
  218. ### search_log_create_cache
  219. ###
  220. ### Creates a cache file for a new message search or other search whose
  221. ### results are displayed in a message tree
  222. ###
  223.  
  224. sub search_log_create_cache {
  225.     my ($s, $tt, $uid) = @_;
  226.     return undef if ref $s ne "ARRAY";
  227.     return undef if ref $tt ne "ARRAY";
  228.     return undef if scalar(@{ $tt }) <= 0;
  229.     return undef if scalar(@{ $s }) <= 0;
  230.     if (! -d "$DCONF->{admin_dir}/msg_index/searches") {
  231.         unlink "$DCONF->{admin_dir}/msg_index/searches" if -f "$DCONF->{admin_dir}/msg_index/searches";
  232.         mkdir "$DCONF->{admin_dir}/msg_index/searches", oct($DCONF->{perms0777}) || error_message("Directory Error", "Could not create 'searches' subdirectory.  This is probably a permissions problem.  Make sure that the 'msg_index' subdirectory is world writable.");
  233.         chmod(oct($DCONF->{perms0777}), "$DCONF->{admin_dir}/msg_index/searches");
  234.     }
  235.     my $cfile = join("", time, $$, $ENV{REMOTE_ADDR}); $cfile =~ s/\D//g;
  236.     $cfile++ while -r "$DCONF->{admin_dir}/msg_index/searches/$cfile.txt";
  237.     my $file = join("/", $DCONF->{admin_dir}, "msg_index", "searches", "$cfile.txt");
  238.     my @store_keys = ('topic', 'parent', 'page', 'name', 'lastmod', 'param', 'properties', 'level');
  239.     open (FILE, "> $file");
  240.     print FILE "$uid\n";
  241.     foreach my $t (@{ $tt }) {
  242.         my @l = ();
  243. Z1:        foreach my $z (@store_keys) {
  244.             push @l, join("=>", $z, escape($t->{$z}));
  245.         }
  246.         print FILE join("\t", "T", @l), "\n";
  247.     }
  248.     foreach my $t (@{ $s }) {
  249.         my @l = ();
  250. Z:        foreach my $z (keys %{$t}) {
  251.             next Z if $z eq "post_list";
  252.             next Z if $z eq "name" && $t->{datatype} eq "mp";
  253.             next Z if $z eq "linked_text" && $t->{datatype} eq "s";
  254.             next Z if $z eq "unlinked_text" && $t->{datatype} eq "s";
  255.             push @l, join("=>", $z, escape($t->{$z})) if $t->{$z} ne "";
  256.         }
  257.         print FILE join("\t", "S$t->{topic}", @l), "\n";
  258.     }
  259.     close (FILE);
  260.     chmod(oct($DCONF->{perms0666}), "$file");
  261.     $PARAMS->{file_access}->{$file}->{write}++;
  262.     $PARAMS->{files_written}++;
  263.     $PARAMS->{file_access}->{$file}->{read} += 0;
  264.     return $cfile;
  265. }
  266.  
  267. ###
  268. ### search_output_tree
  269. ###
  270. ### Outputs search results in a tree view
  271. ###
  272.  
  273. sub search_output_tree {
  274.     my ($hash_in, $sparam, $FORMref, $trees) = @_;
  275.     my $subst = {};
  276.     if (ref $sparam->{stuff} eq 'HASH' && ref $sparam->{stuff}->{result} eq 'ARRAY') {
  277.         $subst->{general}->{your_last_search_time} = 0 + ($sparam->{stuff}->{result})->[0]->{stime};
  278.         $subst->{general}->{your_last_search_time} = 0 if $subst->{general}->{your_last_search_time} != $sparam->{cutoff};
  279.     }
  280.     $subst->{general}->{result_posts} = 0;
  281.     $subst->{general}->{result_pages} = 0;
  282.     $subst->{general}->{result_topics} = 0;
  283.     $subst->{general}->{new_window} = 1 if $sparam->{preferences} =~ m|g|;
  284.     $subst->{general}->{link_to_page} = 1;
  285.     if ($hash_in->{cache} == 0) {
  286.         my @s = ();
  287.         my @tt = ();
  288.         my $tr = read_tree(undef, { no_lock => 1, no_unlock => 1, zero_ok => 1});
  289.         foreach my $tt (reverse @{ $tr }) {
  290.             my $topic = $tt->{topic};
  291.             if (defined $hash_in->{$topic}) {
  292.                 $subst->{general}->{result_topics}++;
  293.                 $subst->{secure}->{$topic} = -e "$DCONF->{admin_dir}/secure/$topic";
  294.                 unshift @tt, $tt;
  295.                 my $st = defined $trees->{$topic} ? $trees->{$topic} : read_tree($topic, { no_lock => 1, no_unlock => 1, zero_ok => 1});
  296.                 my $p = {};
  297.                 foreach my $z (reverse @{ $st }) {
  298.                     if (defined $hash_in->{$topic}->{$z->{page}} || defined $p->{$z->{page}}) {
  299.                         my %i = %{ $z };
  300.                         $i{datatype} = "s";
  301.                         if (! defined $p->{$z->{parent}}) {
  302.                             if (!($sparam->{query_string} eq "" && $z->{param} =~ /Archive/ && $GLOBAL_OPTIONS->{skip_archives_nm} == 1)) {
  303.                                 $i{last_of_parent} = 1;
  304.                                 $p->{$z->{parent}} = 1;
  305.                             }
  306.                         }
  307.                         $i{expanded} = 1;
  308.                         if (ref $hash_in->{$topic}->{$z->{page}} eq "ARRAY") {
  309.                             $subst->{general}->{result_pages}++;
  310.                             my $c = 0;
  311.                             my %k = map { $_, $c++ } split(/,/, $i{post_list});
  312.                             my @r = sort { $k{$b->{'postindex'}} <=> $k{$a->{'postindex'}} } @{ $hash_in->{$topic}->{$z->{page}} };
  313.                             my $fl = 1;
  314.                             $fl = 0 if $p->{$z->{page}};
  315.                             foreach my $r (@r) {
  316.                                 $subst->{general}->{result_posts}++;
  317.                                 my %l = %{ $z };
  318.                                 $l{datatype} = "mp";
  319.                                 $l{is_last_post} = $fl;
  320.                                 $l{postnum} = $r->{postindex};
  321.                                 $l{linked_text} = $r->{poststr};
  322.                                 $l{posttime} = $r->{'time'};
  323.                                 $l{unlinked_text} = $r->{firstchars};
  324.                                 $l{last_of_parent} = $i{last_of_parent};
  325.                                 $fl = 0;
  326.                                 unshift @s, \%l;
  327.                             }
  328.                         }
  329.                         unshift @s, \%i;
  330.                     }
  331.                 }
  332.             }
  333.         }
  334.         if ($GLOBAL_OPTIONS->{tree_search_no_cache} == 1 || $subst->{general}->{result_posts} <= $GLOBAL_OPTIONS->{tree_search_expand_all_point} ) {
  335.             foreach my $t (@tt) {
  336.                 $t->{expanded} = 1;
  337.             }
  338.             foreach my $ss (@s) {
  339.                 next if ($sparam->{query_string} eq "" && $GLOBAL_OPTIONS->{skip_archives_nm} == 1 && $ss->{param} =~ m|Archive|);
  340.                 push @{ $subst->{subtopic_tree} }, $ss;
  341.             }
  342.             $subst->{general}->{expand_url} = '#" onClick="return false;" e="';
  343.             $subst->{general}->{expand_url_X} = 1;
  344.         } elsif ($subst->{general}->{result_posts} <= $GLOBAL_OPTIONS->{tree_search_cutoff_point}) {
  345.             if (scalar (@tt)) {
  346.                 $tt[0]->{expanded} = 1;
  347.                 $subst->{general}->{topic_shown} = $tt[0]->{topic};
  348.             }
  349.             foreach my $ss (@s) {
  350.                 next if ($sparam->{query_string} eq "" && $GLOBAL_OPTIONS->{skip_archives_nm} == 1 && $ss->{param} =~ m|Archive|);
  351.                 push @{ $subst->{subtopic_tree} }, $ss if $ss->{topic} == $subst->{general}->{topic_shown};
  352.             }
  353.             $subst->{general}->{cache} = search_log_create_cache(\@s, \@tt, $sparam->{stuff}->{uid});
  354.             $subst->{general}->{expand_url} = join("/", $DCONF->{script_url}, "search.$DCONF->{cgi_extension}");
  355.         } else {
  356.             my @p = ();
  357.             $subst->{subtopic_tree} = \@p;
  358.             $subst->{general}->{cache} = search_log_create_cache(\@s, \@tt, $sparam->{stuff}->{uid});
  359.             $subst->{general}->{expand_url} = join("/", $DCONF->{script_url}, "search.$DCONF->{cgi_extension}");
  360.         }
  361.         $subst->{topic_tree} = \@tt;
  362.     } else {
  363.         my $cfile = $hash_in->{cache};
  364.         $cfile =~ s/\D//g;
  365.         my $file = join("/", $DCONF->{admin_dir}, "msg_index", "searches", "$cfile.txt");
  366.         my @s = ();
  367.         my @tt = ();
  368.         my $x = {};
  369.         my $selt = $sparam->{stuff}->{st};
  370.         $selt = 0 if $sparam->{stuff}->{x} eq "c";
  371.         my $tseen = {};
  372.         my $rpage = {};
  373.         if (open (FILE, "< $file")) {
  374.             $PARAMS->{file_access}->{$file}->{write} += 0;
  375.             $PARAMS->{files_read}++;
  376.             $PARAMS->{file_access}->{$file}->{read}++;
  377.             my $uid = <FILE>; chomp $uid;
  378.             if ($DCONF->{pro} && $GLOBAL_OPTIONS->{search_verify_uid} && $sparam->{stuff}->{uid} ne $uid) {
  379.                 close(FILE);
  380.                 error_message(read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_TITLE}, read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_EXPL}, 0, 1);
  381.             }
  382. WFILE:        while (my $f = <FILE>) {
  383.                 chomp $f;
  384.                 my ($type, $other) = split(/\t/, $f, 2);
  385.                 $type = substr($type, 0, 1);
  386.                 my @o = split(/\t/, $other);
  387.                 my $i = {};
  388.                 foreach my $o (@o) {
  389.                     if ($o =~ m|^(\w+)=>(.*)|) {
  390.                         $i->{$1} = unescape($2);
  391.                     }
  392.                 }
  393.                 if ($type eq "S") {
  394.                     push @s, $i if $i->{topic} == $selt;
  395.                     if ($i->{datatype} eq "mp") {
  396.                         $subst->{general}->{result_posts}++;
  397.                         $x->{$i->{page}} = 1;
  398.                     }
  399.                 } else {
  400.                     $tseen->{$i->{topic}} = 1;
  401.                     $i->{expanded} = ($selt == $i->{topic});
  402.                     $subst->{secure}->{$i->{topic}} = -e "$DCONF->{admin_dir}/secure/$i->{topic}";
  403.                     push @tt, $i;
  404.                 }
  405.             }
  406.             close (FILE);
  407.             $subst->{general}->{result_topics} = scalar keys %{$tseen};
  408.         }
  409.         if (scalar(@tt) == 0) {
  410.             error_message(read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_TITLE}, read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_EXPL}, 0, 1);
  411.         }
  412.         $subst->{general}->{topic_shown} = $selt;
  413.         foreach my $ss (@s) {
  414.             next if ($sparam->{query_string} eq "" && $GLOBAL_OPTIONS->{skip_archives_nm} == 1 && $ss->{param} =~ m|Archive|);
  415.             push @{ $subst->{subtopic_tree} }, $ss if $ss->{topic} == $subst->{general}->{topic_shown};
  416.         }
  417.         $subst->{topic_tree} = \@tt;
  418.         $subst->{general}->{cache} = $cfile;
  419.         $subst->{general}->{result_pages} = scalar(keys(%{ $x }));
  420.         $subst->{general}->{expand_url} = join("/", $DCONF->{script_url}, "search.$DCONF->{cgi_extension}");
  421.     }
  422.     $DCONF->{html_url} =~ m%^(https?://.*?)(/|$)%; my $html_server = $1; my $html_after = join("", $2, $');
  423.     $DCONF->{message_url} =~ m%^(https?://.*?)(/|$)%; my $message_server = $1; my $message_after = join("", $2, $');
  424.     $DCONF->{script_url} =~ m%^(https?://.*?)(/|$)%; my $script_server = $1; my $script_after = join("", $2, $');
  425.     if ($script_server eq $html_server) {
  426.         $subst->{general}->{icon_url} = join("/", $html_after, $DCONF->{icon_dir});
  427.         $subst->{general}->{script_url} = $script_after;
  428.     } else {
  429.         $subst->{general}->{icon_url} = join("/", $DCONF->{html_url}, $DCONF->{icon_dir});
  430.         $subst->{general}->{script_url} = $DCONF->{script_url};
  431.     }
  432.     $subst->{general}->{message_url} = $message_server eq $script_server ? $message_after : $DCONF->{message_url};
  433.     $subst->{general}->{screen} = 1;
  434.     screen_out("treeview", $subst, $sparam->{stuff}->{cookie_str});
  435. }
  436.  
  437. ###
  438. ### search_keyword_cache
  439. ###
  440. ### Spits out results from a keyword search cache
  441. ###
  442.  
  443. sub search_keyword_cache {
  444.     my ($sparam, $dbh) = @_;
  445.     dreq("maintain");
  446.     cleaning_remove_old_files("$DCONF->{admin_dir}/msg_index/searches", '^\d+\.txt$', 2, 0);
  447.     my $cachefile = $sparam->{thing}->{cache};
  448.     $cachefile =~ s/\D//g;
  449.     my $u = readfile("$DCONF->{admin_dir}/msg_index/searches/$cachefile.txt", "search_keyword_cache", { no_lock => 1, no_unlock => 1, create => 1, zero_ok => 1 });
  450.     if (! defined $u || ref $u ne "ARRAY" || scalar (@{ $u }) == 0) {
  451.         error_message(read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_TITLE}, read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_EXPL}, 0, 1);
  452.     }
  453.     my $k = shift (@{ $u });
  454.     my ($uid, $qs) = split(/\//, trim(unescape($k)), 2);
  455.     if ($DCONF->{pro} && $GLOBAL_OPTIONS->{search_verify_uid} && $sparam->{stuff}->{uid} ne $uid) {
  456.         error_message(read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_TITLE}, read_language()->{SEARCH_ENGINE_CACHE_EXPIRED_EXPL}, 0, 1);
  457.     }
  458.     my @x = ();
  459.     foreach my $x (@{ $u }) {
  460.         push @x, search_keyword_cache_line_to_hash($x);
  461.     }
  462.     return (\@x, { uid => $uid, query_string => $qs, cache => $cachefile });
  463. }
  464.  
  465. ###
  466. ### search_keyword_create_cache
  467. ###
  468. ### Creates a keyword search cache
  469. ###
  470.  
  471. sub search_keyword_create_cache {
  472.     my ($s, $sparam) = @_;
  473.     return undef if ref $s ne "ARRAY";
  474.     return undef if scalar(@{ $s }) <= 0;
  475.     if (! -d "$DCONF->{admin_dir}/msg_index/searches") {
  476.         unlink "$DCONF->{admin_dir}/msg_index/searches" if -f "$DCONF->{admin_dir}/msg_index/searches";
  477.         mkdir "$DCONF->{admin_dir}/msg_index/searches", oct($DCONF->{perms0777}) || error_message("Directory Error", "Could not create 'searches' subdirectory.  This is probably a permissions problem.  Make sure that the 'msg_index' subdirectory is world writable.");
  478.         chmod(oct($DCONF->{perms0777}), "$DCONF->{admin_dir}/msg_index/searches");
  479.     }
  480.     my @u = ();
  481.     my $u = join("/", $sparam->{stuff}->{uid}, escape($sparam->{query_string}));
  482.     push @u, join("", $u, "\n");
  483.     foreach my $_s (@{ $s }) {
  484.         push @u, search_keyword_cache_hash_to_line($_s);
  485.     }
  486.     my $cfile = join("", time, $$, $ENV{REMOTE_ADDR}); $cfile =~ s/\D//g;
  487.     $cfile++ while -r "$DCONF->{admin_dir}/msg_index/searches/$cfile.txt";
  488.     writefile("$DCONF->{admin_dir}/msg_index/searches/$cfile.txt", \@u, "search_keyword_create_cache", { create => 1, no_lock => 1, no_unlock => 1 });
  489.     chmod(oct($DCONF->{perms0666}), "$DCONF->{admin_dir}/msg_index/searches/$cfile.txt");
  490.     return $cfile;
  491. }
  492.  
  493. ###
  494. ### search_keyword_cache_hash_to_line
  495. ###
  496. ### Converts a keyword cache hash to a line
  497. ###
  498.  
  499. sub search_keyword_cache_hash_to_line {
  500.     my ($hash) = @_;
  501.     my $u = "";
  502.     if ($hash->{match}) {
  503.         my @z = ();
  504.         foreach my $z (@{ $hash->{match} }) {
  505.             push @z, join("=", $z->{postindex}, $z->{'time'}, trim($z->{text}));
  506.         }
  507.         $u = join("\t", "m", @z);
  508.     } elsif ($hash->{lastmod}) {
  509.         $u = join("\t", "t", $hash->{lastmod}, $hash->{post_list});
  510.     }
  511.     my $l = join("\t", $hash->{score}, $hash->{topic}, $hash->{page}, $u);
  512.     $l .= "\n";
  513.     return $l;
  514. }
  515.  
  516. ###
  517. ### search_keyword_cache_line_to_hash
  518. ###
  519. ### Converts a keyword cache line to a hash
  520. ###
  521.  
  522. sub search_keyword_cache_line_to_hash {
  523.     my ($line) = @_;
  524.     chomp $line;
  525.     my $h = {};
  526.     my @u = split(/\t/, $line);
  527.     ($h->{score}, $h->{topic}, $h->{page}, $h->{type}) = splice @u, 0, 4;
  528.     if ($h->{type} eq "m") {
  529.         my @m = ();
  530.         while (my $m = shift @u) {
  531.             my $u = {};
  532.             ($u->{postindex}, $u->{'time'}, $u->{text}) = split(/=/, $m, 3);
  533.             push @m, $u;
  534.         }
  535.         $h->{match} = \@m;
  536.     } elsif ($h->{type} eq "t") {
  537.         ($h->{lastmod}, $h->{post_list}) = splice @u, 0, 2;
  538.     }
  539.     return $h;
  540. }
  541.  
  542. ###
  543. ### search_keyword
  544. ###
  545. ### Searches the keyword search log or database
  546. ###
  547.  
  548. sub search_keyword {
  549.     my ($sparam, $dbh) = @_;
  550.     my ($s, $trees);
  551.     if ($GLOBAL_OPTIONS->{database} && $DCONF->{pro}) {
  552.         dreq("sql-logs-PRO");
  553.         ($s, $trees) = search_keyword_database($sparam);
  554.     } else {
  555.         ($s, $trees) = search_keyword_files($sparam);
  556.     }
  557.     my $cfile = search_keyword_create_cache($s, $sparam);
  558.     $s->[0]->{cache} = $cfile if $cfile;
  559.     return ($s, $trees);
  560. }
  561.  
  562. ###
  563. ### search_logs
  564. ###
  565. ### Searches the posting log
  566. ###
  567.  
  568. sub search_logs {
  569.     my ($sparam, $dbh) = @_;
  570.     return ({}, undef) if ! defined $sparam->{where};
  571.     my $searches = {};
  572.     $searches->{bottom_cutoff} = $sparam->{cutoff};
  573.     $searches->{top_cutoff} = time;
  574.     $searches->{poster_match} = $sparam->{query_string};
  575.     $searches->{query_info} = $sparam;
  576.     $searches->{id} = $sparam->{id};
  577.     my $result = {};
  578.     my $trees = {};
  579.     my $resultsnum = 0;
  580.     my $resultcache = undef;
  581.     dreq("fcn-logs");
  582.     foreach my $ht ( @{ log_read_file($sparam->{where}, undef, $searches) } ) {
  583.         if ($ht->{topic} != $resultsnum) {
  584.             $resultsnum = $ht->{topic};
  585.             $resultcache = undef;
  586.         }
  587.         my ($page, $resultcache0, $treecache0) = search_post_locater($ht->{postindex}, $resultcache, $trees->{$ht->{topic}}, $ht->{topic});
  588.         $resultcache = $resultcache0 if ! defined $resultcache;
  589.         $trees->{$ht->{topic}} = $treecache0 if defined $treecache0;
  590.         next if $page eq "";
  591.         $ht->{page} = $page; $ht->{where} = join("/", $ht->{topic}, $ht->{page});
  592.         push @{ $result->{$ht->{topic}}->{$ht->{page}} }, $ht;
  593.     }
  594.     return ($result, $trees);
  595. }
  596.  
  597. ###
  598. ### search_post_locater
  599. ###
  600. ### Locates a post within a given tree file
  601. ###
  602.  
  603. sub search_post_locater {
  604.     my ($postnum, $result_cache, $treecache, $topic) = @_;
  605.     if (defined $PARAMS->{postloc_db_sth}) {
  606.         return sql_post_locater($topic, $postnum);
  607.     }
  608.     return $result_cache->{$postnum} if $result_cache->{$postnum};
  609.     return undef if ref $result_cache eq 'HASH' && scalar keys %{$result_cache};
  610.     $treecache = read_tree($topic, { no_lock => 1, no_unlock => 1 }) if ! defined $treecache;
  611.     my $result = {};
  612.     foreach my $line (@{ $treecache }) {
  613.         foreach my $post (split(/,/, $line->{post_list})) {
  614.             $result->{$post} = $line->{page};
  615.         }
  616.     }
  617.     return ($result->{$postnum}, $result, $treecache);
  618. }
  619.  
  620. ###
  621. ### search_tree
  622. ###
  623. ### Searches the tree file for subject lines
  624. ###
  625.  
  626. sub search_tree {
  627.     my ($o, $dbh) = @_;
  628.     my @r = search_prepare_query($o);
  629.     my @req = @{ $r[0] };
  630.     my @neg = @{ $r[1] };
  631.     my @opt = @{ $r[2] };
  632.     if (scalar(@req) == 0 && scalar(@opt) == 0) {
  633.         my @l = ();
  634.         return \@l;
  635.     }
  636.     my @k = ();
  637.     my $tr = {};
  638.     foreach my $topic (keys(%{ $o->{where} })) {
  639.         my $tree = read_tree($topic, { no_lock => 1, no_unlock => 1 });
  640.         next if ref $tree ne "ARRAY";
  641.         $tr->{$topic} = $tree;
  642.         my $cutoff_field = $o->{cutoff} > 0 ? 'lastmod' : undef;
  643.         my $cutoff_time = $o->{cutoff} > 0 ? $o->{cutoff} : undef;
  644.         my @u = search_does_it_match(\@req, \@neg, \@opt, 'name', $o->{search_opt}->{sensitive}, $cutoff_field, $cutoff_time, @{ $tree });
  645.         push @k, @u if (scalar(@u) && ref $u[0] eq "HASH");
  646.     }
  647.     @k = sort { $b->{score} <=> $a->{score} } @k;
  648.     return (\@k, $tr) if scalar(@k) == 0;
  649.     $k[0]->{cache} = search_keyword_create_cache(\@k, $o);
  650.     return (\@k, $tr);
  651. }
  652.  
  653. ###
  654. ### search_prepare_query
  655. ###
  656. ### Creates a search query (required, excluded, optional) from a
  657. ### query string
  658. ###
  659.  
  660. sub search_prepare_query {
  661.     my ($o, $flag, $no_stop) = @_;
  662.     my $sstr = $o->{query_string};
  663.     my @req = (); my @opt = (); my @neg = ();
  664.     my $quot = join("|", quotemeta('"'), quotemeta('"'), quotemeta('"'));
  665.     my $plus = join("|", quotemeta('+'), quotemeta('+'));
  666.     my $minus = join("|", quotemeta('-'), quotemeta('-'));
  667.     while ($sstr =~ m%(?:$plus)(?:$quot)(.*?)(?:$quot)%) { push @req, trim($1); $sstr = join("", $`, $'); }
  668.     while ($sstr =~ m%(?:$minus)(?:$quot)(.*?)(?:$quot)%) { push @neg, trim($1); $sstr = join("", $`, $'); }
  669.     while ($sstr =~ m%(?:$quot)(.*?)(?:$quot)%) { push @opt, trim($1); $sstr = join("", $`, $'); }
  670.     while ($sstr =~ m%(?:$plus)(\S+)%) { push @req, trim($1); $sstr = join("", $`, $'); }
  671.     while ($sstr =~ m%(?:$minus)(\S+)%) { push @neg, trim($1); $sstr = join("", $`, $'); }
  672.     while ($sstr =~ m%(\S+)%) { push @opt, trim($1); $sstr = join("", $`, $'); }
  673.     if (! $no_stop) {
  674.         @req = grep (/\S/, map { search_keyword_is_stop_word($_) } @req);
  675.         @opt = grep (/\S/, map { search_keyword_is_stop_word($_) } @opt);
  676.         @neg = grep (/\S/, map { search_keyword_is_stop_word($_) } @neg);
  677.     }
  678.     @req = map { quotemeta($_) } @req;
  679.     @neg = map { quotemeta($_) } @neg;
  680.     @opt = map { quotemeta($_) } @opt;
  681.     return (\@req, \@neg, \@opt) if $flag;
  682.     if ($o->{search_opt}->{whole}) {
  683.         @req = map { join($_, '(^|\b)', '(\b|$)') } @req;
  684.         @neg = map { join($_, '(^|\b)', '(\b|$)') } @neg;
  685.         @opt = map { join($_, '(^|\b)', '(\b|$)') } @opt;
  686.     }
  687.     if ($o->{search_opt}->{and}) {
  688.         push @req, @opt;
  689.         @opt = ();
  690.     }
  691.     return (\@req, \@neg, \@opt);
  692. }
  693.  
  694. ###
  695. ### search_keyword_is_stop_word
  696. ###
  697. ### Sees if a particular word is a 'stop word'
  698. ###
  699.  
  700. sub search_keyword_is_stop_word {
  701.     my ($word) = @_;
  702.     my $swf = search_stop_word_file();
  703.     my $regexp = join("|", map { quotemeta($_) } keys %{$swf});
  704.     my @newword = ();
  705.     foreach my $comp (split(/\s+/, $word)) {
  706.         push @newword, $comp if $comp !~ /^($regexp)$/i;
  707.     }
  708.     return join(" ", @newword);
  709. }
  710.  
  711. ###
  712. ### search_keyword_files
  713. ###
  714. ### Searches the keyword search logs
  715. ###
  716.  
  717. sub search_keyword_files {
  718.     my ($o) = @_;
  719.     my @r = search_prepare_query($o);
  720.     my @req = @{ $r[0] };
  721.     my @neg = @{ $r[1] };
  722.     my @opt = @{ $r[2] };
  723.     if (scalar(@req) == 0 && scalar(@opt) == 0) {
  724.         my @l = ();
  725.         return \@l;
  726.     }
  727.     my $k = {};
  728.     my $results = {};
  729.     my $trees = {};
  730.     my $found_count = 0;
  731. T:    foreach my $topic (keys(%{ $o->{where} })) {
  732.         my $file = join("/", $DCONF->{admin_dir}, "msg_index", "$topic-search.txt");
  733.         $PARAMS->{files_read}++;
  734.         $PARAMS->{file_access}->{$file}->{read} += 1;
  735.         $PARAMS->{file_access}->{$file}->{write} += 0;
  736.         performance_string("< search_keyword_files $file") if $GLOBAL_OPTIONS->{performance_monitoring};
  737.         open (SFILE, "< $file");
  738. F:        while (<SFILE>) {
  739.             my $x = search_line_to_hash($_);
  740.             next F if ($o->{cutoff} && $x->{'time'} < $o->{cutoff});
  741.             my $d = search_does_it_match(\@req, \@neg, \@opt, 'text', $o->{search_opt}->{sensitive}, $x);
  742.             next F if ! $d;
  743.             my ($page, $resultcache, $treecache) = search_post_locater($d->{postindex}, $results->{$topic}, $trees->{$topic}, $topic);
  744.             $trees->{$topic} = $treecache if defined $treecache;
  745.             $results->{$topic} = $resultcache if defined $resultcache;
  746.             next F if $page == 0;
  747.             $d->{page} = $page; $d->{topic} = $topic;
  748.             if ($d) {
  749.                 $found_count += 1 if ! defined $k->{$topic}->{$page};
  750.                 push @{ $k->{$topic}->{$page} }, $d;
  751.                 if ($found_count >= $GLOBAL_OPTIONS->{search_link_limit_value} && $GLOBAL_OPTIONS->{search_link_limit} == 1 && $GLOBAL_OPTIONS->{search_link_limit_value} > 0) {
  752.                     last T;
  753.                 }
  754.             }
  755.         }
  756.         close (SFILE);
  757.     }
  758.     my @k = ();
  759.     foreach my $topic (keys(%{ $k })) {
  760.         my $l = $k->{$topic};
  761.         foreach my $page (keys(%{ $l })) {
  762.             my $u = {};
  763.             $u->{topic} = $topic;
  764.             $u->{page} = $page;
  765.             foreach my $m (sort { $b->{score} <=> $a->{score} } @{ $l->{$page} }) {
  766.                 push @{ $u->{match} }, $m;
  767.                 $u->{score} += $m->{score};
  768.             }
  769.             push @k, $u;
  770.         }
  771.     }
  772.     @k = sort { $b->{score} <=> $a->{score} } @k;
  773.     return (\@k, $trees);
  774. }
  775.  
  776. ###
  777. ### search_does_it_match
  778. ###
  779. ### Checks a line, or series of lines, to see if they match
  780. ###
  781.  
  782. sub search_does_it_match {
  783.     my ($req, $neg, $opt, $k, $sens) = splice @_, 0, 5;
  784.     my @pd = @_;
  785.     my $cutoff_field = undef;
  786.     my $cutoff_time = undef;
  787.     if (ref $pd[0] ne "HASH") {
  788.         ($cutoff_field, $cutoff_time) = splice @pd, 0, 2;
  789.     }
  790.     my $opt_re = join("|", @{ $opt });
  791.     my $neg_re = join("|", @{ $neg });
  792.     $opt_re = join("", "(", $opt_re, ")") if $opt_re ne "";
  793.     $neg_re = join("", "(", $neg_re, ")") if $neg_re ne "";
  794.     $opt_re = join("", "(?i)", $opt_re) if (! $sens && $opt_re);
  795.     $neg_re = join("", "(?i)", $neg_re) if (! $sens && $neg_re);
  796.     my @o = ();
  797.     my @r = @{ $req };
  798.     my $r = scalar(@r);
  799.     my @p = @{ $opt }; push @p, @r;
  800.     if (! $sens) {
  801.         foreach my $p (@p) {
  802.             $p = join("", "(?i)", $p);
  803.         }
  804.         foreach my $p (@r) {
  805.             $p = join("", "(?i)", $p);
  806.         }
  807.     }
  808.     while (! defined $pd[0] && scalar(@pd) > 0) {
  809.         shift @pd;
  810.     }
  811.     my $l = 0;
  812. O:    while (my $i = shift @pd) {
  813.         next if ref $i ne "HASH";
  814.         $l++;
  815.         my $u = $i->{$k};
  816.         next if ($neg_re ne "" && $u =~ /$neg_re/o);
  817.         if ($cutoff_field ne "" && ! $i->{islink}) {
  818.             next if $i->{$cutoff_field} < $cutoff_time;
  819.         }
  820.         if ($r > 0) {
  821. I:            foreach my $rr (@r) {
  822.                 next O if $u !~ /$rr/;
  823.  
  824.             }
  825.         } else {
  826.             next if $u !~ /$opt_re/o;
  827.         }
  828.         my $score = 0;
  829.         my $matches = 0;
  830.         foreach my $rr (@p) {
  831.             if ($u =~ /$rr/) {
  832.                 $score += length($rr);
  833.                 while ($u =~ /$rr/g) {
  834.                     $matches += (length($rr) / length($u));
  835.                 }
  836.             }
  837.         }
  838.         $i->{score} = ($score * (1.5 ** $matches));
  839.         push @o, $i;
  840.     }
  841.     return @o if $l != 1;
  842.     return $o[0];
  843. }
  844.  
  845. ###
  846. ### search_form_prepare
  847. ###
  848. ### Configures the search engine for a search about to be performed based on
  849. ### the input from the searching form.  Note that if you have your own form
  850. ### that you want to use for searching, you can write your own subroutine to
  851. ### turn data from that form into the internal searching language used here
  852. ### and then pass it to the searching processor engine.
  853. ###
  854.  
  855. sub search_form_prepare {
  856.     my ($FORMref, $cookie_str) = @_;
  857.     my $o = {};
  858.     my ($cache, $ckstr, $result, $privcache);
  859.     my $silent = undef;
  860.     if ($FORMref->{nmsm} == 2) {
  861.         $silent = $FORMref->{silent} == 1 ? 1 : 0;
  862.     }
  863.     $FORMref->{COOKIE}->{user} = "" if $FORMref->{nmsm} == 2;
  864.     $FORMref->{password} = $FORMref->{passwd} if ! defined $FORMref->{password};
  865.     if ($DCONF->{pro}) {
  866.         dreq("authwrap-PRO");
  867.         ($cache, $ckstr, $result, $privcache) = user_utility_login_check($FORMref, undef, $silent);
  868.         if ($FORMref->{nmsm} == 2) {
  869.             if (! defined $result || ref $result ne "ARRAY" || scalar(@{ $result }) == 0) {
  870.                 error_message(read_language()->{BPAUTHERROR}, read_language()->{ERROR_AUTHENTICATION_WRONG_USERNAME_AND_PW}, 0, 1);
  871.             }
  872.         }
  873.         $o->{favorites} = $cache->{favorites};
  874.         $o->{preferences} = $cache->{preferences};
  875.         $cookie_str .= $ckstr if $ckstr;
  876.     } elsif ($FORMref->{nmsm} == 2) {
  877.         dreq("authpass");
  878.         my $arg = {};
  879.         $arg->{new_message} = 1 if ($silent == 0 && defined $silent);
  880.         $arg->{silent} = 1 if $silent == 1;
  881.         $result = check_password($FORMref->{username}, $FORMref->{password}, $arg, $FORMref->{COOKIE});
  882.         if (! defined $result || ref $result ne "ARRAY" || scalar(@{ $result }) == 0) {
  883.             error_message(read_language()->{BPAUTHERROR}, read_language()->{ERROR_AUTHENTICATION_WRONG_USERNAME_AND_PW}, 0, 1);
  884.         }
  885.     }
  886.     $o->{stuff}->{cookie_str} = $cookie_str;
  887.     $o->{stuff}->{result} = $result;
  888.     $o->{stuff}->{privcache} = $privcache;
  889.     $o->{stuff}->{uid} = $FORMref->{COOKIE}->{uid};
  890.     $o->{stuff}->{x} = $FORMref->{x};
  891.     $o->{stuff}->{st} = $FORMref->{st};
  892.     return $o if ($FORMref->{stype} eq "" && $FORMref->{method} eq "" && $FORMref->{query} eq "" && $FORMref->{cache} eq "" && $FORMref->{scache} eq "");
  893.     if ($FORMref->{stype} eq "") {
  894.         if ($FORMref->{method} ne "") {
  895.             $o->{thing}->{log} = 1 if $FORMref->{method} eq "check";
  896.             $o->{thing}->{log} = 1 if $FORMref->{method} eq "last";
  897.         }
  898.         if ($FORMref->{query} ne "") {
  899.             $o->{thing}->{tree} = 1 if $FORMref->{lookin} == 1;
  900.             $o->{thing}->{log} = 1 if $FORMref->{lookin} == 2;
  901.             $o->{thing}->{search} = 1 if $FORMref->{lookin} == 3;
  902.         }
  903.     }
  904.     $o->{thing}->{log} if ($FORMref->{nmsm} && $FORMref->{stype} !~ m|s|);
  905.     if ($FORMref->{stype} =~ m|s|) {
  906.         $o->{thing}->{tree} = 1 if $FORMref->{slookin} == 1;
  907.         $o->{thing}->{log} = 1 if $FORMref->{slookin} == 2;
  908.         $o->{thing}->{search} = 1 if $FORMref->{slookin} == 3;
  909.     } elsif ($FORMref->{stype} =~ m|n|) {
  910.         $o->{thing}->{log} = 1;
  911.     }
  912.     if ($FORMref->{query} || $FORMref->{stype} =~ m|s|) {
  913.         $o->{query_string} = defined $FORMref->{squery} ? $FORMref->{squery} : $FORMref->{query};
  914.     }
  915.     if ($FORMref->{method} || $FORMref->{nmsm}) {
  916.         if (ref $result eq "ARRAY" && scalar(@{ $result }) && ($FORMref->{nmsm} == 2 || $FORMref->{method} eq "check")) {
  917.             $o->{cutoff} = 0 + $result->[0]->{stime};
  918.             $o->{cutoff} = time - (7*24*60*60) if $o->{cutoff} == 0;
  919.         } else {
  920.             $o->{cutoff} = time - 60*$FORMref->{units}*($FORMref->{nnumber}+$FORMref->{number});
  921.         }
  922.     }
  923.     if ($FORMref->{sopts}) {
  924.         $o->{search_opt}->{and} = 1 if $FORMref->{sopts} == 1;
  925.     }
  926.     if ($FORMref->{smethod}) {
  927.         $o->{search_opt}->{whole} = 1 if $FORMref->{smethod} == 1;
  928.     }
  929.     if ($FORMref->{scase}) {
  930.         $o->{search_opt}->{sensitive} = 1 if $FORMref->{scase} == 1;
  931.     }
  932.     if ($FORMref->{cache}) {
  933.         $o->{thing}->{cache} = $FORMref->{cache};
  934.     }
  935.     if ($FORMref->{scache}) {
  936.         $o->{thing}->{scache} = $FORMref->{scache};
  937.     }
  938.     $FORMref->{where} = $cache->{favorites}    if ($FORMref->{where} eq "all" && $FORMref->{method} && $cache->{preferences} =~ m|h| && $cache->{favorites});
  939.     my $topics = board_topics();
  940.     my @t = grep($_->{type} == 1, @{$topics});
  941.     my %t = map { $_->{number}, 1 } @t;
  942.     if ($FORMref->{where} =~ m|^[\d\,]+$|) {
  943.         my @u = split(/,/, $FORMref->{where});
  944.         foreach my $u (@u) {
  945.             next if ! $t{$u};
  946.             if ($DCONF->{pro}) {
  947.                 $o->{where}->{$u} = 1 if $cache->{topic_auth}->{$u};
  948.             } else {
  949.                 $o->{where}->{$u} = 1;
  950.             }
  951.         }
  952.     } elsif ($FORMref->{where} eq "all") {
  953.         foreach my $uu (@t) {
  954.             my $u = $uu->{number};
  955.             if ($DCONF->{pro}) {
  956.                 $o->{where}->{$u} = 1 if $cache->{topic_auth}->{$u};
  957.             } else {
  958.                 $o->{where}->{$u} = 1;
  959.             }
  960.         }
  961.     }
  962.     $o->{query_string} = char_convert($o->{query_string});
  963.     return $o;
  964. }
  965.  
  966. ###
  967. ### search_first
  968. ###
  969. ### First interface for searching
  970. ###
  971.  
  972. sub search_first {
  973.     my ($FORMref, $sparam) = @_;
  974.     my $subst = {};
  975.     my $cookie_str = "";
  976.     my $tree = readfile("$DCONF->{admin_dir}/tree.txt", "search_first", { zero_ok => 1, no_lock => 1, no_unlock => 1});
  977.     if ($DCONF->{pro}) {
  978.         my $topics = board_topics(undef, undef, $tree, 0, 1);
  979.         ($subst->{topics}, $cookie_str) = topic_list_simple_prune($topics, $FORMref, $sparam->{stuff}->{cookie_str}, $sparam->{stuff}->{result}, $sparam->{stuff}->{privcache});
  980.         $subst->{general}->{favorites} = $sparam->{favorites};
  981.         $subst->{general}->{preferences} = $sparam->{preferences};
  982.     } else {
  983.         $subst->{topics} = board_topics(undef, undef, $tree, 0, 1);
  984.     }
  985.     my @t = @{ $subst->{topics} };
  986.     @t = grep ($_->{type} == 1, @t);
  987.     $subst->{topics} = \@t;
  988.     $subst->{fill}->{username} = $FORMref->{COOKIE}->{user};
  989.     $subst->{fill}->{password} = $FORMref->{COOKIE}->{rpwd};
  990.     screen_out("search", $subst, $cookie_str);
  991. }
  992.  
  993. 1;
  994.