Skip to content

Extensible Search Documentation

nick edited this page Apr 25, 2011 · 1 revision

Table of Contents

TagSearch

Description

TagSearch extension to mt-search allows Movable Type to search for entries by tags.

TagSearh extension to mt-search is included in the distribution.

Setting Up

Nothing. Movable Type uses it to search by tags by default.

Search Syntax

TagSearch allows the following syntax in url-encoded "tag" parameter.

"MovableType,TypePad,Vox" matches objects which has either MovableType, TypePad, or Vox tag.

"MovableType+TypePad+Vox" matches objects which has all three tags.

"TypePad OR Vox AND "Movable Type"" matches objects which has either TypePad or Vox tag *and* "Movable Type" tag.

Technical Description

TagSearch leverages Movable Type's standard extension method to MT::App which is registry based addition of "mode". In MT::App::Search::core_methods, the mode "tag" redirects to MT::App::Search::TagSearch::process. When the request such as "mt_search.cgi?__mode=tag&search=Tag1" comes in, MT::App::Search redirects the request to MT::App::Search::TagSearch::process.

In the process method the flow is mostly the same as in the default search, but is different when it comes to creating search terms and args, to populate entries based on tags.

TagSearch is therefore an example of how to extend mt-search by adding another "mode" to it so your plugin can add a behavior to MT::App::Search.

FreeTextSearch

Description

FreeText extension to mt-search allows Movable Type installation to leverage MySQL's freetext index. Mt-search can find search results faster when using freetext index than when using the default query which uses LIKE search in SQL queries.



FreeText extension to mt-search is included in the distribution.

Sytem Requirements

FreeText extension requires MySQL 4.1 or newer. Note freetext index is supported in MySQL 4.0, but Movable Type uses BOOLEAN MODE syntax to search for in the database, which was added to MySQL 4.1.

Setting Up

  1. Add configuration to use mt-ftsearch.cgi in place of mt-search.cgi.
    1. Add the following configuration to mt-config.cgi
  SearchScript mt-ftsearch.cgi
  1. Create fulltext index on your MySQL database
    1. By default, Movable Type searches for the query in keywords, text, text_more and title columns in the table mt_entry. Therefore, you have to create a fulltext index that covers all of the columns. Syntax will be like below.
  CREATE FULLTEXT INDEX mt_entry_fulltext_index ON mt_entry (entry_keywords, entry_text,entry_text_more, entry_title);
  1. Republish index templates
    1. Or more specifically, rebuild all the templates that refers to , because the name was changed to mt-ftsearch.cgi.
  2. You are all set!

Search Syntax

In the fulltext search, you can use the syntax that is supported by MySQL's Boolean Mode search syntax. For exampl, specify '"Movable Type" -WordPress' to search for entries which contain Movable Type but do not contain WordPress. For more information, please consult to MySQL documentation [1].

[1] http://dev.mysql.com/doc/refman/5.0/en/fulltext-boolean.html

Technical Description

FullText search leverages template method pattern offered in the new search framework. As you can see, MT::App::Search::FullText consisuts of one method, query_parse. The method set up "args" parameter in a hash which contains which columns should be included in the fulltext search. All of the other heavy lifting, including caching, loading of search results template, rendering and the actual searching and fetching of result entries, are done in MT::App::Search, the parent class of MT::App::Search::FullText.

Because MT::App::Search::FullText inherits from MT::App::Search (thus it is another type of MT::App that must be instantiated to work), mt-ftsearch.cgi passes MT::App::Search::FullText class to Bootstrap, which instantiates the class.

FreeText is therefore an example of how to extend mt-search by creating a new class and inherting from MT::App::Search to leverage the most out of it but changes its behavior a little.

Throttling

Description

By default, mt-search throttles search requests that takes more than five seconds to complete[1]. You can increase or decrease the number of seconds before throttling occurs by specifying the number in SearchThrottleSeconds directive in mt-config.cgi.

In addition, you as a plugin developer can add other methods to throttle requests, or can completely replace it.

[1] This uses SIGALRM and doesn't work on Windows.

How Throttling Works

MT::App::Search::process method which is the starting point of the search request, calls throttle_control method before processing the request. MT::App::Search::throttle_control method simply calls prepare_throttle callback and returns the result of it. So, if one of the callbacks registered to prepare_throttle sets "0" in the "result" argument, the request is immediately throttled in this phase. You can specify error messages in the callback to return to the client.

By default, MT::App::Search registers a method to prepare_throttle callback. The method, _default_throttle, verifies the request so it won't throttle the request if it's from a user already loggedin, or the IP address is registered in the whitelist. If none of these criteria meets, the method sets up SIGALARM in five seconds, in which the process simply dies.

_default_throttle does not touch the "result" argument so it won't stop request from being processed. However, if any of the processing before search results are rendered to the client takes five seconds, SIGALARM kicks off and the process dies.

Creating your own throttling methods

Plugins can register their methods to "prepare_throttle" callback to have chance to add their own mechanism of throttling. For example, you can add a throttling mechanism that checks uptime of the server to see if the server is in heavy load recently. If it is, the plugin can return "0" to the result to stop further processing of mt-search.

The default callback (_default_throttle) is registered in priority 5. Therefore, your plugin can add another throttling mechanism and take precedence if you add it in priority 4. In this case, if you add the "uptime throttle" in priority 4, your plugin will be called before the default throttle method. If your plugin returns 0, no further processing occurs and it saves load on the server.

Example Plugin

 use strict;
 use MT 4;
 use base 'MT::Plugin';
 our $VERSION = '1.0';
 
 my $plugin = MT::Plugin::SearchThrottle1->new({
     name        => "SearchThrottle1",
     version     => $VERSION,
     registry => {
         callbacks => {
             'cb1' => {
                 callback => 'MT::App::Search::prepare_throttle',
                 handler => \&throttle_by_uptime,
                 priority => 3,
             },
         },
     },
 });
 
 MT->add_plugin($plugin);
 
 sub throttle_by_uptime {
     my ( $cb, $app, $result, $messages ) = @_;
     # Don't bother if a callback proiritized higher
     # set up its throttle already
     return 1 if defined $$result;
     my $uptime = `uptime`;
     my @uptime = split ',', $uptime;
     my $up5 = $uptime[3];
     chomp $up5;
 
     if ( $up5 > 0.1 ) {
         push @$messages, $app->translate('Under heavy load');
         $$result = 0;
     }
     1;
 }
 1;

Safe MultiBlog Search

Description

Mt-search by default searches for entries from multiple blogs, based on IDs specified in IncludeBlogs query parameter. However, it can't be used to protect private blogs from being searched for because blog readers can easily modify URL to add blog id that is not included in the original query. You can use ExcludeBlogs and NoOverride directives wisely to prevent it from happnening, but it will often be too restrictive so even administrators can't search in private blogs, let alone tag based entry listing which uses mt-search.

How the new search may be used to solve the issue

MT::App::Search now calls a callback called "create_blog_list". No method is registered to the callback by default.

MT::App::Search executes the callback before processing IncludeBlogs et al. If there is no evidence that a plugin created the list of blogs to search for, MT::App::Search falls back to the default behavior which is the same as in the legacy mt-search, by looking at IncludeBlogs, ExcludeBlogs and NoOverride.

A plugin can register its method to the callback to create the list of blogs. Callback has to return the result in $list argument that is a reference to a hash which is sturctrued like the following, where 1 and 3 are the IDs of the blogs.

 $list = {
   '1' => 1,
   '3' => 1,
 };

Example Plugin - SearchKey

SearchKey plugin consists of a template tag (SearchKey) and a callback method to create_blog_list. SearchKey plugin works as follows.

  1. Add SearchKey template tag to the HTML form that has IncludeBlogs, with include_blogs argument in it. IncludeBlogs and include_blogs must have the same IDs.
           <input type="hidden" name="IncludeBlogs" value="1,2,3" />
           '''<input type="hidden" name="SearchKey" value="<MTSearchKey include_blogs="1,2,3>" />'''
           <input type="hidden" name="limit" value="20" />
 
           <input type="submit" accesskey="4" value="Search" />
       </form>
   </div>
 </div>
  1. Upon rebuilding, MTSearchKey template tag is evaluated and generate a sequence of numbers. It is a SHA-1 hash salted with the value of SecretToken that is automatically generated in Movable Type.
  2. When a request comes in, create_blog_list callback is executed and the plugin's own method runs. In the method, the value from IncludeBlogs are used to create another SHA-1 hash and compared to the value of SearchKey parameter. If they match, IncludeBlogs is not modified from the original, thus it is okay to proceed. If they don't match, it means IncludeBlogs was modified to something else from the original. The plugin returns 0 to stop processing of the request.

Code

config.yaml




lib/SearchKey.pm

 use strict;
 use MT::Util qw( perl_sha1_digest_hex );
 
 sub hdlr_searchkey {
     my ( $ctx, $args, $cond ) = @_;
 
     my $include_blogs = $args->{include_blogs};
     return q() unless $include_blogs;
 
     my @blogs = split ',', $include_blogs;
     @blogs = sort { $a <=> $b } @blogs;
     my $key = perl_sha1_digest_hex(
         join(',', @blogs) . $ctx->{config}->SecretToken
     );
     $key;
 }
 
 sub create_blog_list {
     my ( $cb, $app, $list, $processed ) = @_;
 
     my $include_blogs = $app->param('IncludeBlogs');
     my $searchkey     = $app->param('SearchKey');
     return 0 unless $include_blogs && $searchkey;
 
     my @blogs = split ',', $include_blogs;
     @blogs = sort { $a <=> $b } @blogs;
     my $key = perl_sha1_digest_hex(
         join(',', @blogs) . $app->config->SecretToken
     );
     return 0 if $key ne $searchkey;
 
     $list->{ $_ } = 1 foreach @blogs;
     $$processed = 1;
     1;
 }
 
 1;

