package MT::App::SearchPN; ## ---------------------------------------------------------------------------- # MT::App::SearchPN # - ver 0.02 # bug-fix : MT::Entry->count で status=>2 を追加。 # # - ver 0.01 # - MT::App::Search のページ処理版。plugin で実装したかったけど断念。ダサイ実装になってしまった・・・ # まだ、_new_comments検索には未対応 # MT::ObjectDriver::DBI::mysql は regex 句対応版にオーバーライド # # Author: drk # License: same as Perl ## ---------------------------------------------------------------------------- use strict; use File::Spec; use MT::Util qw( encode_html ); use constant TEMP_TABLE => 'temp_Table'; use MT::App; use MT::ConfigMgr; use POSIX; @MT::App::SearchPN::ISA = qw( MT::App ); our $imported = 0; ## ---------------------------------------------------------------------------- ## MT::ObjectDriver::DBI::mysql をオーバーライド。regexp 句に対応 ## ---------------------------------------------------------------------------- sub _import { my $app = shift; # return if($imported); # $imported=1; no strict 'refs'; my $mysqlsuper = MT::ObjectDriver::DBI::mysql->can('_prepare_from_where'); *{"MT::ObjectDriver::DBI::mysql::_prepare_from_where"} = sub { my($driver, $class, $terms, $args) = @_; my($tbl, $sql, $bind) = &$mysqlsuper(@_); return ($tbl, $sql, $bind) unless(defined $args->{regexp}); #print STDERR "$tbl\n$sql:",join(',',@$bind),"\n"; my @w_terms = sort keys %{$args->{regexp}}; my @w_bind = map{ @{$args->{regexp}->{$_}} } @w_terms; ## where 句があるとき if ($sql=~/^(.+where.+?\n)(.*)$/ims) { my $cond = join(' and ', @w_terms) . "\n"; $sql = "$1 and $cond $2"; push @$bind, @w_bind; ## where 句がないとき } elsif ($sql=~/^(.+from.+?\n)(.*)$/ims) { my $cond = "where " . join(' and ', @w_terms) . "\n"; $sql = "$1 $cond $2"; push @$bind, @w_bind; } #print STDERR "->$tbl\n$sql:",join(',',@$bind),"\n"; return ($tbl, $sql, $bind); }; } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::init ## 変更点:$app->{search_page}, $app->{search_limit} 追加 ## ---------------------------------------------------------------------------- sub init { my $app = shift; $app->SUPER::init(@_) or return; $app->add_methods( search => \&execute ); $app->{default_mode} = 'search'; $app->{user_class} = 'MT::Author'; $app->{charset} = $app->{cfg}->PublishCharset; my $q = $app->{query}; my $cfg = $app->{cfg}; ## Check whether IP address has searched in the last ## minute which is still progressing. If so, block it. if (eval { require DB_File; 1 }) { my $file = File::Spec->catfile($cfg->TempDir, 'mt-throttle.db'); my $DB = tie my %db, 'DB_File', $file; if ($DB) { my $ip = $app->remote_ip; if (my $time = $db{$ip}) { if ($time > time - 10) { ## Within the last minute. return $app->error($app->translate( "You are currently performing a search. Please wait " . "until your search is completed.")); } } $db{$ip} = time; undef $DB; untie %db; $app->{__have_throttle} = 1; } } my %no_override = map { $_ => 1 } split /\s*,\s*/, $cfg->NoOverride; ## Combine user-selected included/excluded blogs ## with config file settings. for my $type (qw( IncludeBlogs ExcludeBlogs )) { $app->{searchparam}{$type} = { }; if (my $list = $cfg->$type()) { $app->{searchparam}{$type} = { map { $_ => 1 } split /\s*,\s*/, $list }; } next if $no_override{$type}; for my $blog_id ($q->param($type)) { $app->{searchparam}{$type}{$blog_id} = 1; } } ## If IncludeBlogs has not been set, we need to build a list of ## the blogs to search. If ExcludeBlogs was set, exclude any blogs ## set in that list from our final list. if (!keys %{ $app->{searchparam}{IncludeBlogs} }) { my $exclude = $app->{searchparam}{ExcludeBlogs}; require MT::Blog; my $iter = MT::Blog->load_iter; while (my $blog = $iter->()) { $app->{searchparam}{IncludeBlogs}{$blog->id} = 1 unless $exclude && $exclude->{$blog->id}; } } ## Set other search params--prefer per-query setting, default to ## config file. for my $key (qw( RegexSearch CaseSearch ResultDisplay SearchCutoff CommentSearchCutoff ExcerptWords SearchElement Type MaxResults SearchSortBy )) { $app->{searchparam}{$key} = $no_override{$key} ? $cfg->$key() : ($q->param($key) || $cfg->$key()); } $app->{searchparam}{Template} = $q->param('Template') || ($app->{searchparam}{Type} eq 'newcomments' ? 'comments' : 'default'); ## Define alternate user templates from config file if (my @tmpls = $cfg->AltTemplate) { for my $tmpl (@tmpls) { my($nickname, $file) = split /\s+/, $tmpl; $app->{templates}{$nickname} = $file; } } $app->{templates}{default} = $cfg->DefaultTemplate; $app->{searchparam}{SearchTemplatePath} = $cfg->SearchTemplatePath; ## Set search_string (for display only) if ($app->{searchparam}{Type} eq 'straight') { $app->{search_string} = $q->param('search') || ''; } ## Get login information if user is logged in (via cookie). ## If no login cookie, this fails silently, and that's fine. ($app->{user}) = $app->login; $app; } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::DESTROY ## 変更点:なし ## ---------------------------------------------------------------------------- sub DESTROY { my $app = shift; if ($app->{__have_throttle}) { my $file = File::Spec->catfile(MT::ConfigMgr->instance()->TempDir, 'mt-throttle.db'); if (tie my %db, 'DB_File', $file) { delete $db{$app->remote_ip}; untie %db; } } } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::execute ## 変更点:$ctx->stash に 'results_total', 'search_page', 'search_limit' 追加 ## ---------------------------------------------------------------------------- sub execute { my $app = shift; $app->_import(); ## added by drk 05.12.10 paginate search results / $app->{search_string}= $app->{query}->param('search') || ''; $app->{search_page} = $app->{query}->param('page') || 0; ## page number $app->{search_limit} = $app->{query}->param('limit') || 10; ## limit delete $app->{results_total}; ## / added by drk 05.12.10 paginate search results my @results; if ($app->{searchparam}{Type} eq 'newcomments') { $app->_new_comments or return $app->error($app->translate( "Search failed: [_1]", $app->errstr)); @results = $app->{results} ? @{ $app->{results} } : (); } else { $app->_straight_search or return $app->error($app->translate( "Search failed: [_1]", $app->errstr)); ## Results are stored per-blog, so we sort the list of blogs by name, ## then add in the results to the final list. my $col = $app->{searchparam}{SearchSortBy}; my $order = $app->{searchparam}{ResultDisplay} || 'ascend'; for my $blog (sort keys %{ $app->{results} }) { my @res = @{ $app->{results}{$blog} }; if ($col) { @res = $order eq 'ascend' ? sort { $a->{entry}->$col() cmp $b->{entry}->$col() } @res : sort { $b->{entry}->$col() cmp $a->{entry}->$col() } @res; } $res[0]{blogheader} = 1; push @results, @res; } } ## We need to put a blog in context so that includes and <$MTBlog*$> ## tags will work, if they are used. So we choose the first blog in ## the result list. If there is no result list, just load the first ## blog from the database. my($blog); if (@results) { $blog = $results[0]{blog}; } unless ($blog) { my $include = $app->{searchparam}{IncludeBlogs}; if ($include && keys(%$include) == 1) { $blog = MT::Blog->load((keys %$include)[0]); } else { $blog = MT::Blog->load; } } ## Initialize and set up the context object. my $ctx = MT::App::SearchPN::Context->new; $ctx->stash('blog', $blog); $ctx->stash('blog_id', $blog->id); $ctx->stash('results', \@results); $ctx->stash('user', $app->{user}); if (my $str = $app->{search_string}) { $ctx->stash('search_string', encode_html($str)); } ## added by drk 05.12.10 paginate search results / $ctx->stash('results_total', $app->{results_total}{$blog->name}); $ctx->stash('search_page', $app->{search_page}); $ctx->stash('search_limit', $app->{search_limit}); ## / added by drk 05.12.10 paginate search results ## Load the search template. my $tmpl_file = $app->{templates}{ $app->{searchparam}{Template} } or return $app->error($app->translate("No alternate template is specified for the " . "Template '[_1]'", $app->{searchparam}{Template})); my $tmpl = File::Spec->catfile($app->{searchparam}{SearchTemplatePath}, $tmpl_file); local *FH; open FH, $tmpl or return $app->error($app->translate( "Opening local file '[_1]' failed: [_2]", $tmpl, "$!" )); my $str; { local $/; $str = }; close FH; # $app->set_language($blog->language); # $str = $app->l10n_filter($str); $str = MT->translate_templatized($str); ## Compile and build the search template with results. require MT::Builder; my $build = MT::Builder->new; my $tokens = $build->compile($ctx, $str) or return $app->error($app->translate( "Building results failed: [_1]", $build->errstr)); defined(my $res = $build->build($ctx, $tokens, { NoSearch => $app->{query}->param('help') || ($app->{searchparam}{Type} ne 'newcomments' && (!$ctx->stash('search_string') || $ctx->stash('search_string') !~ /\S/)) ? 1 : 0, NoSearchResults => $ctx->stash('search_string') && $ctx->stash('search_string') =~ /\S/ && !scalar @results, SearchResults => scalar @results, } )) or return $app->error($app->translate( "Building results failed: [_1]", $build->errstr)); $app->_set_form_elements($res); } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::_straight_search ## 変更点:ページ処理いろいろ追加 ## ---------------------------------------------------------------------------- sub _straight_search { my $app = shift; return 1 unless $app->{search_string} =~ /\S/; $app->log($app->translate("Search: query for '[_1]'", $app->{search_string})); ## Parse, tokenize and optimize the search query. $app->query_parse; ## added by drk 05.12.10 paginate search results / my $mgr = MT::ConfigMgr->instance; my $regexpcond = ''; my @regexpval; if($mgr->ObjectDriver eq 'DBI::mysql') { map { my $type = $_->{type}; my $atom = $_->{atom}; $atom=~ s/\?://g; push @regexpval, ($atom,$atom); $regexpcond .= ' and ' if($regexpcond); if($type eq 'AND') { $regexpcond .= '(entry_text regexp ? or entry_text_more regexp ?)'; } else { $regexpcond .= '(entry_text not regexp ? and entry_text_more not regexp ?)'; } } @{$app->{searchparam}{search_keys}}; } ## / added by drk 05.12.10 paginate search results ## Load available blogs and iterate through entries looking for search term require MT::Util; require MT::Blog; require MT::Entry; my %terms = (status => MT::Entry::RELEASE()); my %args = (direction => $app->{searchparam}{ResultDisplay}, 'sort' => 'created_on'); ## added by drk 05.12.10 paginate search results / if($mgr->ObjectDriver eq 'DBI::mysql') { $args{'limit'} = $app->{search_limit}; $args{'offset'} = $app->{search_limit}*$app->{search_page}; $args{'regexp'} = { $regexpcond => [@regexpval] }; } ## / added by drk 05.12.10 paginate search results if ($app->{searchparam}{SearchCutoff} && $app->{searchparam}{SearchCutoff} != 9999999) { my @ago = MT::Util::offset_time_list(time - 3600 * 24 * $app->{searchparam}{SearchCutoff}); my $ago = sprintf "%04d%02d%02d%02d%02d%02d", $ago[5]+1900, $ago[4]+1, @ago[3,2,1,0]; $terms{created_on} = [ $ago ]; $args{range} = { created_on => 1 }; } my $iter = MT::Entry->load_iter(\%terms, \%args); my(%blogs, %hits); my $max = $app->{searchparam}{MaxResults}; my $include = $app->{searchparam}{IncludeBlogs}; my %tmp_blog; ## added by drk 05.12.10 paginate search results while (my $entry = $iter->()) { my $blog_id = $entry->blog_id; next unless $include->{$blog_id}; next if $hits{$blog_id} && $hits{$blog_id} >= $max; if ($app->_search_hit($entry)) { my $blog = $blogs{$blog_id} || MT::Blog->load($blog_id); $app->_store_hit_data($blog, $entry, $hits{$blog_id}++); $tmp_blog{$blog->id} = $blog unless($tmp_blog{$blog->id}); ## added by drk 05.12.10 paginate search results } } ## added by drk 05.12.10 paginate search results / return 1 unless($mgr->ObjectDriver eq 'DBI::mysql'); map{ my $blog = $tmp_blog{$_}; $app->{results_total}{$blog->name} = MT::Entry->count({blog_id => $blog->id, status => 2,},,{ 'regexp' => { $regexpcond => [@regexpval] }, }); } keys %tmp_blog; ## / added by drk 05.12.10 paginate search results 1; } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::_new_comments ## 変更点:なし ## ---------------------------------------------------------------------------- sub _new_comments { my $app = shift; return 1 if $app->{query}->param('help'); $app->log($app->translate("Search: new comment search")); require MT::Entry; require MT::Blog; require MT::Util; my %args = ('join' => [ 'MT::Comment', 'entry_id', {}, { 'sort' => 'created_on', direction => 'descend', unique => 1, } ]); if ($app->{searchparam}{CommentSearchCutoff} && $app->{searchparam}{CommentSearchCutoff} != 9999999) { my @ago = MT::Util::offset_time_list(time - 3600 * 24 * $app->{searchparam}{CommentSearchCutoff}); my $ago = sprintf "%04d%02d%02d%02d%02d%02d", $ago[5]+1900, $ago[4]+1, @ago[3,2,1,0]; $args{'join'}->[2]{created_on} = [ $ago ]; $args{'join'}->[3]{range} = { created_on => 1 }; } elsif ($app->{searchparam}{MaxResults} && $app->{searchparam}{MaxResults} != 9999999) { $args{limit} = $app->{searchparam}{MaxResults}; } my $iter = MT::Entry->load_iter({ status => MT::Entry::RELEASE() }, \%args); my %blogs; my $include = $app->{searchparam}{IncludeBlogs}; while (my $entry = $iter->()) { next unless $include->{ $entry->blog_id }; my $blog = $blogs{ $entry->blog_id } || MT::Blog->load($entry->blog_id); $app->_store_hit_data($blog, $entry); } 1; } ## ---------------------------------------------------------------------------- ## MT::App::SearchPN::_set_form_elements ## 変更点:なし ## ---------------------------------------------------------------------------- sub _set_form_elements { my($app, $tmpl) = @_; ## Fill in user-defined template with proper form settings. if ($app->{searchparam}{Type} eq 'newcomments') { if ($app->{searchparam}{CommentSearchCutoff}) { $tmpl =~ s/(.*