Index: /branches/release-33/lib/MT/Template/Context/Search.pm
===================================================================
--- /branches/release-33/lib/MT/Template/Context/Search.pm (revision 1640)
+++ /branches/release-33/lib/MT/Template/Context/Search.pm (revision 1708)
@@ -9,5 +9,5 @@
 use strict;
 use base qw( MT::Template::Context );
-use MT::Util qw( encode_url );
+use MT::Util qw( encode_url decode_html );
 
 sub load_core_tags {
@@ -134,8 +134,8 @@
 	my ( $ctx, $args, $cond ) = @_;
 
-    my $search_string = encode_url($ctx->stash('search_string'));
+    my $search_string = decode_html( $ctx->stash('search_string') ) ;
     my $cgipath = $ctx->_hdlr_cgi_path($args);
     my $script = $ctx->{config}->SearchScript;
-    my $link = $cgipath.$script . '?search=' . $search_string;
+    my $link = $cgipath.$script . '?search=' . encode_url( $search_string );
     if ( my $mode = $ctx->stash('mode') ) {
         $mode = encode_url($mode);
Index: /branches/release-33/lib/MT/App/Search.pm
===================================================================
--- /branches/release-33/lib/MT/App/Search.pm (revision 1647)
+++ /branches/release-33/lib/MT/App/Search.pm (revision 1708)
@@ -50,5 +50,6 @@
     my $app = shift;
     return {
-        params => [ qw( searchTerms search count limit startIndex offset ) ],
+        params => [ qw( searchTerms search count limit startIndex offset
+            category author )],
         types  => {
             #author => {
@@ -58,7 +59,16 @@
             #},
             entry => {
-                columns => [ qw( title keywords text text_more ) ],
+                columns => {
+                    title     => 'like',
+                    keywords  => 'like',
+                    text      => 'like',
+                    text_more => 'like'
+                },
                 'sort'  => 'authored_on',
                 terms   => { status => 2 }, #MT::Entry::RELEASE()
+                filter_types => {
+                    author   => \&_join_author,
+                    category => \&_join_category,
+                },
             },
         },
@@ -281,4 +291,5 @@
 
     $count = $class->count( $terms, $args );
+    return $app->error($class->errstr) unless defined $count;
 
     my $cache_driver = $app->{cache_driver};
@@ -296,5 +307,5 @@
 
     my $count = $app->count( $class, $terms, $args );
-    return $app->error($class->errstr) unless defined $count;
+    return $app->errtrans("Invalid query.  [_1]", $app->errstr) unless defined $count;
 
     my $iter = $class->load_iter( $terms, $args )
@@ -334,8 +345,9 @@
 
     my $columns = $params->{columns};
+    delete $columns->{'plugin'}; #FIXME: why is this in here?
     return $app->errtrans('No column was specified to search for [_1].', $app->{searchparam}{Type})
-        unless $columns && @$columns;
-
-    my $parsed = $app->query_parse( $columns );
+        unless $columns && %$columns;
+
+    my $parsed = $app->query_parse( %$columns );
     return $app->errtrans('Parse error: [1]', $app->errstr)
         unless $parsed && %$parsed;
@@ -474,5 +486,7 @@
         # load specified template
         my $filename;
-        if (my @tmpls = ($app->config->default('SearchAltTemplate'), $app->config->SearchAltTemplate)) {
+        if (my @tmpls = (
+          $app->config->default('SearchAltTemplate'),
+          $app->config->SearchAltTemplate) ) {
             for my $tmpl (@tmpls) {
                 next unless defined $tmpl;
@@ -550,33 +564,112 @@
 sub query_parse {
     my $app = shift;
-    my ( $columns ) = @_;
+    my ( %columns ) = @_;
+
+    my $search = $app->{search_string};
+
+    my $reg = $app->registry( $app->mode, 'types', $app->{searchparam}{Type} );
+    my $filter_types = $reg->{ 'filter_types' };
+    foreach my $type ( keys %$filter_types ) {
+        if ( my $filter = $app->param($type) ) {
+            $search .= " $type:$filter";
+        }
+    }
 
     require Lucene::QueryParser;
-    my $lucene_struct = Lucene::QueryParser::parse_query( $app->{search_string} );
-    my %columns = map { $_ => 1 } @$columns;
-    my $structure = $app->_query_parse_core( $lucene_struct, \%columns );
-    { terms => $structure };
+    my $lucene_struct = Lucene::QueryParser::parse_query( $search );
+    my ( $terms, $joins ) = $app->_query_parse_core( $lucene_struct, \%columns, $filter_types );
+    my $return = {
+        $terms && @$terms ? (terms => $terms) : ()
+    };
+    if ( $joins && @$joins ) {
+        my $args = {};
+        _create_join_arg( $args, $joins );
+        if ( $args && %$args ) {
+            $return->{args} = $args;
+        }
+    }
+    $return;
+}
+
+sub _create_join_arg {
+    my ( $args, $joins ) = @_;
+    my $join = shift @$joins;
+    return unless $join && @$join;
+    my $next = $join->[3];
+    if ( defined $next ) {
+        if ( exists $next->{'join'} ) {
+            $next = $next->{'join'}->[3];
+        }
+    }
+    else {
+        $next = {};
+        $join->[3] = $next;
+    }
+    _create_join_arg($next, $joins);
+    $args->{'join'} = $join;
 }
 
 sub _query_parse_core {
     my $app = shift;
-    my ( $lucene_struct, $columns ) = @_;
-
-    my @structure;
+    my ( $lucene_struct, $columns, $filter_types ) = @_;
+
+    my $rvalue = sub {
+        my %rvalues = (
+            NORMALlike => { like => '%' . $_[1] . '%' },
+            NORMAL1    => $_[1],
+            PROHIBITEDlike => { not_like => '%' . $_[1] . '%' },
+            PROHIBITED1    => { not => $_[1] }
+        );
+        $rvalues{$_[0]};
+    };
+
+    my ( @structure, @joins );
     for my $term ( @$lucene_struct ) {
-        next if exists( $term->{field} )
-            && !exists( $columns->{ $term->{field} } );
-
-        my $test;
+        if ( exists $term->{field} ) {
+            unless ( exists $columns->{ $term->{field} } ) {
+                next if $filter_types && %$filter_types
+                    && !exists( $filter_types->{ $term->{field} } );
+            }
+        }
+
+        my @tmp;
         if ( ( 'TERM' eq $term->{query} ) || ( 'PHRASE' eq $term->{query} ) ){
-            if ( 'PROHIBITED' eq $term->{type} ) {
-                $test = { not_like => '%'.$term->{term}.'%' };
+            my $test;
+            if ( exists( $term->{field} ) ) {
+                if ( $filter_types && %$filter_types
+                  && exists( $filter_types->{ $term->{field} } ) ) {
+                    my $code = $app->handler_to_coderef($filter_types->{ $term->{field} });
+                    if ( $code ) {
+                        my $join_args = $code->( $app, $term );
+                        push @joins, $join_args;
+                        next;
+                    }
+                }
+                elsif ( exists $columns->{ $term->{field} } ) {
+                    my $test = $rvalue->(
+                        ( $term->{type} || '' ) . $columns->{ $term->{field} },
+                        $term->{term}
+                    );
+                    push @tmp, { $term->{field} => $test };
+                }
             }
             else {
-                $test = { like => '%'.$term->{term}.'%' };
+                my @cols = keys %$columns;
+                my $number = scalar @cols;
+                for ( my $i = 0; $i < $number; $i++ ) {
+                    my $test = $rvalue->(
+                        ( $term->{type} || '' ) . $columns->{ $cols[$i] },
+                        $term->{term}
+                    );
+                    push @tmp, { $cols[$i] => $test };
+                    unless ( $i == $number - 1 ) {
+                        push @tmp, '-or';
+                    }
+                }
             }
         }
         elsif ( 'SUBQUERY' eq $term->{query} ) {
-            $test = $app->_query_parse_core( $term->{subquery}, $columns );
+            my ( $test, $more_joins ) = $app->_query_parse_core(
+                $term->{subquery}, $columns, $filter_types );
             next unless $test && @$test;
             if ( @structure ) {
@@ -586,21 +679,8 @@
             }
             push @structure, $test->[0];
+            push @joins, @$more_joins;
             next;
         }
 
-        my @tmp;
-        if ( exists( $term->{field} ) ) {
-            push @tmp, { $term->{field} => $test };
-        }
-        else {
-            my @columns = keys %$columns;
-            my $number = scalar @columns;
-            for ( my $i = 0; $i < $number; $i++) {
-                push @tmp, { $columns[$i] => $test };
-                unless ( $i == $number - 1 ) {
-                    push @tmp, '-or';
-                }
-            }
-        }
         if ( exists($term->{conj}) && ( 'OR' eq $term->{conj} ) ) {
             if ( my $prev = pop @structure ) {
@@ -615,5 +695,60 @@
         }
     }
-    \@structure;
+    ( \@structure, \@joins );
+}
+
+# add category filter to entry search
+sub _join_category {
+    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;
+    }
+    # search for exact match
+    my ( $terms ) = $app->_query_parse_core( $lucene_struct, { label => 1 }, {} );
+    return unless $terms && @$terms;
+    push @$terms, '-and', {
+        id => \'= placement_category_id',
+        blog_id => \'= entry_blog_id',
+    };
+
+    require MT::Placement;
+    require MT::Category;
+    return MT::Placement->join_on( undef,
+        { entry_id => \'= entry_id', blog_id => \'= entry_blog_id' },
+        { join => MT::Category->join_on( undef, $terms, {} ),
+          unique => 1 }
+    );
+}
+
+# add author filter to entry search
+sub _join_author {
+    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 => \'= entry_author_id',
+    };
+    require MT::Author;
+    return MT::Author->join_on( undef,
+        $terms,
+        { unique => 1 }
+    );
 }
 