Search for Authors

Description

Mt-search searches for entries and pages by default. However, a plugin can easily add what to search without any Perl code to write.

Adding search types

If you want to add author search to mt-search, your plugin should have the following registry settings:

 applications:
     new_search:
         default:
             types:
                 author:
                     columns:
                         name: like
                         nickname: like
                         email: 1
                         url: 1
                     sort: created_on
                     terms:
                         status: 1
  • "new_search" is the application id of the mt-search.
  • "default" is the mode of the application in which the search is executed.
  • "types" is the key to the registry in which you can add a search type.
  • "author" is what to search for. This corresponds to the key of the object types registry.
  • "columns" is where in the database table to search for the keyword.
    • you can specify either "like" or "1" to the name of the column. In this example, name and nickname columns are searched using LIKE query, while email and url columns are searched in exact match. For example, the query keyword "Melody" yields the following query:







  • "sort" is to specify what column to sort by default.
  • "terms" is to specify the default query terms. Note it has "status: 1" in the above example, thus the query example above has "author_status = 1" in it.

Add search template

In order to render search results the way you want, a search template is also required. For example, you can prepare a template like below and save it in MT_DIR/search_templates directory to show author name and his/her entries.

 &lt;MTSearchResults&gt;
    &lt;MTAuthorName setvar="author"&gt;
      &lt;p&gt;&lt;MTAuthorDisplayName&gt;:&lt;/p&gt;
      &lt;MTEntries author="$author" lastn="10" include_blogs="all"&gt;
          &lt;MTEntryTitle&gt;&lt;br /&gt;
      &lt;/MTEntries&gt;
    &lt;hr /&gt;
 &lt;/MTSearchResults&gt;

Define new template in mt-config.cgi

In order to put nickname to the template above, you have to add two lines in mt-config.cgi like below. In this case, it is assumed that the template above is saved in search_templates directory with the name "author_listing.tmpl".

 SearchAltTemplate feed results_feed.tmpl
 SearchAltTemplate authors author_listing.tmpl

Note that "feed" is also added. This is to avoid the default value to the SearchAltTemplate config directive not be overwritten.

Constructing search query

With these settings, you can construct search query like below to search for authors.

 <MTCGIPath><MTSearchScript>?search=Melody&limit=20&type=author&Template=authors

Note these two parameters: type=author and Template=authors.

Comment Pagination by mt-search

MT::App::Search's ability to add what to search for can be leveraged further to allow pagination of comments in the entry archive template.

Modify Comments template module

To support Ajax based comment pagination, Comments module has to be modified. Open default_templates/comments.mtml in an editor and find which is on the fourth line.

Start selecting lines immediately below the line (iow, from the fifth line), all the way down to the closing tag and remove all the selected lines.

Add the following template snippet to the place where you just removed, that is, below and up above the <mt:ignore></mt:ignore>.

<div id="comments" class="comments"> <script type="text/javascript">

/* <![CDATA[*/
     &lt;h2 class="comments-header"&gt;&lt;$MTEntryCommentCount singular="&lt;__trans phrase="1 Comment"&gt;" plural="&lt;__trans phrase="# Comments"&gt;" none="&lt;__trans phrase="No Comments"&gt;"$&gt;&lt;/h2&gt;
     &lt;div class="comments-content"&gt;
                 &lt;div class="comment-content" id="comments_content"&gt;
 
                 &lt;/div&gt;
     &lt;/div&gt;

Save the file.  Go to MT's template listing screen and refresh the template so the changes above will be loaded to the system.


== Add comment to the search type ==

"config.yaml" file can be written like below to add comment search.  Note the setting is to seaerch for entry_id in exact match.

 name: ajc
 version: 1.0
 id: ajc
 
 applications:
     new_search:
         default:
             types:
                 comment:
                     columns:
                         entry_id: 1
                     sort: created_on
                     terms:
                         visible: 1


== Add rendering method to the new app ==

The example plugin adds new class which derives from MT::App::Search (just like FreeText search).  It only adds renderjsc method which is called during rendering the search results.  Note in the above JavaScript block, the url has "format=jsc" parameter in the end.  When MT::App::Search sees "format" parameter, it tries to call "renderFORMAT" method where FORMAT is replaced to the value of the parameter.  Thus, in this case, renderjsc method of the app is called.

renderjsc method calls superclass's render method to render search results in HTML and put the result in the JSON result, that is later rendered by embedding all of the content to innerHTML of the element.


=== CODE (lib/ajc.pm) ===

 use strict;
 
 package ajc;
 
 use base qw( MT::App::Search );
 
 sub renderjsc {
     my $app = shift;
     my $out = $app->SUPER::render( @_ );
     my $result;
     if (ref($out) && ($out->isa('MT::Template'))) {
         defined( $result = $app->build_page($out) )
             or return $app->error($out->errstr);
     }
     else {
         $result = $out;
     }
 
     return $app->json_result({ content => $result });
 }
 
 1;


== Create mt-csearch.cgi ==

Because lib/ajc.pm is a new MT::App application, you should create a new cgi file to instantiate it.  The content is simple, mostly copied from mt-search.cgi.  The difference is to add plugin's lib directory in the search path.  Save the file to MT_DIR.


=== CODE (mt-csearch.cgi) ===

 #!/usr/bin/perl -w
 
 # Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
 # This program is distributed under the terms of the
 # GNU General Public License, version 2.
 #
 # $Id: mt-csearch.cgi 1493 2008-03-07 11:32:52Z fumiakiy $
 
 use strict;
 use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/lib" : 'lib';
 use lib $ENV{MT_HOME} ? "$ENV{MT_HOME}/plugins/ajc/lib" : 'plugins/ajc/lib';
 use MT::Bootstrap App => 'ajc';


=== Add configuration ===

Just like FreeText, you have to add the following configuration to mt-config.cgi.
  SearchScript mt-csearch.cgi


== Add search template to render comments ==

In order to make comment listing look exactly like the default template, first copy a part of the template snippet from default_templates/comments.mtml and wrap it inside the MTSearchResults tag:

 &lt;MTSearchResults&gt;
        &lt;div id="comment-&lt;$MTCommentID$&gt;" class="comment&lt;mt:IfCommentParent&gt; comment-reply&lt;/mt:IfCommentParent&gt;"&gt;
            &lt;div class="inner"&gt;
                &lt;div class="comment-header"&gt;
                    &lt;div class="asset-meta"&gt;
                        &lt;span class="byline"&gt;
                            &lt;$MTCommentAuthorIdentity$&gt;
        &lt;mt:IfCommentParent&gt;
                            &lt;__trans phrase="[_1] replied to &lt;a href="[_2]"&gt;comment from [_3]&lt;/a&gt;" params="&lt;span class="vcard author"&gt;&lt;$MTCommentAuthorLink$&gt;&lt;/span&gt;%%&lt;mt:CommentParent&gt;&lt;$mt:CommentLink$&gt;&lt;/mt:CommentParent&gt;%%&lt;mt:CommentParent&gt;&lt;$MTCommentAuthor$&gt;&lt;/mt:CommentParent&gt;"&gt;
        &lt;mt:else&gt;
                            &lt;span class="vcard author"&gt;&lt;$MTCommentAuthorLink$&gt;&lt;/span&gt;
        &lt;/mt:IfCommentParent&gt;
                            | &lt;a href="&lt;$mt:CommentLink$&gt;"&gt;&lt;abbr class="published" title="&lt;$MTCommentDate format_name="iso8601"$&gt;"&gt;&lt;$MTCommentDate$&gt;&lt;/abbr&gt;&lt;/a&gt;
        &lt;MTIfCommentsAccepted&gt;
                            | &lt;$MTCommentReplyLink$&gt;
        &lt;/MTIfCommentsAccepted&gt;
                        &lt;/span&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div class="comment-content"&gt;
                    &lt;$MTCommentBody$&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
 &lt;/MTSearchResults&gt;

And add the following snippet below the closing MTSearchResults tag to add pager block.

 &lt;MTCurrentPage setvar="current_page"&gt;
                                &lt;div class="content-nav"&gt;
                                    &lt;MTIfPreviousResults&gt;&lt;a href="javascript:void(0)" rel="prev" onclick="getComments(&lt;MTGetVar name="current_page" op="-" value="1"&gt;)"&gt;&lt; &lt;__trans phrase="Previous"&gt;&lt;/a&gt;&nbsp;&nbsp;&lt;/MTIfPreviousResults&gt;&lt;MTPagerBlock&gt;&lt;MTIfCurrentPage&gt;&lt;MTVar name="__value__"&gt;&lt;MTElse&gt;&lt;a href="javascript:void(0)" onclick="getComments(&lt;MTVar name="__value__"&gt;)"&gt;&lt;MTVar name="__value__"&gt;&lt;/a&gt;&lt;/MTIfCurrentPage&gt;&lt;mt:unless name="__last__"&gt;&nbsp;&lt;/mt:unless&gt;&lt;/MTPagerBlock&gt;&lt;MTIfMoreResults&gt;&nbsp;&nbsp;&lt;a href="javascript:void(0)" rel="next" onclick="getComments(&lt;MTGetVar name="current_page" op="+" value="1"&gt;)"&gt;&lt;__trans phrase="Next"&gt; &gt;&lt;/a&gt;&lt;/MTIfMoreResults&gt;
                                &lt;/div&gt;


Save the file in search_templates with the name "comment_listing.tmpl".


== Republish entry archive template ==

Republish entry archive template and go to an entry.  Voila! comments are paginated.


= Add new filter to search syntax =


== Description ==

