[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
mark.
Files modified:
http://bugs.dwscoalition.org/show_bug.cgi?id=1902
Add revamped 'Site and Journal Search' feature.
Patch by
![[staff profile]](https://www.dreamwidth.org/img/silk/identity/user_staff.png)
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 --------------------------------------------------------------------------------