mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-10-13 05:24 am

[dw-free] Site-wide search

[commit: http://hg.dwscoalition.org/dw-free/rev/111ac3e6b53c]

http://bugs.dwscoalition.org/show_bug.cgi?id=1902

Add revamped 'Site and Journal Search' feature.

Patch by [staff profile] mark.

Files modified:
  • bin/upgrading/en.dat
  • bin/upgrading/proplists.dat
  • bin/worker/sphinx-copier
  • bin/worker/sphinx-search-gm
  • cgi-bin/DW/Logic/MenuNav.pm
  • cgi-bin/DW/Logic/UserLinkBar.pm
  • cgi-bin/DW/Setting/AllowSearchBy.pm
  • cgi-bin/DW/Setting/GlobalSearch.pm
  • cgi-bin/LJ/User.pm
  • htdocs/manage/settings/index.bml
  • htdocs/search.bml
--------------------------------------------------------------------------------
diff -r eca92edcffa8 -r 111ac3e6b53c bin/upgrading/en.dat
--- a/bin/upgrading/en.dat	Sun Oct 11 17:12:18 2009 +0000
+++ b/bin/upgrading/en.dat	Tue Oct 13 05:24:02 2009 +0000
@@ -2209,11 +2209,11 @@ menunav.explore.interests=Interests
 
 menunav.explore.directorysearch=Directory Search
 
-menunav.explore.journalsearch=Journal Search
-
 menunav.explore.randomjournal=Random Journal
 
 menunav.explore.randomcommunity=Random Community
+
+menunav.explore.sitesearch=Site and Journal Search
 
 menunav.explore.faq=FAQ
 
@@ -2449,6 +2449,14 @@ setting.adultcontent.option.self=This jo
 
 setting.adultcontentreason.label=Adult Content Reason
 
+setting.allowsearchby.label=Allow Search By
+
+setting.allowsearchby.sel.a=Allow everybody to search my journal
+
+setting.allowsearchby.sel.f=Allow people on my access list to search my journal (default)
+
+setting.allowsearchby.sel.n=Allow nobody else (only me) to search my journal
+
 setting.bio.desc|notes=There shouldn't be a description unless the page specifies one
 setting.bio.desc=_none
 
@@ -2682,6 +2690,12 @@ setting.gender.error.wrongtype=Only pers
 setting.gender.error.wrongtype=Only personal or identity journals can have a gender.
 
 setting.gender.question=What's your gender?
+
+setting.globalsearch.label=Global Search Inclusion
+
+setting.globalsearch.sel.no=No, do not allow my content to be displayed
+
+setting.globalsearch.sel.yes=Yes, display my public content in search results (default)
 
 setting.graphicpreviews.label=Graphic Previews
 
diff -r eca92edcffa8 -r 111ac3e6b53c bin/upgrading/proplists.dat
--- a/bin/upgrading/proplists.dat	Sun Oct 11 17:12:18 2009 +0000
+++ b/bin/upgrading/proplists.dat	Tue Oct 13 05:24:02 2009 +0000
@@ -518,6 +518,14 @@ userproplist.no_mail_alias:
   multihomed: 0
   prettyname: Disable site e-mail alias
 
+userproplist.opt_allowsearchby:
+  cldversion: 4
+  datatype: char
+  des: A = everybody, F = access list only, N = nobody (me only).
+  indexed: 0
+  multihomed: 0
+  prettyname: Allow Search By
+
 userproplist.opt_bdaymail:
   cldversion: 0
   datatype: bool
@@ -525,6 +533,14 @@ userproplist.opt_bdaymail:
   indexed: 1
   multihomed: 0
   prettyname: Get Birthday Reminders
+
+userproplist.opt_blockglobalsearch:
+  cldversion: 4
+  datatype: char
+  des: Y: don't allow global search, N: do allow
+  indexed: 0
+  multihomed: 0
+  prettyname: Block Global Search of Content
 
 userproplist.opt_blockrobots:
   cldversion: 4
diff -r eca92edcffa8 -r 111ac3e6b53c bin/worker/sphinx-copier
--- a/bin/worker/sphinx-copier	Sun Oct 11 17:12:18 2009 +0000
+++ b/bin/worker/sphinx-copier	Tue Oct 13 05:24:02 2009 +0000
@@ -103,9 +103,9 @@ sub work {
     # already has most of their posts copied and they are just updating one or two.
     foreach my $jitemid ( @copy_jitemids ) {
         my $row = $dbfrom->selectrow_hashref(
-            qq{SELECT l.journalid, l.jitemid, l.posterid, l.security, l.eventtime, lt.subject, lt.event
-               FROM log2 l INNER JOIN logtext2 lt ON (l.journalid = lt.journalid AND l.jitemid = lt.jitemid)
-               WHERE l.journalid = ? AND l.jitemid = ?},
+            q{SELECT l.journalid, l.jitemid, l.posterid, l.security, l.allowmask, l.eventtime, lt.subject, lt.event
+              FROM log2 l INNER JOIN logtext2 lt ON (l.journalid = lt.journalid AND l.jitemid = lt.jitemid)
+              WHERE l.journalid = ? AND l.jitemid = ?},
             undef, $u->id, $jitemid
         );
         die $dbfrom->errstr if $dbfrom->err;
@@ -113,17 +113,25 @@ sub work {
         # have to do some more munging
         $row->{is_public} = $row->{security} eq 'public' ? 1 : 0;
         $row->{edittime} = $db_times->{$jitemid};
+        $row->{allowmask} = join ',', LJ::bit_breakdown( $row->{allowmask} );
+        $row->{allowpublic} = $u->include_in_global_search ? 1 : 0;
 
         # very important, the search engine can't index compressed crap...
         foreach ( qw/ subject event / ) {
             LJ::text_uncompress( \$row->{$_} );
+
+            # required, we store raw-bytes in our own database but the Sphinx system expects
+            # things to be proper UTF-8, this does it.
             $row->{$_} = Encode::decode( 'utf8', $row->{$_} );
         }
 
         # insert
-        $dbto->do( 'REPLACE INTO posts_raw (id, journal_id, jitemid, poster_id, is_public, date_posted, title, data, revtime) ' .
-                   'VALUES (NULL, ?, ?, ?, ?, UNIX_TIMESTAMP(?), ?, ?, ?)',
-                   undef, map { $row->{$_} } qw/ journalid jitemid posterid is_public eventtime subject event edittime /, );
+        $dbto->do(
+            q{REPLACE INTO posts_raw (id, journal_id, jitemid, poster_id, is_public, security_bits, allow_global_search, date_posted,
+                                      title, data, revtime)
+              VALUES (NULL, ?, ?, ?, ?, ?, ?, UNIX_TIMESTAMP(?), ?, ?, ?)},
+            undef, map { $row->{$_} } qw/ journalid jitemid posterid is_public allowmask allowpublic eventtime subject event edittime /
+        );
         die $dbto->errstr if $dbto->err;
 
         # let the viewer know what they missed
diff -r eca92edcffa8 -r 111ac3e6b53c bin/worker/sphinx-search-gm
--- a/bin/worker/sphinx-search-gm	Sun Oct 11 17:12:18 2009 +0000
+++ b/bin/worker/sphinx-search-gm	Tue Oct 13 05:24:02 2009 +0000
@@ -32,8 +32,10 @@ sub sphinx_search {
     my $job = $_[0];
 
     my $args = Storable::thaw( $job->arg ) || {};
-    return undef
-        unless $args->{userid} && $args->{query};
+    return undef unless $args->{query};
+
+    # a little sanity check, we don't currently allow a global search on non-public content
+    return undef if $args->{userid} == 0 && $args->{public} != 1;
 
     my $sx = Sphinx::Search->new();
     $sx->SetServer( @LJ::SPHINX_SEARCHD );
@@ -41,9 +43,15 @@ sub sphinx_search {
     $sx->SetMatchMode( SPH_MATCH_ALL )
        ->SetSortMode( SPH_SORT_RELEVANCE )
        ->SetMaxQueryTime( 15_000 )
-       ->SetFilter( 'journal_id', [ $args->{userid} ] )
        ->SetLimits( $args->{offset} || 0, 20 );
+    
+    # filter to a journal if we have a userid set; else, filter on allow_global_search items
+    $sx->SetFilter( 'journal_id', [ $args->{userid} ] )
+        if $args->{userid};
+    $sx->SetFilter( 'allow_global_search', [ 1 ] )
+        unless $args->{userid};
 
+    # further filter on public items or not
     $sx->SetFilter( 'is_public', [ 1 ] )
         if $args->{public};
 
diff -r eca92edcffa8 -r 111ac3e6b53c cgi-bin/DW/Logic/MenuNav.pm
--- a/cgi-bin/DW/Logic/MenuNav.pm	Sun Oct 11 17:12:18 2009 +0000
+++ b/cgi-bin/DW/Logic/MenuNav.pm	Tue Oct 13 05:24:02 2009 +0000
@@ -201,8 +201,8 @@ sub get_menu_navigation {
                 },
                 {
                     url => "$LJ::SITEROOT/search",
-                    text => "menunav.explore.journalsearch",
-                    display => $loggedin_ispaid && @LJ::SPHINX_SEARCHD ? 1 : 0,
+                    text => "menunav.explore.sitesearch",
+                    display => @LJ::SPHINX_SEARCHD ? 1 : 0,
                 },
                 {
                     url => "$LJ::SITEROOT/random",
diff -r eca92edcffa8 -r 111ac3e6b53c cgi-bin/DW/Logic/UserLinkBar.pm
--- a/cgi-bin/DW/Logic/UserLinkBar.pm	Sun Oct 11 17:12:18 2009 +0000
+++ b/cgi-bin/DW/Logic/UserLinkBar.pm	Tue Oct 13 05:24:02 2009 +0000
@@ -463,16 +463,13 @@ sub search {
 
     my $u = $self->{u};
     my $remote = $self->{remote};
-    my $user = $u->user;
 
     # don't show if search is disabled
     return undef unless
-        @LJ::SPHINX_SEARCHD &&
-        ( $u->is_community || ( $u->equals( $remote ) ) ) &&
-        $u->is_paid;
+        @LJ::SPHINX_SEARCHD && $u->allow_search_by( $remote );
 
     my $link = {
-        url => 'search' . ( $u->is_community ? "?search_user=" . $u->user : '' ),
+        url => 'search?user=' . $u->user,
         image => 'search.png',
         text_ml => "userlinkbar.search",
         title_ml => "userlinkbar.search.title",
diff -r eca92edcffa8 -r 111ac3e6b53c cgi-bin/DW/Setting/AllowSearchBy.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Setting/AllowSearchBy.pm	Tue Oct 13 05:24:02 2009 +0000
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+#
+# DW::Setting::AllowSearchBy
+#
+# Module to set the opt_allowsearchby setting.
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself.  For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Setting::AllowSearchBy;
+use base 'LJ::Setting';
+use strict;
+use warnings;
+
+sub should_render {
+    return $_[1] && $_[1]->is_person;
+}
+
+sub label {
+    return $_[0]->ml( 'setting.allowsearchby.label' );
+}
+
+sub option {
+    my ( $class, $u, $errs, $args ) = @_;
+    my $key = $class->pkgkey;
+
+    # the selection values are opposite the text, since the property is an
+    # opt-out property, it basically negates what we're trying to display to
+    # the user ... yes, it's confusing, sorry
+    my $sel = $class->get_arg( $args, "allowsearchby" ) ||  $u->prop( 'opt_allowsearchby' ) || 'F';
+
+    my $ret .= LJ::html_select(
+        {
+            id       => "${key}allowsearchby",
+            name     => "${key}allowsearchby",
+            selected => $sel,
+        },
+
+        'A' => $class->ml( 'setting.allowsearchby.sel.a' ),
+        'F' => $class->ml( 'setting.allowsearchby.sel.f' ),
+        'N' => $class->ml( 'setting.allowsearchby.sel.n' ),
+    );
+
+    my $errdiv = $class->errdiv( $errs, 'allowsearchby' );
+    $ret .= "<br />$errdiv" if $errdiv;
+
+    return $ret;
+}
+
+sub save {
+    my ( $class, $u, $args ) = @_;
+
+    # must be defined and Y or N
+    my $val = $class->get_arg( $args, 'allowsearchby' ) || 0;
+    return unless defined $val && $val =~ /^[AFN]$/;
+
+    $u->set_prop( opt_allowsearchby => $val );
+
+    return 1;
+}
+
+1;
diff -r eca92edcffa8 -r 111ac3e6b53c cgi-bin/DW/Setting/GlobalSearch.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Setting/GlobalSearch.pm	Tue Oct 13 05:24:02 2009 +0000
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+#
+# DW::Setting::GlobalSearch
+#
+# Module to set the opt_blockglobalsearch setting.
+#
+# Authors:
+#      Mark Smith <mark@dreamwidth.org>
+#
+# Copyright (c) 2009 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself.  For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Setting::GlobalSearch;
+use base 'LJ::Setting';
+use strict;
+use warnings;
+
+sub should_render {
+    return $_[1] && ( $_[1]->is_person || $_[1]->is_community );
+}
+
+sub label {
+    return $_[0]->ml( 'setting.globalsearch.label' );
+}
+
+sub option {
+    my ( $class, $u, $errs, $args ) = @_;
+    my $key = $class->pkgkey;
+
+    # the selection values are opposite the text, since the property is an
+    # opt-out property, it basically negates what we're trying to display to
+    # the user ... yes, it's confusing, sorry
+    my $sel = $class->get_arg( $args, "globalsearch" );
+    $sel = $u->include_in_global_search ? 'N' : 'Y'
+        unless defined $sel && length $sel;
+
+    my $ret .= LJ::html_select(
+        {
+            id       => "${key}globalsearch",
+            name     => "${key}globalsearch",
+            selected => $sel,
+        },
+
+        'N' => $class->ml( 'setting.globalsearch.sel.yes' ),
+        'Y' => $class->ml( 'setting.globalsearch.sel.no' ),
+    );
+
+    my $errdiv = $class->errdiv( $errs, 'globalsearch' );
+    $ret .= "<br />$errdiv" if $errdiv;
+
+    return $ret;
+}
+
+sub save {
+    my ( $class, $u, $args ) = @_;
+
+    # must be defined and Y or N
+    my $val = $class->get_arg( $args, 'globalsearch' ) || 0;
+    return unless defined $val && $val =~ /^[YN]$/;
+
+    $u->set_prop( opt_blockglobalsearch => $val );
+
+    return 1;
+}
+
+1;
diff -r eca92edcffa8 -r 111ac3e6b53c cgi-bin/LJ/User.pm
--- a/cgi-bin/LJ/User.pm	Sun Oct 11 17:12:18 2009 +0000
+++ b/cgi-bin/LJ/User.pm	Tue Oct 13 05:24:02 2009 +0000
@@ -1736,6 +1736,31 @@ sub add_to_class {
 }
 
 
+# 1/0 whether the argument is allowed to search this journal
+sub allow_search_by {
+    my ( $u, $by ) = @_;
+    return 0 unless LJ::isu( $u ) && LJ::isu( $by );
+
+    # the person doing the search has to be an individual
+    return 0 unless $by->is_individual;
+
+    # someone in the equation has to be a paid account
+    return 0 unless $u->is_paid || $by->is_paid;
+
+    # allow searches if this is a community or it's us
+    return 1 if $u->is_community || $u->equals( $by );
+
+    # check the userprop for security access
+    my $whocan = $u->prop( 'opt_allowsearchby' );
+    return 1 if $whocan eq 'A';
+    return 1 if $whocan eq 'F' && $u->trusts( $by );
+    return 1 if $whocan eq 'N' && $u->equals( $by );
+
+    # failing the above, sorry, no search for you
+    return 0;
+}
+
+
 sub caps {
     my $u = shift;
     return $u->{caps};
@@ -1905,6 +1930,27 @@ sub in_class {
 sub in_class {
     my ($u, $class) = @_;
     return LJ::caps_in_group($u->{caps}, $class);
+}
+
+
+# 1/0; whether or not this account should be included in the global search
+# system.  this is used by the bin/worker/sphinx-copier mostly.
+sub include_in_global_search {
+    my $u = $_[0];
+
+    # only P/C accounts should be globally searched
+    return 0 unless $u->is_person || $u->is_community;
+
+    # default) check opt_blockglobalsearch and use that if it's defined
+    my $bgs = $u->prop( 'opt_blockglobalsearch' );
+    return $bgs eq 'Y' ? 0 : 1 if defined $bgs && length $bgs;
+
+    # fallback) use their robot blocking value if it's set
+    my $br = $u->prop( 'opt_blockrobots' );
+    return $br ? 0 : 1 if defined $br && length $br;
+
+    # allow search of this user's content
+    return 1;
 }
 
 
diff -r eca92edcffa8 -r 111ac3e6b53c htdocs/manage/settings/index.bml
--- a/htdocs/manage/settings/index.bml	Sun Oct 11 17:12:18 2009 +0000
+++ b/htdocs/manage/settings/index.bml	Tue Oct 13 05:24:02 2009 +0000
@@ -118,6 +118,8 @@ body<=
                 LJ::Setting::Display::BanUsers
                 DW::Setting::ProfileEmail
                 DW::Setting::RandomPaidGifts
+                DW::Setting::GlobalSearch
+                DW::Setting::AllowSearchBy
             )],
         },
         history => {
diff -r eca92edcffa8 -r 111ac3e6b53c htdocs/search.bml
--- a/htdocs/search.bml	Sun Oct 11 17:12:18 2009 +0000
+++ b/htdocs/search.bml	Tue Oct 13 05:24:02 2009 +0000
@@ -33,126 +33,147 @@ body<=
     my $remote = LJ::get_remote();
     return "<?needlogin?>" unless $remote;
 
-    # okay, now let's make sure the data is valid
-    my $su = LJ::load_user( $GET{search_user} || $remote->user );
-    return "Invalid search user.\n" unless $su;
-    return "You can't search that journal!\n"
-        unless $remote->equals( $su ) ||
-               $su->is_community;
+    # see what search mode...
+    my $su = LJ::load_user( $POST{mode} || $GET{user} );
+    my $q = LJ::strip_html( LJ::trim( $POST{query} ) );
 
-    # and make sure we're searching a paid account
-    return "Sorry, searches are only available for paid accounts.\n"
-        unless $su->is_paid;
+    # helper sub for returning the search form
+    my $search_form = sub {
+        my $ret = "<form method='post' action='$LJ::SITEROOT/search'>" . LJ::form_auth();
 
-    # for later
-    my $sulj = $su->ljuser_display;
-    my $suu = $su->user;
+        $ret .= LJ::html_check( { type => 'radio', selected => $su ? 0 : 1, id => 'm-global', name => 'mode',
+                                  value => '', label => 'Site Search (Public Entries)' } );
 
-    # if they posted we might have to do something
-    if ( LJ::did_post() ) {
-        return "Failauth.\n" unless LJ::check_form_auth();
-        # and make sure we got a query
-        my $q = LJ::strip_html( LJ::trim( $POST{query} ) );
-        return "Query must be shorter than 255 characters, sorry!\n"
-            if length( $q ) > 255;
-        return "Please enter a search query. <a href='$LJ::SITEROOT/search'>Search again.</a>\n"
-            unless $q;
-
-        # if an offset, less than 1000 please
-        my $offset = $GET{offset} + 0;
-        return "Hey, that offset is nonsensical... :(\n"
-            if $offset < 0 || $offset > 1000;
-
-        # set a public only flag if this is not your journal
-        my $public = ( $remote->equals( $su ) || ( $su->is_community && $remote->member_of( $su ) ) ) ? 0 : 1;
-
-        # the arguments to the search
-        my $args = { userid => $su->id, query => $q, offset => $offset, public => $public };
-        my $arg = Storable::nfreeze( $args );
-
-        # so we know that they're searching something valid, send to gearman
-        my $result;
-        my $task = Gearman::Task->new(
-            'sphinx_search', \$arg,
-            {
-                uniq => '-',
-                on_complete => sub {
-                    my $res = $_[0] or return undef;
-                    $result = Storable::thaw( $$res );
-                },
-            }
-        );
-
-        # setup the task set for gearman... really, isn't there a way to make this
-        # simpler?  oh well
-        my $ts = $gc->new_task_set();
-        $ts->add_task( $task );
-        $ts->wait( timeout => 20 );
-
-        # if we didn't get a result...
-        return "Sorry, we were unable to find a result in the time allotted.  This may mean that ".
-               "the server is busy or down.  Please try your query again later.\n"
-            unless $result;
-
-        # if we didn't get any matches...
-        return "Sorry, we didn't find any matches for the search <strong>$q</strong>.  We looked for $result->{time} seconds, too!\n"
-            if $result->{total} <= 0;
-
-        # now we can process the results and do something fascinating!
-        my $matches = '';
-        foreach my $match ( @{ $result->{matches} } ) {
-            my $icon = {
-                    public => '',
-                    private => "<img src='$LJ::IMGPREFIX/silk/entry/private.png'>",
-                    usemask => "<img src='$LJ::IMGPREFIX/silk/entry/filtered.png'>",
-                    access => "<img src='$LJ::IMGPREFIX/silk/entry/locked.png'>",
-                }->{$match->{security}};
-
-            my $tags = join( ', ', map { "<strong>" . $match->{tags}->{$_} . "</strong>" } keys %{ $match->{tags} } );
-            $tags = "<br />Tags: $tags"
-                if $tags;
-
-            my $html = qq(<div class='searchres'>$icon <a href="$match->{url}">$match->{subject}</a><br />
-                          <span class='exc'>$match->{excerpt}</span>$tags<br />Posted: <strong>$match->{eventtime}</strong><br /><br />
-                          </div>);
-            $matches .= $html;
+        my $tu = $su || $remote;
+        if ( $tu->allow_search_by( $remote ) ) {
+            $ret .= LJ::html_check( { type => 'radio', selected => $su ? 1 : 0, id => 'm-user', name => 'mode',
+                                      value => $tu->user, label => "Journal Search: <strong>" . $tu->user . "</strong>", noescape => 1 } );
         }
 
-        # build the rest of the search page
-        my $ret = "<?p You have searched $sulj... p?>" . $matches;
-
-        # put some stats on the output
-        my $matchct = scalar( @{ $result->{matches} } );
-        my $skip = $offset > 0 ? " (skipped $offset)" : "";
-        $ret .= qq(<span class="stats">$matchct results displayed out of $result->{total} hits total$skip for <strong>$q</strong>.
-                   $result->{time} seconds.</span>);
-
-        if ( $result->{total} > ( $offset + $matchct ) ) {
-            my $offsetm = $offset + $matchct;
-            my $fa = LJ::form_auth();
-            $ret .= qq(<form method="post" action="$LJ::SITEROOT/search?search_user=$suu&offset=$offsetm">$fa
-                       <input type="hidden" name="query" value="$q"> <input type="submit" value="More Results..." />
-                       </form>);
-        }
+        $ret .= '<br /><input type="text" name="query" maxlength="255" size="60" value="' . LJ::ehtml( $q ) .
+                '"> <input type="submit" value="Search" /></form>';
 
         return $ret;
+    };
+
+    # an error redisplays the form, with an error message
+    my $error = sub {
+        return $search_form->() . "<br /><?p <strong>Error:</strong> $_[0] p?>";
+    };
+
+    # if no $su, then this is a public search, that's allowed.  but if it's a user,
+    # ensure that it's an account that we CAN search
+    return $error->( "You can't search that journal." )
+        if $su && ! $su->allow_search_by( $remote );
+
+    ################################################################################
+    ################################################################################
+
+    # give them the form to do a search if they haven't actually posted to us yet
+    unless ( LJ::did_post() ) {
+        # give them a form to enter their search options
+        return "<?p $LJ::SITENAME content search.  Please select where to search and enter your search terms. p?>" .
+               $search_form->() .
+               '</form><br /> <?p To control who can search your journal, and whether or not your public ' .
+               'entries appear in global search results, please adjust your <a href="' . $LJ::SITEROOT .
+               '/manage/settings/?cat=privacy">account privacy settings</a>. p?>';
     }
 
-    # give them a form to search...
-    my $fa = LJ::form_auth();
-    return <<EOF;
+    ################################################################################
+    ################################################################################
 
-<?p Searching $sulj ... p?>
+    # at this point, they have done a POST, which means they want to search something
+    return $error->( "Failauth." ) unless LJ::check_form_auth();
 
-<form method="post" action="$LJ::SITEROOT/search?search_user=$suu">
-$fa
-<input type="text" name="query" maxlength="255" size="60"> <input type="submit" value="Search!" />
-</form>
+    # and make sure we got a query
+    return $error->( "Query must be shorter than 255 characters, sorry!" )
+        if length( $q ) > 255;
+    return $error->( "Please enter a search query." )
+        unless $q;
 
-<?p This is a beta search.  Things may be slow or broken while we iron out the kinks
-in the system.  If you run into trouble, let us know! p?>
+    # if an offset, less than 1000 please
+    my $offset = $GET{offset} + 0;
+    return $error->( "Hey, that offset is nonsensical... :(" )
+        if $offset < 0 || $offset > 1000;
 
-EOF
+    # set a public only flag if this is not your journal or a community you are in
+    my $public = 1;
+    $public = 0 if $su && ( $remote->equals( $su ) || ( $su->is_community && $remote->member_of( $su ) ) );
+
+    # the arguments to the search (userid=0 implies global search)
+    my $args = { userid => $su ? $su->id : 0, query => $q, offset => $offset, public => $public };
+    my $arg = Storable::nfreeze( $args );
+
+    # so we know that they're searching something valid, send to gearman
+    my $result;
+    my $task = Gearman::Task->new(
+        'sphinx_search', \$arg,
+        {
+            uniq => '-',
+            on_complete => sub {
+                my $res = $_[0] or return undef;
+                $result = Storable::thaw( $$res );
+            },
+        }
+    );
+
+    # setup the task set for gearman... really, isn't there a way to make this
+    # simpler?  oh well
+    my $ts = $gc->new_task_set();
+    $ts->add_task( $task );
+    $ts->wait( timeout => 20 );
+
+    # if we didn't get a result...
+    return $error->( "Sorry, we were unable to find a result in the time allotted.  This may mean that ".
+                     "the server is busy or down.  Please try your query again later." )
+        unless $result;
+
+    # if we didn't get any matches...
+    return $error->( "Sorry, we didn't find any matches for the search <strong>$q</strong>.  We looked for $result->{time} seconds, too!" )
+        if $result->{total} <= 0;
+
+    # now we can process the results and do something fascinating!
+    my $matches = '<br /><br />';
+    foreach my $match ( @{ $result->{matches} } ) {
+        my $mu = LJ::load_userid( $match->{journal_id} );
+
+        my $icon = {
+                public => '',
+                private => "<img src='$LJ::IMGPREFIX/silk/entry/private.png'>",
+                usemask => "<img src='$LJ::IMGPREFIX/silk/entry/filtered.png'>",
+                access => "<img src='$LJ::IMGPREFIX/silk/entry/locked.png'>",
+            }->{$match->{security}};
+
+        my $tags = join( ', ', map { "<strong>" . $match->{tags}->{$_} . "</strong>" } keys %{ $match->{tags} } );
+        $tags = "<br />Tags: $tags"
+            if $tags;
+
+        my $mulj = $mu->ljuser_display;
+        my $html = qq(<div class='searchres'>$mulj: $icon <a href="$match->{url}">$match->{subject}</a><br />
+                      <span class='exc'>$match->{excerpt}</span>$tags<br />Posted: <strong>$match->{eventtime}</strong><br /><br />
+                      </div>);
+        $matches .= $html;
+    }
+
+    # build the rest of the search page
+    my $ret = $search_form->() . $matches;
+
+    # put some stats on the output
+    my $matchct = scalar( @{ $result->{matches} } );
+    my $skip = $offset > 0 ? " (skipped $offset)" : "";
+    $ret .= qq(<span class="stats">$matchct results displayed out of $result->{total} hits total$skip for <strong>$q</strong>.
+               $result->{time} seconds.</span>);
+
+    if ( $result->{total} > ( $offset + $matchct ) ) {
+        my $offsetm = $offset + $matchct;
+        $ret .= "<form method='post' action='$LJ::SITEROOT/search?offset=$offsetm'>" . LJ::form_auth() .
+                "<input type='hidden' name='query' value='" . LJ::ehtml( $q ) . "'>" .
+                "<input type='hidden' name='mode' value='" . ( $su ? $su->user : '' ) . "'>" .
+                "<input type='submit' value='More Results...' />" .
+                "</form>";
+    }
+
+    return $ret;
 }
 _code?>
 <=body
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
No Subject Icon Selected
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org