By default, MT::App::Search supports category and author filter.  You can for example query like ["Movable] to narrow search results based on the category of the entries.

You as a plugin developer can add the filter by registering your own filtering condition to the registry.


== Example Plugin - add commenter filter ==

The plugin described below adds "comment_by" filter to mt-search, so you can search entries and narrow results by who commented to the entries.

After this plugin is installed, you can search for entries by the following syntax.

 "Movable Type" comment_by:Melody


=== CODE ( commenter_filter.pl ) ===

 package MT::Plugin::CommenterFilter;
 
 use strict;
 use base 'MT::Plugin';
 our $VERSION = '1.0';
 
 my $plugin;
 $plugin = MT::Plugin::CommenterFilter->new({
     id => 'CommenterFilter',
     name => "CommenterFilter",
     version => $VERSION,
     registry => {
         applications => {
             new_search => {
                 default => {
                     types => {
                         entry => {
                             filter_types => {
                                 comment_by => \&_join_commenter
                             }
                         }
                     }
                 }
             }
         }
     }
 });
 MT->add_plugin($plugin);
 
 sub _join_commenter {
     my ( $app, $term ) = @_;
 
     my $query = $term->{term};
     if ( 'PHRASE' eq $term->{query} ) {
         $query =~ s/'/"/g;
     }
 
     my $lucene_struct = Lucene::QueryParser::parse_query( $query );
     if ( 'PROHIBITED' eq $term->{type} ) {
         $_->{type} = 'PROHIBITED' foreach @$lucene_struct;
     }
 
     my ( $terms ) = $app->_query_parse_core( $lucene_struct, { nickname => 'like' }, {} );
     return unless $terms && @$terms;
     push @$terms, '-and', {
         id => \'= comment_commenter_id',
     };
 
     require MT::Comment;
     require MT::Author;
     return MT::Comment->join_on( undef,
         { entry_id => \'= entry_id', blog_id => \'= entry_blog_id', visible => 1 },
         { join => MT::Author->join_on( undef, $terms, {} ),
           unique => 1 }
     );
 }
 
 1;

The plugin first registers itself to the registry, so the method can be called when MT::App::Search processes "comment_by" filter.  When comment_by filter is encountered, MT::App::Search calls _join_commenter method.  The method must return either undef or arguments for $args, which will later be used as the second argument to load or load_iter method.

_join_commenter method constructs a nested join clause, so, mt_entry joins to mt_comment joins to mt_author and search for author_nickname to match the specified keyword.  The resulting SQL query will be like this:

 SELECT ... FROM mt_entry, mt_comment, mt_author
 WHERE (...)
 AND comment_entry_id = entry_id
 AND comment_blog_id = entry_blog_id
 AND comment_visible = 1
 AND author_nickname LIKE '%Melody%'
 AND author_id = comment_commenter_id


== NOTE ==

Note this mechanism currently only allows to manipulate join clause.  Therefore, filters that can't be experessed in a single SQL query by using structures supported in MT::ObjectDriver, can't be created.  One such example filter that can't be created is the one to narrow down the results based on new metadata system (iow Custom Fields).


==Related Links==


<a href="http://cargames.com.au/" target="_blank">'''car games'''</a>

<a href="http://www.unbeatable.co.uk/pages/Electronics/TV-and-Video/Flat-Panel-Televisions/" target="_blank">'''lcd tv'''</a>
garbage]*/
                 &amp;lt&#59;/div&amp;gt&#59;
     &amp;lt&#59;/div&amp;gt&#59;

Save the file.  Go to MT&#39;s template listing screen and refresh the template so the changes above will be loaded to the system.


&#61;&#61; Add comment to the search type &#61;&#61;

&quot;config.yaml&quot; file can be written like below to add comment search.  Note the setting is to seaerch for entry_id in exact match.

 name&#58; ajc
 version&#58; 1.0
 id&#58; ajc
 
 applications&#58;
     new_search&#58;
         default&#58;
             types&#58;
                 comment&#58;
                     columns&#58;
                         entry_id&#58; 1
                     sort&#58; created_on
                     terms&#58;
                         visible&#58; 1


&#61;&#61; Add rendering method to the new app &#61;&#61;

The example plugin adds new class which derives from MT&#58;&#58;App&#58;&#58;Search (just like FreeText search).  It only adds renderjsc method which is called during rendering the search results.  Note in the above JavaScript block, the url has &quot;format&#61;jsc&quot; parameter in the end.  When MT&#58;&#58;App&#58;&#58;Search sees &quot;format&quot; parameter, it tries to call &quot;renderFORMAT&quot; method where FORMAT is replaced to the value of the parameter.  Thus, in this case, renderjsc method of the app is called.

renderjsc method calls superclass&#39;s render method to render search results in HTML and put the result in the JSON result, that is later rendered by embedding all of the content to innerHTML of the element.


