mourningdove/cgi-bin/DW/Controller/Search/Journal.pm

230 lines
7.2 KiB
Perl
Raw Permalink Normal View History

2026-05-24 01:03:05 +00:00
#!/usr/bin/perl
#
# DW::Controller::Search::Journal
#
# Conversion of search.bml, used for full text search of journals.
#
# Authors:
# Mark Smith <mark@dreamwidth.org>
# Jen Griffin <kareila@livejournal.com>
#
# Copyright (c) 2009-2015 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::Controller::Search::Journal;
use strict;
use DW::Controller;
use DW::Routing;
use DW::Template;
use DW::FormErrors;
use Storable;
DW::Routing->register_string( '/search', \&search_handler, app => 1 );
my $gc = LJ::gearman_client();
sub _do_search {
return unless $gc && @LJ::SPHINX_SEARCHD;
my ($arg) = @_;
$arg = Storable::nfreeze($arg);
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 );
return $result;
}
sub search_handler {
# before doing the usual setup, first make sure we have Gearman working
return error_ml('/search.tt.error.notconfigured') unless $gc && @LJ::SPHINX_SEARCHD;
my ( $ok, $rv ) = controller( form_auth => 1 );
return $rv unless $ok;
my $remote = $rv->{remote};
my $r = DW::Request->get;
my $errors = DW::FormErrors->new;
# some arguments are passed via GET even when posting
my $get_args = $r->get_args;
my $offset = 0;
$offset += $get_args->{offset} if defined $get_args->{offset};
my $q = LJ::strip_html( LJ::trim( $get_args->{query} ) ); # may be overridden by POST
# if there's an offset, return a fatal error if it's out of bounds
return error_ml('/search.tt.error.wrongoffset') if $offset < 0 || $offset > 1000;
# we can GET a user name, but we can't use the controller's specify_user
# convenience method for this, because it will always default to remote,
# and that will override selection of site search
my $su = LJ::load_user( $get_args->{user} ); # may be overridden by POST
return error_ml('error.invaliduser') if $get_args->{user} && !$su;
# form processing
if ( $r->did_post ) {
my $post_args = $r->post_args;
$su = LJ::load_user( $post_args->{mode} ) if $post_args->{mode};
# 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
$errors->add( "mode", ".error.forbidden" )
if $su && !$su->allow_search_by($remote);
# make sure we got a query
$q = LJ::strip_html( LJ::trim( $post_args->{query} ) );
$errors->add( "query", ".error.noquery" ) unless $q;
$errors->add( "query", ".error.longquery" ) if $q && length $q > 255;
my $sby = $post_args->{sort_by} || 'new';
$sby = 'new' unless $sby =~ /^(?:new|old|rel)$/;
# see if the user wants to include comments, then verify that they are
# allowed to do so; if not, just ignore that they checked the checkbox
my $wc = $post_args->{with_comments} ? 1 : 0;
my $wc_u = $su || $remote;
$wc &&= $wc_u->is_paid; # comment search is a paid account feature
$rv->{sort_by} = $sby;
$rv->{wc} = $wc;
# at this point, we should have good form data; the form can still
# have errors below, but they aren't the fault of the user input
unless ( $errors->exist ) {
# we have to set a few flags on what to search. default to public and no bits.
my ( $ignore_security, $allowmask ) = ( 0, 0 );
if ($su) {
# if it's you, all posts, all bits
if ( $remote->equals($su) ) {
$ignore_security = 1;
}
elsif ( $su->is_community ) {
# if it's a community you administer, also all bits
if ( $remote->can_manage($su) ) {
$ignore_security = 1;
# for communities, member_of is the same as allow mask (no custom groups)
}
else {
$allowmask = $remote->member_of($su);
}
# otherwise, if they trust you, get the mask ...
}
elsif ( $su->trusts($remote) ) {
$allowmask = $su->trustmask($remote);
}
}
# the arguments to the search (userid=0 implies global search)
my $args = {
userid => $su ? $su->id : 0,
remoteid => $remote->id,
query => $q,
offset => $offset,
sort_by => $sby,
ignore_security => $ignore_security,
allowmask => $allowmask,
include_comments => $wc
};
my $result = _do_search($args);
$errors->add( "", ".error.timedout" ) unless $result;
$rv->{result} = $result;
$rv->{matchct} = $result ? scalar( @{ $result->{matches} } ) : 0;
}
}
# end form processing
$rv->{su} = $su;
$rv->{q} = $q;
$rv->{offset} = $offset;
$rv->{did_post} = $r->did_post;
$rv->{errors} = $errors;
$rv->{formdata} = $r->post_args;
$rv->{load_uid} = sub { LJ::load_userid( $_[0] ) };
$rv->{tagprint} = sub {
join(
', ', map { "<strong>" . $_[0]->{$_} . "</strong>" }
keys %{ $_[0] }
);
};
$rv->{sec_icon} = sub {
return {
public => '',
private => LJ::img( "security-private", "" ),
usemask => LJ::img( "security-groups", "" ),
access => LJ::img( "security-protected", "" ),
}->{ $_[0] };
};
return DW::Template->render_template( 'search.tt', $rv );
}
# Translator's note: I couldn't find the format of the search result hash
# documented anywhere, so I reverse-engineered it from the display code.
# Preserving this here in case it comes in useful again in the future.
my $mock_results = {
total => 2,
time => '0.00',
matches => [
{
journalid => 1,
poster_id => 0,
security => 'public',
jtalkid => 0,
url => 'blank',
subject => 'testing',
excerpt => 'text goes here...',
eventtime => LJ::mysql_time,
tags => { 1 => 'foo', 2 => 'bar' }
},
{
journalid => 1,
poster_id => 0,
security => 'access',
jtalkid => 0,
url => 'blank',
subject => 'testing',
excerpt => 'text goes here...',
eventtime => LJ::mysql_time,
tags => {}
},
]
};
1;