http://wiki.dwscoalition.org/notes/Setting_Up_Search
Patch by mark.
diffs (620 lines):
diff -r 6865fbf1b5da -r 4dedfba1fe16 bin/schedule-copier-jobs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/schedule-copier-jobs Sat Jul 25 04:48:46 2009 +0000
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+#
+# bin/schedule-copier-jobs
+#
+# A simple job that schedules copier tasks for the sphinx copier. This should
+# be run whenever you want to clean up the database of new/old data. Probably
+# no more often than once every few weeks.
+#
+# Authors:
+# Mark Smith <[email protected]>
+#
+# 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'.
+#
+
+use strict;
+use lib "$ENV{LJHOME}/cgi-bin";
+require 'ljlib.pl';
+
+die "Need to have Sphinx search enabled.\n"
+ unless @LJ::SPHINX_SEARCHD;
+
+my $dbr = LJ::get_db_reader() or die;
+my $sth = $dbr->prepare( q{SELECT userid FROM user WHERE journaltype IN
('P','C')} );
+$sth->execute;
+
+my $sclient = LJ::theschwartz() or die;
+
+while ( my ( $userid ) = $sth->fetchrow_array ) {
+ warn "Scheduling $userid ...\n";
+ $sclient->insert_jobs( TheSchwartz::Job->new_from_array(
'DW::Worker::Sphinx::Copier', { userid => $userid } ) );
+}
diff -r 6865fbf1b5da -r 4dedfba1fe16 bin/upgrading/en.dat
--- a/bin/upgrading/en.dat Sat Jul 25 04:43:19 2009 +0000
+++ b/bin/upgrading/en.dat Sat Jul 25 04:48:46 2009 +0000
@@ -3633,6 +3633,10 @@ userlinkbar.postentry=Post an Entry
userlinkbar.postentry.title=Post to your journal
+userlinkbar.search=Search for Entries
+
+userlinkbar.search.title=Search for entries in this account
+
userlinkbar.sendmessage=Send Message
userlinkbar.sendmessage.title=Send a private message to this user
diff -r 6865fbf1b5da -r 4dedfba1fe16 bin/worker/sphinx-copier
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/worker/sphinx-copier Sat Jul 25 04:48:46 2009 +0000
@@ -0,0 +1,155 @@
+#!/usr/bin/perl
+#
+# bin/worker/sphinx-copier
+#
+# Responsible for ensuring a user is up to date when they make a new post or
+# edit an existing one.
+#
+# Authors:
+# Mark Smith <[email protected]>
+#
+# 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'.
+#
+
+use strict;
+use lib "$ENV{LJHOME}/cgi-bin";
+use LJ::Worker::TheSchwartz;
+
+schwartz_decl( 'DW::Worker::Sphinx::Copier' );
+schwartz_work();
+
+# ============================================================================
+package DW::Worker::Sphinx::Copier;
+use base 'TheSchwartz::Worker';
+use Encode;
+use DW::Pay;
+use LJ::DBUtil;
+
+sub work {
+ my ( $class, $job ) = @_;
+ my $a = $job->arg;
+
+ my $u = LJ::load_userid( $a->{userid} )
+ or die "Invalid userid: $a->{userid}.\n";
+ my $dbto = LJ::get_dbh( 'sphinx_search' )
+ or die "Unable to connect to Sphinx search database.\n";
+ my $p_revtime = LJ::get_prop( log => 'revtime' )
+ or die "No logprop revtime?\n";
+
+ # annotate copier is going
+ warn "[$$] Sphinx copier started for " . $u->user . "(" . $u->id . ").\n";
+
+ # we have to use utf8 when we write to the db
+ $dbto->do( 'SET NAMES \'utf8\'' );
+
+ # if this is a free user, then it's easy
+ my $ps = DW::Pay::get_paid_status( $u );
+ unless ( $ps && ( $ps->{permanent} || $ps->{expiresin} > 0 ) ) {
+ warn "[$$] Removing " . $u->user . "(" . $u->id . ") from the index
[free user]...\n";
+ $dbto->do( 'DELETE FROM posts_raw WHERE journal_id = ?', undef, $u->id
);
+ die $dbto->errstr if $dbto->err;
+ return $job->completed;
+ }
+
+ # they're a paid user of some sort, so get the posts we know about
+ my $sphinx_times = $dbto->selectall_hashref(
+ 'SELECT jitemid, revtime FROM posts_raw WHERE journal_id = ?',
+ 'jitemid', undef, $u->id
+ );
+ die $dbto->errstr if $dbto->err;
+
+ # now get the times for all of their posts from the database (inactive if
possible)
+ my $dbfrom;
+ if ( $LJ::CLUSTER_PAIR_ACTIVE{$u->clusterid} ) {
+ $dbfrom = LJ::DBUtil->get_inactive_db( $u->clusterid );
+ }
+ $dbfrom ||= LJ::get_cluster_reader( $u->clusterid );
+ die "No dbfrom available.\n"
+ unless $dbfrom;
+
+ # okay, we're actually getting the times now
+ my $db_times = $dbfrom->selectall_hashref(
+ qq{SELECT l.jitemid, UNIX_TIMESTAMP(l.logtime) AS 'createtime',
lp.value AS 'edittime'
+ FROM log2 l LEFT JOIN logprop2 lp ON (l.journalid = lp.journalid
AND l.jitemid = lp.jitemid AND lp.propid = ?)
+ WHERE l.journalid = ?},
+ 'jitemid', undef, $p_revtime->{id}, $u->id
+ );
+ die $dbfrom->errstr if $dbfrom->err;
+
+ # three doors: ignore, copy, delete
+ my ( @copy_jitemids, @delete_jitemids );
+
+ # now iterate through and find entirely NEW and EDITED posts
+ foreach my $jitemid ( keys %$db_times ) {
+
+ # we have to decide which time to use. we want the edit time if one
is available,
+ # but we'll use the createtime if not
+ $db_times->{$jitemid} = $db_times->{$jitemid}->{edittime} ||
$db_times->{$jitemid}->{createtime};
+ $sphinx_times->{$jitemid} = $sphinx_times->{$jitemid}->{revtime};
+
+ next if exists $sphinx_times->{$jitemid} &&
+ $sphinx_times->{$jitemid} == $db_times->{$jitemid};
+
+ push @copy_jitemids, $jitemid;
+ }
+
+ # now find deleted posts
+ foreach my $jitemid ( keys %$sphinx_times ) {
+ next if exists $db_times->{$jitemid};
+
+ warn "[$$] Deleting post #$jitemid.\n";
+ push @delete_jitemids, $jitemid;
+ }
+
+ # deletes are easy...
+ if ( @delete_jitemids ) {
+ my $ct = $dbto->do( 'DELETE FROM posts_raw WHERE journal_id = ? AND
jitemid IN (' .
+ join( ',', @delete_jitemids ) . ')', undef, $u->id
) + 0;
+ die $dbto->errstr if $dbto->err;
+
+ warn "[$$] Actually deleted $ct posts.\n";
+ }
+
+ # now to copy entries. this is not done enmasse since the major case will
be after a user
+ # 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 = ?},
+ undef, $u->id, $jitemid
+ );
+ die $dbfrom->errstr if $dbfrom->err;
+
+ # have to do some more munging
+ $row->{is_public} = $row->{security} eq 'public' ? 1 : 0;
+ $row->{edittime} = $db_times->{$jitemid};
+
+ # very important, the search engine can't index compressed crap...
+ foreach ( qw/ subject event / ) {
+ LJ::text_uncompress( \$row->{$_} );
+ $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 /, );
+ die $dbto->errstr if $dbto->err;
+
+ # let the viewer know what they missed
+ warn "[$$] Inserted post #$jitemid for " . $u->user . "(" . $u->id .
").\n";
+ }
+
+ # all good
+ return $job->completed;
+}
+
+sub keep_exit_status_for { 0 }
+sub grab_for { 1800 }
+sub max_retries { 3 }
+sub retry_delay { 1800 }
diff -r 6865fbf1b5da -r 4dedfba1fe16 bin/worker/sphinx-search-gm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/worker/sphinx-search-gm Sat Jul 25 04:48:46 2009 +0000
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+#
+# sphinx-search-gm
+#
+# This Gearman worker is responsible for taking a search and issuing it to the
+# Sphinx searchd.
+#
+# Authors:
+# Mark Smith <[email protected]>
+#
+# 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'.
+#
+
+use strict;
+use lib "$ENV{LJHOME}/cgi-bin";
+require "ljlib.pl";
+
+use Encode;
+use Gearman::Worker;
+use LJ::Worker::Gearman;
+use Sphinx::Search;
+use Storable;
+
+gearman_decl( 'sphinx_search' => \&sphinx_search );
+gearman_work();
+
+sub sphinx_search {
+ my $job = $_[0];
+
+ my $args = Storable::thaw( $job->arg ) || {};
+ return undef
+ unless $args->{userid} && $args->{query};
+
+ my $sx = Sphinx::Search->new();
+ $sx->SetServer( @LJ::SPHINX_SEARCHD );
+
+ $sx->SetMatchMode( SPH_MATCH_ALL )
+ ->SetSortMode( SPH_SORT_RELEVANCE )
+ ->SetMaxQueryTime( 15_000 )
+ ->SetFilter( 'journal_id', [ $args->{userid} ] )
+ ->SetLimits( $args->{offset} || 0, 20 );
+
+ $sx->SetFilter( 'is_public', [ 1 ] )
+ if $args->{public};
+
+ my $res = $sx->Query( $args->{query} );
+ return undef unless $res;
+
+ # try to build some excerpts of these searches, which involves us loading
+ # up the exact entry contents...
+ if ( $res->{total} > 0 ) {
+
+ # this is weird, I push the hashrefs onto @out from $res->{matches} for
+ # convenience only... they're the same hashrefs you know and love
+ my @out;
+
+ foreach my $match ( @{ $res->{matches} } ) {
+ my $entry = LJ::Entry->new( $match->{journal_id}, jitemid =>
$match->{jitemid} );
+ if ( $entry && $entry->valid ) {
+ # use text only version of event for excerpt purposes. best
effort.
+ $match->{entry} = $entry->event_text;
+ $match->{entry} =~ s#<(?:br|p)\s*/?># #gi;
+ $match->{entry} = LJ::strip_html( $match->{entry} );
+
+ # we don't munge the subject... just clean it
+ $match->{subject} = $entry->subject_text || '(no subject)';
+
+ # also useful information that we want for later
+ $match->{url} = $entry->url;
+ $match->{tags} = $entry->tag_map;
+ $match->{security} = $entry->security;
+ $match->{security} = 'access'
+ if $match->{security} eq 'usemask' &&
+ $entry->allowmask == 1;
+ $match->{eventtime} = $entry->eventtime_mysql;
+
+ } else {
+ # something happened, couldn't get the entry
+ $match->{entry} = '(sorry, this entry has been deleted or is
otherwise unavailable)';
+ $match->{subject} = 'Entry deleted or unavailable.';
+ }
+ push @out, $match;
+ }
+
+ # FIXME: this should use some other index name than 'test1' heh, and
we should probably
+ # try to figure out the language of the journal being searched (or the
searcher?) and use
+ # an appropriate stemming library? (future expansion opportunity)
+ my $exc = $sx->BuildExcerpts( [ map { $_->{entry} } @out ],
'test1stemmed', $args->{query}, {} );
+
+ # if we have a matching number of excerpts to events, then we can
determine
+ # which one goes with which post.
+ if ( scalar( @out ) == scalar( @$exc ) ) {
+ foreach my $m ( @out ) {
+ delete $m->{entry};
+ $m->{excerpt} = shift @$exc;
+ }
+
+ } else {
+ # something terrible has happened..., user gets no excerpts :(
+ foreach my $m ( @out ) {
+ delete $m->{entry};
+ $m->{excerpt} = '(something terrible happened to the
excerpts)';
+ }
+ }
+ }
+
+ return Storable::nfreeze( $res );
+}
diff -r 6865fbf1b5da -r 4dedfba1fe16 cgi-bin/DW/Logic/ProfilePage.pm
--- a/cgi-bin/DW/Logic/ProfilePage.pm Sat Jul 25 04:43:19 2009 +0000
+++ b/cgi-bin/DW/Logic/ProfilePage.pm Sat Jul 25 04:48:46 2009 +0000
@@ -58,7 +58,7 @@ sub action_links {
my $remote = $self->{remote};
my $user_link_bar = $u->user_link_bar( $remote, class_prefix => "profile"
);
- my @ret = $user_link_bar->get_links( "manage_membership", "trust",
"watch", "post", "track", "message", "buyaccount" );
+ my @ret = $user_link_bar->get_links( "manage_membership", "trust",
"watch", "post", "track", "message", "search", "buyaccount" );
}
diff -r 6865fbf1b5da -r 4dedfba1fe16 cgi-bin/DW/Logic/UserLinkBar.pm
--- a/cgi-bin/DW/Logic/UserLinkBar.pm Sat Jul 25 04:43:19 2009 +0000
+++ b/cgi-bin/DW/Logic/UserLinkBar.pm Sat Jul 25 04:48:46 2009 +0000
@@ -38,6 +38,7 @@ DW::Logic::UserLinkBar - This module pro
$link = $user_link_bar->message;
$link = $user_link_bar->tellafriend;
$link = $user_link_bar->memories;
+ $link = $user_link_bar->search;
=cut
@@ -453,6 +454,36 @@ sub memories {
return $self->fix_link( $link );
}
+=head2 C<< $obj->search >>
+
+Returns a hashref with the appropriate icon/link/text for searching this
journal.
+
+=cut
+
+sub search {
+ my $self = $_[0];
+
+ 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;
+
+ my $link = {
+ url => 'search' . ( $u->is_community ? "?search_user=" . $u->user : ''
),
+ image => 'search.png',
+ text_ml => "userlinkbar.search",
+ title_ml => "userlinkbar.search.title",
+ class => 'search',
+ };
+
+ return $self->fix_link( $link );
+}
+
=head2 C<< $obj->buyaccount >>
Returns a hashref with the appropriate icon/link/text for buying this user a
paid account.
diff -r 6865fbf1b5da -r 4dedfba1fe16 cgi-bin/DW/Pay.pm
--- a/cgi-bin/DW/Pay.pm Sat Jul 25 04:43:19 2009 +0000
+++ b/cgi-bin/DW/Pay.pm Sat Jul 25 04:48:46 2009 +0000
@@ -490,6 +490,12 @@ sub update_paid_status {
if $dbh->err;
}
+ # and now, at this last step, we kick off a job to check if this user
+ # needs to have their search index setup/messed with.
+ if ( @LJ::SPHINX_SEARCHD && ( my $sclient = LJ::theschwartz() ) ) {
+ $sclient->insert_jobs( TheSchwartz::Job->new_from_array(
'DW::Worker::Sphinx::Copier', { userid => $u->id } ) );
+ }
+
return 1;
}
diff -r 6865fbf1b5da -r 4dedfba1fe16 cgi-bin/LJ/Entry.pm
--- a/cgi-bin/LJ/Entry.pm Sat Jul 25 04:43:19 2009 +0000
+++ b/cgi-bin/LJ/Entry.pm Sat Jul 25 04:48:46 2009 +0000
@@ -1982,6 +1982,11 @@ sub delete_entry
# delete all comments
LJ::delete_all_comments($u, 'L', $jitemid);
+ # fired to delete the post from the Sphinx search database
+ if ( @LJ::SPHINX_SEARCHD && ( my $sclient = LJ::theschwartz() ) ) {
+ $sclient->insert_jobs( TheSchwartz::Job->new_from_array(
'DW::Worker::Sphinx::Copier', { userid => $u->id } ) );
+ }
+
return 1;
}
diff -r 6865fbf1b5da -r 4dedfba1fe16 cgi-bin/ljprotocol.pl
--- a/cgi-bin/ljprotocol.pl Sat Jul 25 04:43:19 2009 +0000
+++ b/cgi-bin/ljprotocol.pl Sat Jul 25 04:48:46 2009 +0000
@@ -1540,6 +1540,11 @@ sub postevent
}
push @jobs, LJ::EventLogRecord::NewEntry->new($entry)->fire_job;
+ # update the sphinx search engine
+ if ( @LJ::SPHINX_SEARCHD ) {
+ push @jobs, TheSchwartz::Job->new_from_array(
'DW::Worker::Sphinx::Copier', { userid => $uowner->id } );
+ }
+
my $sclient = LJ::theschwartz();
if ($sclient && @jobs) {
my @handles = $sclient->insert_jobs(@jobs);
@@ -1887,6 +1892,12 @@ sub editevent
my $entry = LJ::Entry->new($ownerid, jitemid => $itemid);
LJ::EventLogRecord::EditEntry->new($entry)->fire;
+
+ # fired to copy the post over to the Sphinx search database
+ if ( @LJ::SPHINX_SEARCHD && ( my $sclient = LJ::theschwartz() ) ) {
+ $sclient->insert_jobs( TheSchwartz::Job->new_from_array(
'DW::Worker::Sphinx::Copier', { userid => $ownerid } ) );
+ }
+
LJ::run_hooks("editpost", $entry);
return $res;
diff -r 6865fbf1b5da -r 4dedfba1fe16 htdocs/img/silk/profile/search.png
Binary file htdocs/img/silk/profile/search.png has changed
diff -r 6865fbf1b5da -r 4dedfba1fe16 htdocs/search.bml
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/htdocs/search.bml Sat Jul 25 04:48:46 2009 +0000
@@ -0,0 +1,167 @@
+<?_c
+#
+# search.bml
+#
+# A very basic search function that allows you to search a given journal in
+# a few particular cases.
+#
+# Authors:
+# Mark Smith <[email protected]>
+#
+# 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'.
+#
+_c?><?page
+body<=
+<?_code
+{
+ use strict;
+ use vars qw/ %POST %GET /;
+ use Storable;
+
+ # FIXME: english strip and make the UI a lot better :)
+
+ # if no gearman, bail
+ my $gc = LJ::gearman_client();
+ return "Sorry, content searching is not configured on this server.\n"
+ unless $gc && @LJ::SPHINX_SEARCHD;
+
+ # for pages that require authentication
+ 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;
+
+ # and make sure we're searching a paid account
+ return "Sorry, searches are only available for paid accounts.\n"
+ unless $su->is_paid;
+
+ # for later
+ my $sulj = $su->ljuser_display;
+ my $suu = $su->user;
+
+ # 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.\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;
+ }
+
+ # 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>);
+ }
+
+ return $ret;
+ }
+
+ # give them a form to search...
+ my $fa = LJ::form_auth();
+ return <<EOF;
+
+<?p Searching $sulj ... p?>
+
+<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>
+
+<?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?>
+
+EOF
+}
+_code?>
+<=body
+title=>Content Search
+head<=
+<style type="text/css">
+.exc { padding-left: 1em; font-style: italic; font-size: smaller; }
+.stats { font-style: italic; }
+.searchres { margin: 0.2em 0em 0.2em 2em; }
+</style>
+<=head
+page?>
_______________________________________________
dw-changelog mailing list
[email protected]
http://lists.dwscoalition.org/mailman/listinfo/dw-changelog