&#61;&#61;&#61; CODE (lib/ajc.pm) &#61;&#61;&#61;

 use strict&#59;
 
 package ajc&#59;
 
 use base qw( MT&#58;&#58;App&#58;&#58;Search )&#59;
 
 sub renderjsc &#123;
     my $app &#61; shift&#59;
     my $out &#61; $app&#45;&gt;SUPER&#58;&#58;render( @_ )&#59;
     my $result&#59;
     if (ref($out) &amp;&amp; ($out&#45;&gt;isa(&#39;MT&#58;&#58;Template&#39;))) &#123;
         defined( $result &#61; $app&#45;&gt;build_page($out) )
             or return $app&#45;&gt;error($out&#45;&gt;errstr)&#59;
     &#125;
     else &#123;
         $result &#61; $out&#59;
     &#125;
 
     return $app&#45;&gt;json_result(&#123; content &#61;&gt; $result &#125;)&#59;
 &#125;
 
 1&#59;


&#61;&#61; Create mt&#45;csearch.cgi &#61;&#61;

Because lib/ajc.pm is a new MT&#58;&#58;App application, you should create a new cgi file to instantiate it.  The content is simple, mostly copied from mt&#45;search.cgi.  The difference is to add plugin&#39;s lib directory in the search path.  Save the file to MT_DIR.


&#61;&#61;&#61; CODE (mt&#45;csearch.cgi) &#61;&#61;&#61;

 &#35;&#33;/usr/bin/perl &#45;w
 
 &#35; Movable Type (r) Open Source (C) 2001&#45;2008 Six Apart, Ltd.
 &#35; This program is distributed under the terms of the
 &#35; GNU General Public License, version 2.
 &#35;
 &#35; $Id&#58; mt&#45;csearch.cgi 1493 2008&#45;03&#45;07 11&#58;32&#58;52Z fumiakiy $
 
 use strict&#59;
 use lib $ENV&#123;MT_HOME&#125; ? &quot;$ENV&#123;MT_HOME&#125;/lib&quot; &#58; &#39;lib&#39;&#59;
 use lib $ENV&#123;MT_HOME&#125; ? &quot;$ENV&#123;MT_HOME&#125;/plugins/ajc/lib&quot; &#58; &#39;plugins/ajc/lib&#39;&#59;
 use MT&#58;&#58;Bootstrap App &#61;&gt; &#39;ajc&#39;&#59;


&#61;&#61;&#61; Add configuration &#61;&#61;&#61;

Just like FreeText, you have to add the following configuration to mt&#45;config.cgi.
  SearchScript mt&#45;csearch.cgi


&#61;&#61; Add search template to render comments &#61;&#61;

In order to make comment listing look exactly like the default template, first copy a part of the template snippet from default_templates/comments.mtml and wrap it inside the MTSearchResults tag&#58;

 &amp;lt&#59;MTSearchResults&amp;gt&#59;
        &amp;lt&#59;div id&#61;&quot;comment&#45;&amp;lt&#59;$MTCommentID$&amp;gt&#59;&quot; class&#61;&quot;comment&amp;lt&#59;mt&#58;IfCommentParent&amp;gt&#59; comment&#45;reply&amp;lt&#59;/mt&#58;IfCommentParent&amp;gt&#59;&quot;&amp;gt&#59;
            &amp;lt&#59;div class&#61;&quot;inner&quot;&amp;gt&#59;
                &amp;lt&#59;div class&#61;&quot;comment&#45;header&quot;&amp;gt&#59;
                    &amp;lt&#59;div class&#61;&quot;asset&#45;meta&quot;&amp;gt&#59;
                        &amp;lt&#59;span class&#61;&quot;byline&quot;&amp;gt&#59;
                            &amp;lt&#59;$MTCommentAuthorIdentity$&amp;gt&#59;
        &amp;lt&#59;mt&#58;IfCommentParent&amp;gt&#59;
                            &amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;&#91;_1&#93; replied to &amp;lt&#59;a href&#61;&quot;&#91;_2&#93;&quot;&amp;gt&#59;comment from &#91;_3&#93;&amp;lt&#59;/a&amp;gt&#59;&quot; params&#61;&quot;&amp;lt&#59;span class&#61;&quot;vcard author&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentAuthorLink$&amp;gt&#59;&amp;lt&#59;/span&amp;gt&#59;%%&amp;lt&#59;mt&#58;CommentParent&amp;gt&#59;&amp;lt&#59;$mt&#58;CommentLink$&amp;gt&#59;&amp;lt&#59;/mt&#58;CommentParent&amp;gt&#59;%%&amp;lt&#59;mt&#58;CommentParent&amp;gt&#59;&amp;lt&#59;$MTCommentAuthor$&amp;gt&#59;&amp;lt&#59;/mt&#58;CommentParent&amp;gt&#59;&quot;&amp;gt&#59;
        &amp;lt&#59;mt&#58;else&amp;gt&#59;
                            &amp;lt&#59;span class&#61;&quot;vcard author&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentAuthorLink$&amp;gt&#59;&amp;lt&#59;/span&amp;gt&#59;
        &amp;lt&#59;/mt&#58;IfCommentParent&amp;gt&#59;
                            &#124; &amp;lt&#59;a href&#61;&quot;&amp;lt&#59;$mt&#58;CommentLink$&amp;gt&#59;&quot;&amp;gt&#59;&amp;lt&#59;abbr class&#61;&quot;published&quot; title&#61;&quot;&amp;lt&#59;$MTCommentDate format_name&#61;&quot;iso8601&quot;$&amp;gt&#59;&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentDate$&amp;gt&#59;&amp;lt&#59;/abbr&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;
        &amp;lt&#59;MTIfCommentsAccepted&amp;gt&#59;
                            &#124; &amp;lt&#59;$MTCommentReplyLink$&amp;gt&#59;
        &amp;lt&#59;/MTIfCommentsAccepted&amp;gt&#59;
                        &amp;lt&#59;/span&amp;gt&#59;
                    &amp;lt&#59;/div&amp;gt&#59;
                &amp;lt&#59;/div&amp;gt&#59;
                &amp;lt&#59;div class&#61;&quot;comment&#45;content&quot;&amp;gt&#59;
                    &amp;lt&#59;$MTCommentBody$&amp;gt&#59;
                &amp;lt&#59;/div&amp;gt&#59;
            &amp;lt&#59;/div&amp;gt&#59;
        &amp;lt&#59;/div&amp;gt&#59;
 &amp;lt&#59;/MTSearchResults&amp;gt&#59;

And add the following snippet below the closing MTSearchResults tag to add pager block.

 &amp;lt&#59;MTCurrentPage setvar&#61;&quot;current_page&quot;&amp;gt&#59;
                                &amp;lt&#59;div class&#61;&quot;content&#45;nav&quot;&amp;gt&#59;
                                    &amp;lt&#59;MTIfPreviousResults&amp;gt&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; rel&#61;&quot;prev&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTGetVar name&#61;&quot;current_page&quot; op&#61;&quot;&#45;&quot; value&#61;&quot;1&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59; &amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;Previous&quot;&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;nbsp&#59;&amp;nbsp&#59;&amp;lt&#59;/MTIfPreviousResults&amp;gt&#59;&amp;lt&#59;MTPagerBlock&amp;gt&#59;&amp;lt&#59;MTIfCurrentPage&amp;gt&#59;&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;&amp;lt&#59;MTElse&amp;gt&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;lt&#59;/MTIfCurrentPage&amp;gt&#59;&amp;lt&#59;mt&#58;unless name&#61;&quot;&#95;&#95;last&#95;&#95;&quot;&amp;gt&#59;&amp;nbsp&#59;&amp;lt&#59;/mt&#58;unless&amp;gt&#59;&amp;lt&#59;/MTPagerBlock&amp;gt&#59;&amp;lt&#59;MTIfMoreResults&amp;gt&#59;&amp;nbsp&#59;&amp;nbsp&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; rel&#61;&quot;next&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTGetVar name&#61;&quot;current_page&quot; op&#61;&quot;+&quot; value&#61;&quot;1&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;Next&quot;&amp;gt&#59; &amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;lt&#59;/MTIfMoreResults&amp;gt&#59;
                                &amp;lt&#59;/div&amp;gt&#59;


Save the file in search_templates with the name &quot;comment_listing.tmpl&quot;.


&#61;&#61; Republish entry archive template &#61;&#61;

Republish entry archive template and go to an entry.  Voila&#33; comments are paginated.


&#61; Add new filter to search syntax &#61;


&#61;&#61; Description &#61;&#61;

By default, MT&#58;&#58;App&#58;&#58;Search supports category and author filter.  You can for example query like &#91;&quot;Movable&#93; to narrow search results based on the category of the entries.

You as a plugin developer can add the filter by registering your own filtering condition to the registry.


&#61;&#61; Example Plugin &#45; add commenter filter &#61;&#61;

The plugin described below adds &quot;comment_by&quot; filter to mt&#45;search, so you can search entries and narrow results by who commented to the entries.

After this plugin is installed, you can search for entries by the following syntax.

 &quot;Movable Type&quot; comment_by&#58;Melody


&#61;&#61;&#61; CODE ( commenter_filter.pl ) &#61;&#61;&#61;

 package MT&#58;&#58;Plugin&#58;&#58;CommenterFilter&#59;
 
 use strict&#59;
 use base &#39;MT&#58;&#58;Plugin&#39;&#59;
 our $VERSION &#61; &#39;1.0&#39;&#59;
 
 my $plugin&#59;
 $plugin &#61; MT&#58;&#58;Plugin&#58;&#58;CommenterFilter&#45;&gt;new(&#123;
     id &#61;&gt; &#39;CommenterFilter&#39;,
     name &#61;&gt; &quot;CommenterFilter&quot;,
     version &#61;&gt; $VERSION,
     registry &#61;&gt; &#123;
         applications &#61;&gt; &#123;
             new_search &#61;&gt; &#123;
                 default &#61;&gt; &#123;
                     types &#61;&gt; &#123;
                         entry &#61;&gt; &#123;
                             filter_types &#61;&gt; &#123;
                                 comment_by &#61;&gt; \&amp;_join_commenter
                             &#125;
                         &#125;
                     &#125;
                 &#125;
             &#125;
         &#125;
     &#125;
 &#125;)&#59;
 MT&#45;&gt;add_plugin($plugin)&#59;
 
 sub _join_commenter &#123;
     my ( $app, $term ) &#61; @_&#59;
 
     my $query &#61; $term&#45;&gt;&#123;term&#125;&#59;
     if ( &#39;PHRASE&#39; eq $term&#45;&gt;&#123;query&#125; ) &#123;
         $query &#61;~ s/&#39;/&quot;/g&#59;
     &#125;
 
     my $lucene_struct &#61; Lucene&#58;&#58;QueryParser&#58;&#58;parse_query( $query )&#59;
     if ( &#39;PROHIBITED&#39; eq $term&#45;&gt;&#123;type&#125; ) &#123;
         $_&#45;&gt;&#123;type&#125; &#61; &#39;PROHIBITED&#39; foreach @$lucene_struct&#59;
     &#125;
 
     my ( $terms ) &#61; $app&#45;&gt;_query_parse_core( $lucene_struct, &#123; nickname &#61;&gt; &#39;like&#39; &#125;, &#123;&#125; )&#59;
     return unless $terms &amp;&amp; @$terms&#59;
     push @$terms, &#39;&#45;and&#39;, &#123;
         id &#61;&gt; \&#39;&#61; comment_commenter_id&#39;,
     &#125;&#59;
 
     require MT&#58;&#58;Comment&#59;
     require MT&#58;&#58;Author&#59;
     return MT&#58;&#58;Comment&#45;&gt;join_on( undef,
         &#123; entry_id &#61;&gt; \&#39;&#61; entry_id&#39;, blog_id &#61;&gt; \&#39;&#61; entry_blog_id&#39;, visible &#61;&gt; 1 &#125;,
         &#123; join &#61;&gt; MT&#58;&#58;Author&#45;&gt;join_on( undef, $terms, &#123;&#125; ),
           unique &#61;&gt; 1 &#125;
     )&#59;
 &#125;
 
 1&#59;

The plugin first registers itself to the registry, so the method can be called when MT&#58;&#58;App&#58;&#58;Search processes &quot;comment_by&quot; filter.  When comment_by filter is encountered, MT&#58;&#58;App&#58;&#58;Search calls _join_commenter method.  The method must return either undef or arguments for $args, which will later be used as the second argument to load or load_iter method.

_join_commenter method constructs a nested join clause, so, mt_entry joins to mt_comment joins to mt_author and search for author_nickname to match the specified keyword.  The resulting SQL query will be like this&#58;

 SELECT ... FROM mt_entry, mt_comment, mt_author
 WHERE (...)
 AND comment_entry_id &#61; entry_id
 AND comment_blog_id &#61; entry_blog_id
 AND comment_visible &#61; 1
 AND author_nickname LIKE &#39;%Melody%&#39;
 AND author_id &#61; comment_commenter_id


&#61;&#61; NOTE &#61;&#61;

Note this mechanism currently only allows to manipulate join clause.  Therefore, filters that can&#39;t be experessed in a single SQL query by using structures supported in MT&#58;&#58;ObjectDriver, can&#39;t be created.  One such example filter that can&#39;t be created is the one to narrow down the results based on new metadata system (iow Custom Fields).


&#61;&#61;Related Links&#61;&#61;


&lt;a href&#61;&quot;http&#58;//cargames.com.au/&quot; target&#61;&quot;_blank&quot;&gt;&#39;&#39;&#39;car games&#39;&#39;&#39;&lt;/a&gt;

&lt;a href&#61;&quot;http&#58;//www.unbeatable.co.uk/pages/Electronics/TV&#45;and&#45;Video/Flat&#45;Panel&#45;Televisions/&quot; target&#61;&quot;_blank&quot;&gt;&#39;&#39;&#39;lcd tv&#39;&#39;&#39;&lt;/a&gt;
garbage]CDATA[*/
                 &amp;lt&#59;/div&amp;gt&#59;
     &amp;lt&#59;/div&amp;gt&#59;

Save the file.  Go to MT&#39;s template listing screen and refresh the template so the changes above will be loaded to the system.


&#61;&#61; Add comment to the search type &#61;&#61;

&quot;config.yaml&quot; file can be written like below to add comment search.  Note the setting is to seaerch for entry_id in exact match.

 name&#58; ajc
 version&#58; 1.0
 id&#58; ajc
 
 applications&#58;
     new_search&#58;
         default&#58;
             types&#58;
                 comment&#58;
                     columns&#58;
                         entry_id&#58; 1
                     sort&#58; created_on
                     terms&#58;
                         visible&#58; 1


&#61;&#61; Add rendering method to the new app &#61;&#61;

The example plugin adds new class which derives from MT&#58;&#58;App&#58;&#58;Search (just like FreeText search).  It only adds renderjsc method which is called during rendering the search results.  Note in the above JavaScript block, the url has &quot;format&#61;jsc&quot; parameter in the end.  When MT&#58;&#58;App&#58;&#58;Search sees &quot;format&quot; parameter, it tries to call &quot;renderFORMAT&quot; method where FORMAT is replaced to the value of the parameter.  Thus, in this case, renderjsc method of the app is called.

renderjsc method calls superclass&#39;s render method to render search results in HTML and put the result in the JSON result, that is later rendered by embedding all of the content to innerHTML of the element.


&#61;&#61;&#61; CODE (lib/ajc.pm) &#61;&#61;&#61;

 use strict&#59;
 
 package ajc&#59;
 
 use base qw( MT&#58;&#58;App&#58;&#58;Search )&#59;
 
 sub renderjsc &#123;
     my $app &#61; shift&#59;
     my $out &#61; $app&#45;&gt;SUPER&#58;&#58;render( @_ )&#59;
     my $result&#59;
     if (ref($out) &amp;&amp; ($out&#45;&gt;isa(&#39;MT&#58;&#58;Template&#39;))) &#123;
         defined( $result &#61; $app&#45;&gt;build_page($out) )
             or return $app&#45;&gt;error($out&#45;&gt;errstr)&#59;
     &#125;
     else &#123;
         $result &#61; $out&#59;
     &#125;
 
     return $app&#45;&gt;json_result(&#123; content &#61;&gt; $result &#125;)&#59;
 &#125;
 
 1&#59;


&#61;&#61; Create mt&#45;csearch.cgi &#61;&#61;

Because lib/ajc.pm is a new MT&#58;&#58;App application, you should create a new cgi file to instantiate it.  The content is simple, mostly copied from mt&#45;search.cgi.  The difference is to add plugin&#39;s lib directory in the search path.  Save the file to MT_DIR.


&#61;&#61;&#61; CODE (mt&#45;csearch.cgi) &#61;&#61;&#61;

 &#35;&#33;/usr/bin/perl &#45;w
 
 &#35; Movable Type (r) Open Source (C) 2001&#45;2008 Six Apart, Ltd.
 &#35; This program is distributed under the terms of the
 &#35; GNU General Public License, version 2.
 &#35;
 &#35; $Id&#58; mt&#45;csearch.cgi 1493 2008&#45;03&#45;07 11&#58;32&#58;52Z fumiakiy $
 
 use strict&#59;
 use lib $ENV&#123;MT_HOME&#125; ? &quot;$ENV&#123;MT_HOME&#125;/lib&quot; &#58; &#39;lib&#39;&#59;
 use lib $ENV&#123;MT_HOME&#125; ? &quot;$ENV&#123;MT_HOME&#125;/plugins/ajc/lib&quot; &#58; &#39;plugins/ajc/lib&#39;&#59;
 use MT&#58;&#58;Bootstrap App &#61;&gt; &#39;ajc&#39;&#59;


&#61;&#61;&#61; Add configuration &#61;&#61;&#61;

Just like FreeText, you have to add the following configuration to mt&#45;config.cgi.
  SearchScript mt&#45;csearch.cgi


&#61;&#61; Add search template to render comments &#61;&#61;

In order to make comment listing look exactly like the default template, first copy a part of the template snippet from default_templates/comments.mtml and wrap it inside the MTSearchResults tag&#58;

 &amp;lt&#59;MTSearchResults&amp;gt&#59;
        &amp;lt&#59;div id&#61;&quot;comment&#45;&amp;lt&#59;$MTCommentID$&amp;gt&#59;&quot; class&#61;&quot;comment&amp;lt&#59;mt&#58;IfCommentParent&amp;gt&#59; comment&#45;reply&amp;lt&#59;/mt&#58;IfCommentParent&amp;gt&#59;&quot;&amp;gt&#59;
            &amp;lt&#59;div class&#61;&quot;inner&quot;&amp;gt&#59;
                &amp;lt&#59;div class&#61;&quot;comment&#45;header&quot;&amp;gt&#59;
                    &amp;lt&#59;div class&#61;&quot;asset&#45;meta&quot;&amp;gt&#59;
                        &amp;lt&#59;span class&#61;&quot;byline&quot;&amp;gt&#59;
                            &amp;lt&#59;$MTCommentAuthorIdentity$&amp;gt&#59;
        &amp;lt&#59;mt&#58;IfCommentParent&amp;gt&#59;
                            &amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;&#91;_1&#93; replied to &amp;lt&#59;a href&#61;&quot;&#91;_2&#93;&quot;&amp;gt&#59;comment from &#91;_3&#93;&amp;lt&#59;/a&amp;gt&#59;&quot; params&#61;&quot;&amp;lt&#59;span class&#61;&quot;vcard author&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentAuthorLink$&amp;gt&#59;&amp;lt&#59;/span&amp;gt&#59;%%&amp;lt&#59;mt&#58;CommentParent&amp;gt&#59;&amp;lt&#59;$mt&#58;CommentLink$&amp;gt&#59;&amp;lt&#59;/mt&#58;CommentParent&amp;gt&#59;%%&amp;lt&#59;mt&#58;CommentParent&amp;gt&#59;&amp;lt&#59;$MTCommentAuthor$&amp;gt&#59;&amp;lt&#59;/mt&#58;CommentParent&amp;gt&#59;&quot;&amp;gt&#59;
        &amp;lt&#59;mt&#58;else&amp;gt&#59;
                            &amp;lt&#59;span class&#61;&quot;vcard author&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentAuthorLink$&amp;gt&#59;&amp;lt&#59;/span&amp;gt&#59;
        &amp;lt&#59;/mt&#58;IfCommentParent&amp;gt&#59;
                            &#124; &amp;lt&#59;a href&#61;&quot;&amp;lt&#59;$mt&#58;CommentLink$&amp;gt&#59;&quot;&amp;gt&#59;&amp;lt&#59;abbr class&#61;&quot;published&quot; title&#61;&quot;&amp;lt&#59;$MTCommentDate format_name&#61;&quot;iso8601&quot;$&amp;gt&#59;&quot;&amp;gt&#59;&amp;lt&#59;$MTCommentDate$&amp;gt&#59;&amp;lt&#59;/abbr&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;
        &amp;lt&#59;MTIfCommentsAccepted&amp;gt&#59;
                            &#124; &amp;lt&#59;$MTCommentReplyLink$&amp;gt&#59;
        &amp;lt&#59;/MTIfCommentsAccepted&amp;gt&#59;
                        &amp;lt&#59;/span&amp;gt&#59;
                    &amp;lt&#59;/div&amp;gt&#59;
                &amp;lt&#59;/div&amp;gt&#59;
                &amp;lt&#59;div class&#61;&quot;comment&#45;content&quot;&amp;gt&#59;
                    &amp;lt&#59;$MTCommentBody$&amp;gt&#59;
                &amp;lt&#59;/div&amp;gt&#59;
            &amp;lt&#59;/div&amp;gt&#59;
        &amp;lt&#59;/div&amp;gt&#59;
 &amp;lt&#59;/MTSearchResults&amp;gt&#59;

And add the following snippet below the closing MTSearchResults tag to add pager block.

 &amp;lt&#59;MTCurrentPage setvar&#61;&quot;current_page&quot;&amp;gt&#59;
                                &amp;lt&#59;div class&#61;&quot;content&#45;nav&quot;&amp;gt&#59;
                                    &amp;lt&#59;MTIfPreviousResults&amp;gt&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; rel&#61;&quot;prev&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTGetVar name&#61;&quot;current_page&quot; op&#61;&quot;&#45;&quot; value&#61;&quot;1&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59; &amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;Previous&quot;&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;nbsp&#59;&amp;nbsp&#59;&amp;lt&#59;/MTIfPreviousResults&amp;gt&#59;&amp;lt&#59;MTPagerBlock&amp;gt&#59;&amp;lt&#59;MTIfCurrentPage&amp;gt&#59;&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;&amp;lt&#59;MTElse&amp;gt&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59;MTVar name&#61;&quot;&#95;&#95;value&#95;&#95;&quot;&amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;lt&#59;/MTIfCurrentPage&amp;gt&#59;&amp;lt&#59;mt&#58;unless name&#61;&quot;&#95;&#95;last&#95;&#95;&quot;&amp;gt&#59;&amp;nbsp&#59;&amp;lt&#59;/mt&#58;unless&amp;gt&#59;&amp;lt&#59;/MTPagerBlock&amp;gt&#59;&amp;lt&#59;MTIfMoreResults&amp;gt&#59;&amp;nbsp&#59;&amp;nbsp&#59;&amp;lt&#59;a href&#61;&quot;javascript&#58;void(0)&quot; rel&#61;&quot;next&quot; onclick&#61;&quot;getComments(&amp;lt&#59;MTGetVar name&#61;&quot;current_page&quot; op&#61;&quot;+&quot; value&#61;&quot;1&quot;&amp;gt&#59;)&quot;&amp;gt&#59;&amp;lt&#59;&#95;&#95;trans phrase&#61;&quot;Next&quot;&amp;gt&#59; &amp;gt&#59;&amp;lt&#59;/a&amp;gt&#59;&amp;lt&#59;/MTIfMoreResults&amp;gt&#59;
                                &amp;lt&#59;/div&amp;gt&#59;


Save the file in search_templates with the name &quot;comment_listing.tmpl&quot;.


&#61;&#61; Republish entry archive template &#61;&#61;

Republish entry archive template and go to an entry.  Voila&#33; comments are paginated.


&#61; Add new filter to search syntax &#61;


&#61;&#61; Description &#61;&#61;

By default, MT&#58;&#58;App&#58;&#58;Search supports category and author filter.  You can for example query like &#91;&quot;Movable&#93; to narrow search results based on the category of the entries.

You as a plugin developer can add the filter by registering your own filtering condition to the registry.


&#61;&#61; Example Plugin &#45; add commenter filter &#61;&#61;

The plugin described below adds &quot;comment_by&quot; filter to mt&#45;search, so you can search entries and narrow results by who commented to the entries.

After this plugin is installed, you can search for entries by the following syntax.

 &quot;Movable Type&quot; comment_by&#58;Melody


&#61;&#61;&#61; CODE ( commenter_filter.pl ) &#61;&#61;&#61;

 package MT&#58;&#58;Plugin&#58;&#58;CommenterFilter&#59;
 
 use strict&#59;
 use base &#39;MT&#58;&#58;Plugin&#39;&#59;
 our $VERSION &#61; &#39;1.0&#39;&#59;
 
 my $plugin&#59;
 $plugin &#61; MT&#58;&#58;Plugin&#58;&#58;CommenterFilter&#45;&gt;new(&#123;
     id &#61;&gt; &#39;CommenterFilter&#39;,
     name &#61;&gt; &quot;CommenterFilter&quot;,
     version &#61;&gt; $VERSION,
     registry &#61;&gt; &#123;
         applications &#61;&gt; &#123;
             new_search &#61;&gt; &#123;
                 default &#61;&gt; &#123;
                     types &#61;&gt; &#123;
                         entry &#61;&gt; &#123;
                             filter_types &#61;&gt; &#123;
                                 comment_by &#61;&gt; \&amp;_join_commenter
                             &#125;
                         &#125;
                     &#125;
                 &#125;
             &#125;
         &#125;
     &#125;
 &#125;)&#59;
 MT&#45;&gt;add_plugin($plugin)&#59;
 
 sub _join_commenter &#123;
     my ( $app, $term ) &#61; @_&#59;
 
     my $query &#61; $term&#45;&gt;&#123;term&#125;&#59;
     if ( &#39;PHRASE&#39; eq $term&#45;&gt;&#123;query&#125; ) &#123;
         $query &#61;~ s/&#39;/&quot;/g&#59;
     &#125;
 
     my $lucene_struct &#61; Lucene&#58;&#58;QueryParser&#58;&#58;parse_query( $query )&#59;
     if ( &#39;PROHIBITED&#39; eq $term&#45;&gt;&#123;type&#125; ) &#123;
         $_&#45;&gt;&#123;type&#125; &#61; &#39;PROHIBITED&#39; foreach @$lucene_struct&#59;
     &#125;
 
     my ( $terms ) &#61; $app&#45;&gt;_query_parse_core( $lucene_struct, &#123; nickname &#61;&gt; &#39;like&#39; &#125;, &#123;&#125; )&#59;
     return unless $terms &amp;&amp; @$terms&#59;
     push @$terms, &#39;&#45;and&#39;, &#123;
         id &#61;&gt; \&#39;&#61; comment_commenter_id&#39;,
     &#125;&#59;
 
     require MT&#58;&#58;Comment&#59;
     require MT&#58;&#58;Author&#59;
     return MT&#58;&#58;Comment&#45;&gt;join_on( undef,
         &#123; entry_id &#61;&gt; \&#39;&#61; entry_id&#39;, blog_id &#61;&gt; \&#39;&#61; entry_blog_id&#39;, visible &#61;&gt; 1 &#125;,
         &#123; join &#61;&gt; MT&#58;&#58;Author&#45;&gt;join_on( undef, $terms, &#123;&#125; ),
           unique &#61;&gt; 1 &#125;
     )&#59;
 &#125;
 
 1&#59;

The plugin first registers itself to the registry, so the method can be called when MT&#58;&#58;App&#58;&#58;Search processes &quot;comment_by&quot; filter.  When comment_by filter is encountered, MT&#58;&#58;App&#58;&#58;Search calls _join_commenter method.  The method must return either undef or arguments for $args, which will later be used as the second argument to load or load_iter method.

_join_commenter method constructs a nested join clause, so, mt_entry joins to mt_comment joins to mt_author and search for author_nickname to match the specified keyword.  The resulting SQL query will be like this&#58;

 SELECT ... FROM mt_entry, mt_comment, mt_author
 WHERE (...)
 AND comment_entry_id &#61; entry_id
 AND comment_blog_id &#61; entry_blog_id
 AND comment_visible &#61; 1
 AND author_nickname LIKE &#39;%Melody%&#39;
 AND author_id &#61; comment_commenter_id


&#61;&#61; NOTE &#61;&#61;

Note this mechanism currently only allows to manipulate join clause.  Therefore, filters that can&#39;t be experessed in a single SQL query by using structures supported in MT&#58;&#58;ObjectDriver, can&#39;t be created.  One such example filter that can&#39;t be created is the one to narrow down the results based on new metadata system (iow Custom Fields).


&#61;&#61;Related Links&#61;&#61;


&lt;a href&#61;&quot;http&#58;//cargames.com.au/&quot; target&#61;&quot;_blank&quot;&gt;&#39;&#39;&#39;car games&#39;&#39;&#39;&lt;/a&gt;

&lt;a href&#61;&quot;http&#58;//www.unbeatable.co.uk/pages/Electronics/TV&#45;and&#45;Video/Flat&#45;Panel&#45;Televisions/&quot; target&#61;&quot;_blank&quot;&gt;&#39;&#39;&#39;lcd tv&#39;&#39;&#39;&lt;/a&gt;
garbage]*/
Save the file.  Go to MT&amp;&#35;39&#59;s template listing screen and refresh the template so the changes above will be loaded to the system.


&amp;&#35;61&#59;&amp;&#35;61&#59; Add comment to the search type &amp;&#35;61&#59;&amp;&#35;61&#59;

&amp;quot&#59;config.yaml&amp;quot&#59; file can be written like below to add comment search.  Note the setting is to seaerch for entry_id in exact match.

 name&amp;&#35;58&#59; ajc
 version&amp;&#35;58&#59; 1.0
 id&amp;&#35;58&#59; ajc
 
 applications&amp;&#35;58&#59;
     new_search&amp;&#35;58&#59;
         default&amp;&#35;58&#59;
             types&amp;&#35;58&#59;
                 comment&amp;&#35;58&#59;
                     columns&amp;&#35;58&#59;
                         entry_id&amp;&#35;58&#59; 1
                     sort&amp;&#35;58&#59; created_on
                     terms&amp;&#35;58&#59;
                         visible&amp;&#35;58&#59; 1


&amp;&#35;61&#59;&amp;&#35;61&#59; Add rendering method to the new app &amp;&#35;61&#59;&amp;&#35;61&#59;

The example plugin adds new class which derives from MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search (just like FreeText search).  It only adds renderjsc method which is called during rendering the search results.  Note in the above JavaScript block, the url has &amp;quot&#59;format&amp;&#35;61&#59;jsc&amp;quot&#59; parameter in the end.  When MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search sees &amp;quot&#59;format&amp;quot&#59; parameter, it tries to call &amp;quot&#59;renderFORMAT&amp;quot&#59; method where FORMAT is replaced to the value of the parameter.  Thus, in this case, renderjsc method of the app is called.

renderjsc method calls superclass&amp;&#35;39&#59;s render method to render search results in HTML and put the result in the JSON result, that is later rendered by embedding all of the content to innerHTML of the element.


&amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59; CODE (lib/ajc.pm) &amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59;

 use strict&amp;&#35;59&#59;
 
 package ajc&amp;&#35;59&#59;
 
 use base qw( MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search )&amp;&#35;59&#59;
 
 sub renderjsc &amp;&#35;123&#59;
     my $app &amp;&#35;61&#59; shift&amp;&#35;59&#59;
     my $out &amp;&#35;61&#59; $app&amp;&#35;45&#59;&amp;gt&#59;SUPER&amp;&#35;58&#59;&amp;&#35;58&#59;render( @_ )&amp;&#35;59&#59;
     my $result&amp;&#35;59&#59;
     if (ref($out) &amp;amp&#59;&amp;amp&#59; ($out&amp;&#35;45&#59;&amp;gt&#59;isa(&amp;&#35;39&#59;MT&amp;&#35;58&#59;&amp;&#35;58&#59;Template&amp;&#35;39&#59;))) &amp;&#35;123&#59;
         defined( $result &amp;&#35;61&#59; $app&amp;&#35;45&#59;&amp;gt&#59;build_page($out) )
             or return $app&amp;&#35;45&#59;&amp;gt&#59;error($out&amp;&#35;45&#59;&amp;gt&#59;errstr)&amp;&#35;59&#59;
     &amp;&#35;125&#59;
     else &amp;&#35;123&#59;
         $result &amp;&#35;61&#59; $out&amp;&#35;59&#59;
     &amp;&#35;125&#59;
 
     return $app&amp;&#35;45&#59;&amp;gt&#59;json_result(&amp;&#35;123&#59; content &amp;&#35;61&#59;&amp;gt&#59; $result &amp;&#35;125&#59;)&amp;&#35;59&#59;
 &amp;&#35;125&#59;
 
 1&amp;&#35;59&#59;


&amp;&#35;61&#59;&amp;&#35;61&#59; Create mt&amp;&#35;45&#59;csearch.cgi &amp;&#35;61&#59;&amp;&#35;61&#59;

Because lib/ajc.pm is a new MT&amp;&#35;58&#59;&amp;&#35;58&#59;App application, you should create a new cgi file to instantiate it.  The content is simple, mostly copied from mt&amp;&#35;45&#59;search.cgi.  The difference is to add plugin&amp;&#35;39&#59;s lib directory in the search path.  Save the file to MT_DIR.


&amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59; CODE (mt&amp;&#35;45&#59;csearch.cgi) &amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59;

 &amp;&#35;35&#59;&amp;&#35;33&#59;/usr/bin/perl &amp;&#35;45&#59;w
 
 &amp;&#35;35&#59; Movable Type (r) Open Source (C) 2001&amp;&#35;45&#59;2008 Six Apart, Ltd.
 &amp;&#35;35&#59; This program is distributed under the terms of the
 &amp;&#35;35&#59; GNU General Public License, version 2.
 &amp;&#35;35&#59;
 &amp;&#35;35&#59; $Id&amp;&#35;58&#59; mt&amp;&#35;45&#59;csearch.cgi 1493 2008&amp;&#35;45&#59;03&amp;&#35;45&#59;07 11&amp;&#35;58&#59;32&amp;&#35;58&#59;52Z fumiakiy $
 
 use strict&amp;&#35;59&#59;
 use lib $ENV&amp;&#35;123&#59;MT_HOME&amp;&#35;125&#59; ? &amp;quot&#59;$ENV&amp;&#35;123&#59;MT_HOME&amp;&#35;125&#59;/lib&amp;quot&#59; &amp;&#35;58&#59; &amp;&#35;39&#59;lib&amp;&#35;39&#59;&amp;&#35;59&#59;
 use lib $ENV&amp;&#35;123&#59;MT_HOME&amp;&#35;125&#59; ? &amp;quot&#59;$ENV&amp;&#35;123&#59;MT_HOME&amp;&#35;125&#59;/plugins/ajc/lib&amp;quot&#59; &amp;&#35;58&#59; &amp;&#35;39&#59;plugins/ajc/lib&amp;&#35;39&#59;&amp;&#35;59&#59;
 use MT&amp;&#35;58&#59;&amp;&#35;58&#59;Bootstrap App &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;39&#59;ajc&amp;&#35;39&#59;&amp;&#35;59&#59;


&amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59; Add configuration &amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59;

Just like FreeText, you have to add the following configuration to mt&amp;&#35;45&#59;config.cgi.
  SearchScript mt&amp;&#35;45&#59;csearch.cgi


&amp;&#35;61&#59;&amp;&#35;61&#59; Add search template to render comments &amp;&#35;61&#59;&amp;&#35;61&#59;

In order to make comment listing look exactly like the default template, first copy a part of the template snippet from default_templates/comments.mtml and wrap it inside the MTSearchResults tag&amp;&#35;58&#59;

 &amp;amp&#59;lt&amp;&#35;59&#59;MTSearchResults&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;div id&amp;&#35;61&#59;&amp;quot&#59;comment&amp;&#35;45&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentID$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59; class&amp;&#35;61&#59;&amp;quot&#59;comment&amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;IfCommentParent&amp;amp&#59;gt&amp;&#35;59&#59; comment&amp;&#35;45&#59;reply&amp;amp&#59;lt&amp;&#35;59&#59;/mt&amp;&#35;58&#59;IfCommentParent&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
            &amp;amp&#59;lt&amp;&#35;59&#59;div class&amp;&#35;61&#59;&amp;quot&#59;inner&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                &amp;amp&#59;lt&amp;&#35;59&#59;div class&amp;&#35;61&#59;&amp;quot&#59;comment&amp;&#35;45&#59;header&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                    &amp;amp&#59;lt&amp;&#35;59&#59;div class&amp;&#35;61&#59;&amp;quot&#59;asset&amp;&#35;45&#59;meta&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                        &amp;amp&#59;lt&amp;&#35;59&#59;span class&amp;&#35;61&#59;&amp;quot&#59;byline&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                            &amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentAuthorIdentity$&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;IfCommentParent&amp;amp&#59;gt&amp;&#35;59&#59;
                            &amp;amp&#59;lt&amp;&#35;59&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;trans phrase&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;91&#59;_1&amp;&#35;93&#59; replied to &amp;amp&#59;lt&amp;&#35;59&#59;a href&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;91&#59;_2&amp;&#35;93&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;comment from &amp;&#35;91&#59;_3&amp;&#35;93&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/a&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59; params&amp;&#35;61&#59;&amp;quot&#59;&amp;amp&#59;lt&amp;&#35;59&#59;span class&amp;&#35;61&#59;&amp;quot&#59;vcard author&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentAuthorLink$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/span&amp;amp&#59;gt&amp;&#35;59&#59;%%&amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;CommentParent&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$mt&amp;&#35;58&#59;CommentLink$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/mt&amp;&#35;58&#59;CommentParent&amp;amp&#59;gt&amp;&#35;59&#59;%%&amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;CommentParent&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentAuthor$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/mt&amp;&#35;58&#59;CommentParent&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;else&amp;amp&#59;gt&amp;&#35;59&#59;
                            &amp;amp&#59;lt&amp;&#35;59&#59;span class&amp;&#35;61&#59;&amp;quot&#59;vcard author&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentAuthorLink$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/span&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;/mt&amp;&#35;58&#59;IfCommentParent&amp;amp&#59;gt&amp;&#35;59&#59;
                            &amp;&#35;124&#59; &amp;amp&#59;lt&amp;&#35;59&#59;a href&amp;&#35;61&#59;&amp;quot&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$mt&amp;&#35;58&#59;CommentLink$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;abbr class&amp;&#35;61&#59;&amp;quot&#59;published&amp;quot&#59; title&amp;&#35;61&#59;&amp;quot&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentDate format_name&amp;&#35;61&#59;&amp;quot&#59;iso8601&amp;quot&#59;$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentDate$&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/abbr&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/a&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;MTIfCommentsAccepted&amp;amp&#59;gt&amp;&#35;59&#59;
                            &amp;&#35;124&#59; &amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentReplyLink$&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;/MTIfCommentsAccepted&amp;amp&#59;gt&amp;&#35;59&#59;
                        &amp;amp&#59;lt&amp;&#35;59&#59;/span&amp;amp&#59;gt&amp;&#35;59&#59;
                    &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;
                &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;
                &amp;amp&#59;lt&amp;&#35;59&#59;div class&amp;&#35;61&#59;&amp;quot&#59;comment&amp;&#35;45&#59;content&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                    &amp;amp&#59;lt&amp;&#35;59&#59;$MTCommentBody$&amp;amp&#59;gt&amp;&#35;59&#59;
                &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;
            &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;
        &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;
 &amp;amp&#59;lt&amp;&#35;59&#59;/MTSearchResults&amp;amp&#59;gt&amp;&#35;59&#59;

And add the following snippet below the closing MTSearchResults tag to add pager block.

 &amp;amp&#59;lt&amp;&#35;59&#59;MTCurrentPage setvar&amp;&#35;61&#59;&amp;quot&#59;current_page&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                                &amp;amp&#59;lt&amp;&#35;59&#59;div class&amp;&#35;61&#59;&amp;quot&#59;content&amp;&#35;45&#59;nav&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;
                                    &amp;amp&#59;lt&amp;&#35;59&#59;MTIfPreviousResults&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;a href&amp;&#35;61&#59;&amp;quot&#59;javascript&amp;&#35;58&#59;void(0)&amp;quot&#59; rel&amp;&#35;61&#59;&amp;quot&#59;prev&amp;quot&#59; onclick&amp;&#35;61&#59;&amp;quot&#59;getComments(&amp;amp&#59;lt&amp;&#35;59&#59;MTGetVar name&amp;&#35;61&#59;&amp;quot&#59;current_page&amp;quot&#59; op&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;45&#59;&amp;quot&#59; value&amp;&#35;61&#59;&amp;quot&#59;1&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;)&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59; &amp;amp&#59;lt&amp;&#35;59&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;trans phrase&amp;&#35;61&#59;&amp;quot&#59;Previous&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/a&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;nbsp&amp;&#35;59&#59;&amp;amp&#59;nbsp&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/MTIfPreviousResults&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTPagerBlock&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTIfCurrentPage&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTVar name&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;value&amp;&#35;95&#59;&amp;&#35;95&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTElse&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;a href&amp;&#35;61&#59;&amp;quot&#59;javascript&amp;&#35;58&#59;void(0)&amp;quot&#59; onclick&amp;&#35;61&#59;&amp;quot&#59;getComments(&amp;amp&#59;lt&amp;&#35;59&#59;MTVar name&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;value&amp;&#35;95&#59;&amp;&#35;95&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;)&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTVar name&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;value&amp;&#35;95&#59;&amp;&#35;95&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/a&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/MTIfCurrentPage&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;mt&amp;&#35;58&#59;unless name&amp;&#35;61&#59;&amp;quot&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;last&amp;&#35;95&#59;&amp;&#35;95&#59;&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;nbsp&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/mt&amp;&#35;58&#59;unless&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/MTPagerBlock&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;MTIfMoreResults&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;nbsp&amp;&#35;59&#59;&amp;amp&#59;nbsp&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;a href&amp;&#35;61&#59;&amp;quot&#59;javascript&amp;&#35;58&#59;void(0)&amp;quot&#59; rel&amp;&#35;61&#59;&amp;quot&#59;next&amp;quot&#59; onclick&amp;&#35;61&#59;&amp;quot&#59;getComments(&amp;amp&#59;lt&amp;&#35;59&#59;MTGetVar name&amp;&#35;61&#59;&amp;quot&#59;current_page&amp;quot&#59; op&amp;&#35;61&#59;&amp;quot&#59;+&amp;quot&#59; value&amp;&#35;61&#59;&amp;quot&#59;1&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;)&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;&amp;&#35;95&#59;&amp;&#35;95&#59;trans phrase&amp;&#35;61&#59;&amp;quot&#59;Next&amp;quot&#59;&amp;amp&#59;gt&amp;&#35;59&#59; &amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/a&amp;amp&#59;gt&amp;&#35;59&#59;&amp;amp&#59;lt&amp;&#35;59&#59;/MTIfMoreResults&amp;amp&#59;gt&amp;&#35;59&#59;
                                &amp;amp&#59;lt&amp;&#35;59&#59;/div&amp;amp&#59;gt&amp;&#35;59&#59;


Save the file in search_templates with the name &amp;quot&#59;comment_listing.tmpl&amp;quot&#59;.


&amp;&#35;61&#59;&amp;&#35;61&#59; Republish entry archive template &amp;&#35;61&#59;&amp;&#35;61&#59;

Republish entry archive template and go to an entry.  Voila&amp;&#35;33&#59; comments are paginated.


&amp;&#35;61&#59; Add new filter to search syntax &amp;&#35;61&#59;


&amp;&#35;61&#59;&amp;&#35;61&#59; Description &amp;&#35;61&#59;&amp;&#35;61&#59;

By default, MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search supports category and author filter.  You can for example query like &amp;&#35;91&#59;&amp;quot&#59;Movable&amp;&#35;93&#59; to narrow search results based on the category of the entries.

You as a plugin developer can add the filter by registering your own filtering condition to the registry.


&amp;&#35;61&#59;&amp;&#35;61&#59; Example Plugin &amp;&#35;45&#59; add commenter filter &amp;&#35;61&#59;&amp;&#35;61&#59;

The plugin described below adds &amp;quot&#59;comment_by&amp;quot&#59; filter to mt&amp;&#35;45&#59;search, so you can search entries and narrow results by who commented to the entries.

After this plugin is installed, you can search for entries by the following syntax.

 &amp;quot&#59;Movable Type&amp;quot&#59; comment_by&amp;&#35;58&#59;Melody


&amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59; CODE ( commenter_filter.pl ) &amp;&#35;61&#59;&amp;&#35;61&#59;&amp;&#35;61&#59;

 package MT&amp;&#35;58&#59;&amp;&#35;58&#59;Plugin&amp;&#35;58&#59;&amp;&#35;58&#59;CommenterFilter&amp;&#35;59&#59;
 
 use strict&amp;&#35;59&#59;
 use base &amp;&#35;39&#59;MT&amp;&#35;58&#59;&amp;&#35;58&#59;Plugin&amp;&#35;39&#59;&amp;&#35;59&#59;
 our $VERSION &amp;&#35;61&#59; &amp;&#35;39&#59;1.0&amp;&#35;39&#59;&amp;&#35;59&#59;
 
 my $plugin&amp;&#35;59&#59;
 $plugin &amp;&#35;61&#59; MT&amp;&#35;58&#59;&amp;&#35;58&#59;Plugin&amp;&#35;58&#59;&amp;&#35;58&#59;CommenterFilter&amp;&#35;45&#59;&amp;gt&#59;new(&amp;&#35;123&#59;
     id &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;39&#59;CommenterFilter&amp;&#35;39&#59;,
     name &amp;&#35;61&#59;&amp;gt&#59; &amp;quot&#59;CommenterFilter&amp;quot&#59;,
     version &amp;&#35;61&#59;&amp;gt&#59; $VERSION,
     registry &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
         applications &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
             new_search &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
                 default &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
                     types &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
                         entry &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
                             filter_types &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;123&#59;
                                 comment_by &amp;&#35;61&#59;&amp;gt&#59; \&amp;amp&#59;_join_commenter
                             &amp;&#35;125&#59;
                         &amp;&#35;125&#59;
                     &amp;&#35;125&#59;
                 &amp;&#35;125&#59;
             &amp;&#35;125&#59;
         &amp;&#35;125&#59;
     &amp;&#35;125&#59;
 &amp;&#35;125&#59;)&amp;&#35;59&#59;
 MT&amp;&#35;45&#59;&amp;gt&#59;add_plugin($plugin)&amp;&#35;59&#59;
 
 sub _join_commenter &amp;&#35;123&#59;
     my ( $app, $term ) &amp;&#35;61&#59; @_&amp;&#35;59&#59;
 
     my $query &amp;&#35;61&#59; $term&amp;&#35;45&#59;&amp;gt&#59;&amp;&#35;123&#59;term&amp;&#35;125&#59;&amp;&#35;59&#59;
     if ( &amp;&#35;39&#59;PHRASE&amp;&#35;39&#59; eq $term&amp;&#35;45&#59;&amp;gt&#59;&amp;&#35;123&#59;query&amp;&#35;125&#59; ) &amp;&#35;123&#59;
         $query &amp;&#35;61&#59;~ s/&amp;&#35;39&#59;/&amp;quot&#59;/g&amp;&#35;59&#59;
     &amp;&#35;125&#59;
 
     my $lucene_struct &amp;&#35;61&#59; Lucene&amp;&#35;58&#59;&amp;&#35;58&#59;QueryParser&amp;&#35;58&#59;&amp;&#35;58&#59;parse_query( $query )&amp;&#35;59&#59;
     if ( &amp;&#35;39&#59;PROHIBITED&amp;&#35;39&#59; eq $term&amp;&#35;45&#59;&amp;gt&#59;&amp;&#35;123&#59;type&amp;&#35;125&#59; ) &amp;&#35;123&#59;
         $_&amp;&#35;45&#59;&amp;gt&#59;&amp;&#35;123&#59;type&amp;&#35;125&#59; &amp;&#35;61&#59; &amp;&#35;39&#59;PROHIBITED&amp;&#35;39&#59; foreach @$lucene_struct&amp;&#35;59&#59;
     &amp;&#35;125&#59;
 
     my ( $terms ) &amp;&#35;61&#59; $app&amp;&#35;45&#59;&amp;gt&#59;_query_parse_core( $lucene_struct, &amp;&#35;123&#59; nickname &amp;&#35;61&#59;&amp;gt&#59; &amp;&#35;39&#59;like&amp;&#35;39&#59; &amp;&#35;125&#59;, &amp;&#35;123&#59;&amp;&#35;125&#59; )&amp;&#35;59&#59;
     return unless $terms &amp;amp&#59;&amp;amp&#59; @$terms&amp;&#35;59&#59;
     push @$terms, &amp;&#35;39&#59;&amp;&#35;45&#59;and&amp;&#35;39&#59;, &amp;&#35;123&#59;
         id &amp;&#35;61&#59;&amp;gt&#59; \&amp;&#35;39&#59;&amp;&#35;61&#59; comment_commenter_id&amp;&#35;39&#59;,
     &amp;&#35;125&#59;&amp;&#35;59&#59;
 
     require MT&amp;&#35;58&#59;&amp;&#35;58&#59;Comment&amp;&#35;59&#59;
     require MT&amp;&#35;58&#59;&amp;&#35;58&#59;Author&amp;&#35;59&#59;
     return MT&amp;&#35;58&#59;&amp;&#35;58&#59;Comment&amp;&#35;45&#59;&amp;gt&#59;join_on( undef,
         &amp;&#35;123&#59; entry_id &amp;&#35;61&#59;&amp;gt&#59; \&amp;&#35;39&#59;&amp;&#35;61&#59; entry_id&amp;&#35;39&#59;, blog_id &amp;&#35;61&#59;&amp;gt&#59; \&amp;&#35;39&#59;&amp;&#35;61&#59; entry_blog_id&amp;&#35;39&#59;, visible &amp;&#35;61&#59;&amp;gt&#59; 1 &amp;&#35;125&#59;,
         &amp;&#35;123&#59; join &amp;&#35;61&#59;&amp;gt&#59; MT&amp;&#35;58&#59;&amp;&#35;58&#59;Author&amp;&#35;45&#59;&amp;gt&#59;join_on( undef, $terms, &amp;&#35;123&#59;&amp;&#35;125&#59; ),
           unique &amp;&#35;61&#59;&amp;gt&#59; 1 &amp;&#35;125&#59;
     )&amp;&#35;59&#59;
 &amp;&#35;125&#59;
 
 1&amp;&#35;59&#59;

The plugin first registers itself to the registry, so the method can be called when MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search processes &amp;quot&#59;comment_by&amp;quot&#59; filter.  When comment_by filter is encountered, MT&amp;&#35;58&#59;&amp;&#35;58&#59;App&amp;&#35;58&#59;&amp;&#35;58&#59;Search calls _join_commenter method.  The method must return either undef or arguments for $args, which will later be used as the second argument to load or load_iter method.

_join_commenter method constructs a nested join clause, so, mt_entry joins to mt_comment joins to mt_author and search for author_nickname to match the specified keyword.  The resulting SQL query will be like this&amp;&#35;58&#59;

 SELECT ... FROM mt_entry, mt_comment, mt_author
 WHERE (...)
 AND comment_entry_id &amp;&#35;61&#59; entry_id
 AND comment_blog_id &amp;&#35;61&#59; entry_blog_id
 AND comment_visible &amp;&#35;61&#59; 1
 AND author_nickname LIKE &amp;&#35;39&#59;%Melody%&amp;&#35;39&#59;
 AND author_id &amp;&#35;61&#59; comment_commenter_id


&amp;&#35;61&#59;&amp;&#35;61&#59; NOTE &amp;&#35;61&#59;&amp;&#35;61&#59;

Note this mechanism currently only allows to manipulate join clause.  Therefore, filters that can&amp;&#35;39&#59;t be experessed in a single SQL query by using structures supported in MT&amp;&#35;58&#59;&amp;&#35;58&#59;ObjectDriver, can&amp;&#35;39&#59;t be created.  One such example filter that can&amp;&#35;39&#59;t be created is the one to narrow down the results based on new metadata system (iow Custom Fields).


&amp;&#35;61&#59;&amp;&#35;61&#59;Related Links&amp;&#35;61&#59;&amp;&#35;61&#59;


&amp;lt&#59;a href&amp;&#35;61&#59;&amp;quot&#59;http&amp;&#35;58&#59;//cargames.com.au/&amp;quot&#59; target&amp;&#35;61&#59;&amp;quot&#59;_blank&amp;quot&#59;&amp;gt&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;car games&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;lt&#59;/a&amp;gt&#59;

&amp;lt&#59;a href&amp;&#35;61&#59;&amp;quot&#59;http&amp;&#35;58&#59;//www.unbeatable.co.uk/pages/Electronics/TV&amp;&#35;45&#59;and&amp;&#35;45&#59;Video/Flat&amp;&#35;45&#59;Panel&amp;&#35;45&#59;Televisions/&amp;quot&#59; target&amp;&#35;61&#59;&amp;quot&#59;_blank&amp;quot&#59;&amp;gt&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;lcd tv&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;&#35;39&#59;&amp;lt&#59;/a&amp;gt&#59;
Clone this wiki locally