Index: /branches/release-39/default_templates/search_results.mtml
===================================================================
--- /branches/release-39/default_templates/search_results.mtml (revision 2310)
+++ /branches/release-39/default_templates/search_results.mtml (revision 2463)
@@ -3,4 +3,9 @@
 <html xmlns="http://www.w3.org/1999/xhtml" id="sixapart-standard">
 <head>
+    <script type="text/javascript">
+    /* <![CDATA[ */
+    var user = <$MTUserSessionState$>;
+    /* ]]> */
+    </script>
     <$mt:include module="<__trans phrase="HTML Head">"$>
     <title><$MTBlogName encode_html="1"$>: <__trans phrase="Search Results"></title>
Index: /branches/release-39/lib/MT/ObjectDriver/Driver/DBI.pm
===================================================================
--- /branches/release-39/lib/MT/ObjectDriver/Driver/DBI.pm (revision 2459)
+++ /branches/release-39/lib/MT/ObjectDriver/Driver/DBI.pm (revision 2463)
@@ -160,14 +160,10 @@
     $stmt->group([ map { { column => $_ } } @group ]);
 
-    ## Ugly.
-    my $sql = $stmt->as_sql;
-
     ## Set statement's ORDER clause if any.
     if ($order) {
         if (! ref($order)) {
-            $sql .= "\nORDER BY " . $decorate->($order);
-            if ($direction) {
-                $sql .= $direction eq 'descend' ? ' DESC' : ' ASC';
-            }
+            $stmt->order( [ { column => $decorate->($order),
+                desc => $direction eq 'descend' ? 'DESC' : 'ASC'
+            } ] );
         } else {
             my @order;
@@ -179,7 +175,8 @@
             }
             $stmt->order(\@order);
-            $sql .= "\n" . $stmt->as_aggregate('order');
-        }
-    }
+        }
+    }
+
+    my $sql = $stmt->as_sql;
 
     my $dbh = $driver->r_handle;
Index: /branches/release-39/lib/MT/App.pm
===================================================================
--- /branches/release-39/lib/MT/App.pm (revision 2425)
+++ /branches/release-39/lib/MT/App.pm (revision 2463)
@@ -955,4 +955,63 @@
 }
 
+sub session_state {
+    my $app = shift;
+    my $blog = $app->blog;
+    my $blog_id = $blog->id if $blog;
+
+    my $c;
+    if ( $blog_id && $blog ) {
+        my ( $sessobj, $commenter ) = $app->get_commenter_session();
+        if ( $sessobj && $commenter ) {
+            my $blog_perms = $commenter->blog_perm($blog_id);
+            my $banned = $commenter->is_banned($blog_id) ? "1" : "0";
+            $banned = 0 if $blog_perms && $blog_perms->can_administer;
+            $banned ||= 1 if $commenter->status == MT::Author::BANNED();
+
+            if ($banned) {
+                $sessobj->remove;
+            } else {
+                $sessobj->start( time +
+                    $app->config->CommentSessionTimeout); # extend by timeout
+                $sessobj->save();
+            }
+
+            # FIXME: These may not be accurate in 'SingleCommunity' mode...
+            my $can_comment = $banned ? 0 : 1;
+            $can_comment = 0 unless $blog->allow_unreg_comments || $blog->allow_reg_comments;
+            my $can_post = ($blog_perms && $blog_perms->can_create_post) ? "1" : "0";
+            $c = {
+                sid => $sessobj->id,
+                name => $commenter->nickname,
+                url => $commenter->url,
+                email => $commenter->email,
+                userpic => scalar $commenter->userpic_url,
+                profile => "", # profile link url
+                is_authenticated => "1",
+                is_trusted => ($commenter->is_trusted($blog_id) ? "1" : "0"),
+                is_author => ($commenter->type == MT::Author::AUTHOR() ? "1" : "0"),
+                is_anonymous => "0",
+                is_banned => $banned,
+                can_comment => $can_comment,
+                can_post => $can_post,
+            };
+        }
+    }
+
+    unless ($c) {
+        my $can_comment = $blog && $blog->allow_anon_comments ? "1" : "0";
+        $c = {
+            is_authenticated => "0",
+            is_trusted => "0",
+            is_anonymous => "1",
+            can_post => "0", # no anonymous posts
+            can_comment => $can_comment,
+            is_banned => "0",
+        };
+    }
+
+    return $c;
+}
+
 sub session {
     my $app = shift;
@@ -1014,4 +1073,52 @@
         return undef;
     }
+}
+
+sub get_commenter_session {
+    my $app = shift;
+    my $q   = $app->param;
+
+    my $session_key;
+
+    my $blog = $app->blog;
+    if ($blog) {
+        my $auths = $blog->commenter_authenticators || '';
+        if ( $auths =~ /MovableType/ ) {
+            # First, check for a real MT user login. If one exists,
+            # return that as the commenter identity
+            my ($user, $first_time) = $app->login();
+            if ( $user ) {
+                my $sess = $app->session;
+                return ( $sess, $user );
+            }
+        }
+    }
+
+    my %cookies = $app->cookies();
+    my $cookie_name = $app->commenter_cookie;
+    if ( !$cookies{$cookie_name} ) {
+        return ( undef, undef );
+    }
+    $session_key = $cookies{$cookie_name}->value() || "";
+    $session_key =~ y/+/ /;
+    my $cfg = $app->config;
+    require MT::Session;
+    my $sess_obj = MT::Session->load( { id => $session_key, kind => 'SI' } );
+    my $timeout = $cfg->CommentSessionTimeout;
+    my $user_id = $sess_obj->get('author_id') if $sess_obj;
+    my $user = MT::Author->load( $user_id ) if $user_id;
+
+    if (   !$sess_obj
+        || ( $sess_obj->start() + $timeout < time )
+        || ( !$user_id )
+        || ( !$user )
+      )
+    {
+        $app->_invalidate_commenter_session( \%cookies );
+        return ( undef, undef );
+    }
+
+    # session is valid!
+    return ( $sess_obj, $user );
 }
 
Index: /branches/release-39/lib/MT/Template/ContextHandlers.pm
===================================================================
--- /branches/release-39/lib/MT/Template/ContextHandlers.pm (revision 2448)
+++ /branches/release-39/lib/MT/Template/ContextHandlers.pm (revision 2463)
@@ -512,5 +512,5 @@
             SearchTemplateID => sub { 0 },
 
-            UserSessionState => sub { 'null' },
+            UserSessionState => \&_hdlr_user_session_state,
 
             BuildTemplateID => \&_hdlr_build_template_id,
@@ -1573,4 +1573,6 @@
 =back
 
+=for tags application
+
 =cut
 
@@ -1697,4 +1699,6 @@
 which will encode these to HTML entities.
 
+=for tags application
+
 =cut
 
@@ -1753,4 +1757,6 @@
 
 =back
+
+=for tags application
 
 =cut
@@ -1830,4 +1836,6 @@
         (contents of widget go here)
     </mtapp:Widget>
+
+=for tags application
 
 =cut
@@ -1935,4 +1943,6 @@
 
 =back
+
+=for tags application
 
 =cut
@@ -2010,4 +2020,6 @@
 
     <$mtapp:PageActions$>
+
+=for tags application
 
 =cut
@@ -2109,4 +2121,6 @@
         <input type="submit" />
     </form>
+
+=for tags application
 
 =cut
@@ -2184,4 +2198,6 @@
     </MTApp:SettingGroup>
 
+=for tags application
+
 =cut
 
@@ -2298,4 +2314,6 @@
         </div>
     </div>
+
+=for tags application
 
 =cut
@@ -2459,5 +2477,5 @@
     2,4,6,8,10
 
-=for tags loop
+=for tags loop, templating
 
 =cut
@@ -2519,4 +2537,6 @@
     </mt:If>
 
+=for tags templating
+
 =cut
 
@@ -2547,4 +2567,6 @@
 
 An alias for the 'Else' tag.
+
+=for tags templating
 
 =cut
@@ -2771,4 +2793,6 @@
         '<$mt:Var name="some_variable"$>' is 11 characters or longer
     </mt:If>
+
+=for tags templating
 
 =cut
@@ -3016,5 +3040,5 @@
 =back
 
-=for tags loop
+=for tags loop, templating
 
 =cut
@@ -3120,5 +3144,5 @@
 =head2 Var
 
-Retrieves a template variable and outputs it's value.
+A B<function tag> used to store and later output data in a template.
 
 B<Attributes:>
@@ -3126,5 +3150,5 @@
 =over 4
 
-=item var or name
+=item name (or var)
 
 Identifies the template variable. The 'name' attribute supports a variety
@@ -3223,4 +3247,6 @@
 
 =back
+
+=for tags templating
 
 =cut
@@ -3418,4 +3444,6 @@
 =back
 
+=for tags tags, entries
+
 =cut
 
@@ -3595,5 +3623,5 @@
     </ul>
 
-=for tags multiblog, loop
+=for tags tags, multiblog, loop
 
 =cut
@@ -3734,5 +3762,5 @@
 this into a request to mt-search.cgi.
 
-=for tags multiblog
+=for tags tags, multiblog
 
 =cut
@@ -3808,4 +3836,6 @@
 
 =for tags multiblog
+
+=for tags tags
 
 =cut
@@ -3930,4 +3960,6 @@
     </mt:If>
 
+=for tags tags, entries
+
 =cut
 
@@ -3986,4 +4018,6 @@
 =back
 
+=for tags tags
+
 =cut
 
@@ -4007,4 +4041,6 @@
 Outputs the numeric ID of the tag currently in context.
 
+=for tags tags
+
 =cut
 
@@ -4023,5 +4059,5 @@
 in context.
 
-=for tags count
+=for tags tags, count
 
 =cut
@@ -4056,4 +4092,6 @@
 configured with a TypeKey token.
 
+=for tags comments, typekey
+
 =cut
 
@@ -4070,4 +4108,6 @@
 A conditional tag that is true when the blog is configured to moderate
 incoming comments from anonymous commenters.
+
+=for tags comments
 
 =cut
@@ -4090,4 +4130,6 @@
 permit user registration.
 
+=for tags comments
+
 =cut
 
@@ -4108,4 +4150,6 @@
 A conditional tag that is true when the blog has been configured to
 require user registration.
+
+=for tags comments
 
 =cut
@@ -4129,4 +4173,6 @@
 permit anonymous comments.
 
+=for tags comments
+
 =cut
 
@@ -4154,4 +4200,6 @@
 allow comments and the blog is configured to accept comments in some
 fashion.
+
+=for tags comments
 
 =cut
@@ -4205,4 +4253,6 @@
     </mt:IfArchiveType>
 
+=for tags archives
+
 =cut
 
@@ -4242,4 +4292,6 @@
         <!-- do something else -->
     </mt:IfArchiveTypeEnabled>
+
+=for tags archives
 
 =cut
@@ -4308,4 +4360,6 @@
 
 =back
+
+=for tags templating
 
 =cut
@@ -4431,4 +4485,6 @@
 
     <h2><$mt:Var name="title"$></h2>
+
+=for tags templating
 
 =cut
@@ -4866,4 +4922,6 @@
 
     <$mt:FileTemplate format="%y/%m/%f"$>
+
+=for tags archives
 
 =cut
@@ -4947,4 +5005,6 @@
 =for tags date
 
+=for tags templates
+
 =cut
 
@@ -4995,4 +5055,5 @@
     <a href="<mt:Link identifier="main_index">">Home</a>
 
+=for tags archives
 =cut
 
@@ -5038,4 +5099,6 @@
     <mt:Version />
 
+=for tags configuration
+
 =cut
 
@@ -5068,4 +5131,6 @@
 
     Movable Type Open Source
+
+=for tags configuration
 
 =cut
@@ -5092,4 +5157,6 @@
     <$mt:PublishCharset$>
 
+=for tags configuration
+
 =cut
 
@@ -5112,4 +5179,6 @@
 other installed language.
 
+=for tags configuration
+
 =cut
 
@@ -5124,4 +5193,6 @@
 
 The value of the C<SignOnURL> configuration setting.
+
+=for tags comments
 
 =cut
@@ -5239,4 +5310,6 @@
 message. Used in system templates, such as the 'Comment Response' template.
 
+=for tags templating
+
 =cut
 
@@ -5266,4 +5339,6 @@
 
     <ul><li><$mt:Var name="color"$></li></ul>
+
+=for tags templating
 
 =cut
@@ -5306,4 +5381,6 @@
     foo is assigned: <$mt:Var name="my_hash{foo}"$>
 
+=for tags templating
+
 =cut
 
@@ -5373,4 +5450,6 @@
 =back
 
+=for tags templating
+
 =cut
 
@@ -5407,4 +5486,6 @@
 
 =back
+
+=for tags templating
 
 =cut
@@ -5439,4 +5520,6 @@
         <$mt:Var name="entry_title"$>
     </mt:Entries>
+
+=for tags templating
 
 =cut
@@ -5581,4 +5664,6 @@
     <a href="<$mt:CGIPath$>some-cgi-script.cgi">
 
+=for tags configuration
+
 =cut
 
@@ -5611,5 +5696,5 @@
     <$mt:AdminCGIPath$>
 
-=for tags path, system
+=for tags path, configuration
 
 =cut
@@ -5636,4 +5721,6 @@
 (mt-config.cgi).
 
+=for tags configuration
+
 =cut
 
@@ -5648,4 +5735,6 @@
 Returns the file path to the directory where Movable Type has been
 installed. Any trailing "/" character is removed.
+
+=for tags configuration
 
 =cut
@@ -5664,4 +5753,6 @@
 mt-config.cgi. This is the same as L<CGIPath>, but without any
 domain name. This value is guaranteed to end with a "/" character.
+
+=for tags configuration
 
 =cut
@@ -5685,4 +5776,6 @@
 the location of the MT application files alone). This value is
 guaranteed to end with a "/" character.
+
+=for tags configuration
 
 =cut
@@ -5714,4 +5807,6 @@
         alt="Powered by MT" />
 
+=for tags configuration
+
 =cut
 
@@ -5744,4 +5839,6 @@
 for this setting if unassigned is "mt.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5758,4 +5855,6 @@
 default for this setting if unassigned is "mt-comments.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5772,4 +5871,6 @@
 default for this setting if unassigned is "mt-tb.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5786,4 +5887,6 @@
 default for this setting if unassigned is "mt-search.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5802,5 +5905,5 @@
 setting.  Use C<SearchMaxResults> because MaxResults is considered deprecated.
 
-=for tags search
+=for tags search, configuration
 
 =cut
@@ -5818,4 +5921,6 @@
 default for this setting if unassigned is "mt-xmlrpc.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5832,4 +5937,6 @@
 default for this setting if unassigned is "mt-atom.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5846,4 +5953,6 @@
 default for this setting if unassigned is "mt-add-notify.cgi".
 
+=for tags configuration
+
 =cut
 
@@ -5863,4 +5972,6 @@
     </mt:IfAuthor>
 
+=for tags authors
+
 =cut
 
@@ -5880,4 +5991,6 @@
     <a href="<$mt:ArchiveLink type="Author">">Archive for this author</a>
     </mt:AuthorHasEntry>
+
+=for tags authors, entries
 
 =cut
@@ -5905,4 +6018,6 @@
 has written one or more pages that have been published.
 
+=for tags authors, pages
+
 =cut
 
@@ -6018,5 +6133,5 @@
     </mt:Authors>
 
-=for tags multiblog, loop, scoring
+=for tags multiblog, loop, scoring, authors
 
 =cut
@@ -6249,5 +6364,8 @@
 =head2 AuthorID
 
-Outputs the numeric ID of the author currently in context.
+Outputs the numeric ID of the author currently in context. If no author
+is in context, it will use the author of the entry or page in context.
+
+=for tags authors
 
 =cut
@@ -6268,7 +6386,10 @@
 =head2 AuthorName
 
-Outputs the username of the author currently in context.
+Outputs the username of the author currently in context. If no author
+is in context, it will use the author of the entry or page in context.
 
 B<NOTE:> it is not recommended to publish the author's username.
+
+=for tags authors
 
 =cut
@@ -6289,5 +6410,8 @@
 =head2 AuthorDisplayName
 
-Outputs the display name of the author currently in context.
+Outputs the display name of the author currently in context. If no author
+is in context, it will use the author of the entry or page in context.
+
+=for tags authors
 
 =cut
@@ -6308,7 +6432,10 @@
 =head2 AuthorEmail
 
-Outputs the email address of the author currently in context.
+Outputs the email address of the author currently in context. If no author
+is in context, it will use the author of the entry or page in context.
 
 B<NOTE:> it is not recommended to publish the author's email address.
+
+=for tags authors
 
 =cut
@@ -6331,5 +6458,8 @@
 =head2 AuthorURL
 
-Outputs the URL field of the author currently in context.
+Outputs the URL field of the author currently in context. If no author
+is in context, it will use the author of the entry or page in context.
+
+=for tags authors
 
 =cut
@@ -6354,4 +6484,6 @@
 in context. For Movable Type registered users, this is "MT".
 
+=for tags authors
+
 =cut
 
@@ -6397,4 +6529,6 @@
     </mt:Authors>
 
+=for tags authors
+
 =cut
 
@@ -6421,4 +6555,6 @@
         width="100" height="100" />
 
+=for tags authors, userpics
+
 =cut
 
@@ -6442,4 +6578,6 @@
 
 If the author has no userpic, this will output an empty string.
+
+=for tags authors, userpics
 
 =cut
@@ -6474,4 +6612,6 @@
     </mt:Authors></ul>
 
+=for tags authors, userpics, assets
+
 =cut
 
@@ -6499,4 +6639,6 @@
 
 Outputs the 'Basename' field of the author currently in context.
+
+=for tags authors
 
 =cut
@@ -6535,5 +6677,5 @@
 =back
 
-=for tags multiblog, loop
+=for tags multiblog, loop, blogs
 
 =cut
@@ -6592,4 +6734,6 @@
 Outputs the numeric ID of the blog currently in context.
 
+=for tags blogs
+
 =cut
 
@@ -6605,4 +6749,6 @@
 
 Outputs the name of the blog currently in context.
+
+=for tags blogs
 
 =cut
@@ -6622,4 +6768,6 @@
 Outputs the description field of the blog currently in context.
 
+=for tags blogs
+
 =cut
 
@@ -6636,5 +6784,8 @@
 =head2 BlogURL
 
-Outputs the Site URL field of the blog currently in context.
+Outputs the Site URL field of the blog currently in context. An ending
+'/' character is guaranteed.
+
+=for tags blogs
 
 =cut
@@ -6654,5 +6805,8 @@
 =head2 BlogSitePath
 
-Outputs the Site Root field of the blog currently in context.
+Outputs the Site Root field of the blog currently in context. An ending
+'/' character is guaranteed.
+
+=for tags blogs
 
 =cut
@@ -6672,5 +6826,8 @@
 =head2 BlogArchiveURL
 
-Outputs the Archive URL of the blog currently in context.
+Outputs the Archive URL of the blog currently in context. An ending
+'/' character is guaranteed.
+
+=for tags blogs
 
 =cut
@@ -6691,4 +6848,6 @@
 
 Similar to the L<BlogURL> tag, but removes any domain name from the URL.
+
+=for tags blogs
 
 =cut
@@ -6715,4 +6874,17 @@
 General settings screen.
 
+B<Attributes:>
+
+=over 4
+
+=item no_colon (optional; default "0")
+
+If specified, will produce the timezone without the ":" character
+("+|-hhmm" only).
+
+=back
+
+=for tags blogs
+
 =cut
 
@@ -6753,4 +6925,6 @@
 =back
 
+=for tags blogs
+
 =cut
 
@@ -6793,4 +6967,6 @@
 
 =back
+
+=for tags blogs
 
 =cut
@@ -6833,4 +7009,6 @@
 =back
 
+=for tags configuration
+
 =cut
 
@@ -6849,5 +7027,5 @@
 =head2 BlogCategoryCount
 
-=for tags multiblog, count
+=for tags multiblog, count, blogs
 
 =cut
@@ -6869,5 +7047,5 @@
 currently in context.
 
-=for tags multiblog, count
+=for tags multiblog, count, blogs, entries
 
 =cut
@@ -6892,5 +7070,5 @@
 currently in context.
 
-=for tags multiblog, count
+=for tags multiblog, count, blogs, comments
 
 =cut
@@ -6914,5 +7092,5 @@
 currently in context.
 
-=for tags multiblog, count
+=for tags multiblog, count, blogs, pings
 
 =cut
@@ -6939,4 +7117,6 @@
 have a Creative Commons license, this tag returns an empty string.
 
+=for tags blogs, creativecommons
+
 =cut
 
@@ -6961,4 +7141,6 @@
     <img src="<$MTBlogCCLicenseImage$>" alt="Creative Commons" />
     </MTIf>
+
+=for tags blogs, creativecommons
 
 =cut
@@ -6990,4 +7172,6 @@
 If specified, forces the trailing "index" filename to be left on any
 entry permalink published in the RDF block.
+
+=for tags blogs, creativecommons
 
 =cut
@@ -7045,4 +7229,6 @@
 been assigned a Creative Commons License.
 
+=for tags blogs, creativecommons
+
 =cut
 
@@ -7061,4 +7247,6 @@
 '.' character. If no extension is assigned, this returns an empty
 string.
+
+=for tags blogs
 
 =cut
@@ -7081,4 +7269,6 @@
 changed to dashes. In the MT template sets, this identifier is assigned
 to the "id" attribute of the C<body> HTML tag.
+
+=for tags blogs
 
 =cut
@@ -7457,9 +7647,16 @@
             my %map;
             require MT::Placement;
+            my @cat_ids;
             for my $cat (@$cats) {
+                push @cat_ids, $cat->id;
                 my $iter = MT::Placement->load_iter({ category_id => $cat->id });
                 while (my $p = $iter->()) {
                     $map{$p->entry_id}{$cat->id}++;
                 }
+            }
+            if ( !$entries ) {
+                $args{join} = MT::Placement->join_on( 'entry_id', {
+                        category_id => \@cat_ids, %blog_terms
+                    }, { %blog_args, unique => 1 } );
             }
             push @filters, sub { $cexpr->($_[0]->id, \%map) };
@@ -7482,9 +7679,9 @@
         }
         my $tags = [ MT::Tag->load($terms, {
-            binary => { name => 1 },
+            ( $terms ? ( binary => { name => 1 } ) : () ),
             join => MT::ObjectTag->join_on('tag_id', {
                 object_datasource => $class->datasource,
-                %blog_terms
-            }, \%blog_args)
+                %blog_terms,
+            }, { %blog_args, unique => 1 } ),
         }) ];
         my $cexpr = $ctx->compile_tag_filter($tag_arg, $tags);
@@ -7506,4 +7703,10 @@
                 \%map;
             };
+            if (!$entries) {
+                $args{join} = MT::ObjectTag->join_on( 'object_id', {
+                        tag_id => \@tag_ids, object_datasource => 'entry',
+                        %blog_terms
+                    }, { %blog_args, unique => 1 } );
+            }
             push @filters, sub { $cexpr->($preloader->($_[0]->id)) };
         } else {
@@ -7625,5 +7828,5 @@
                 'categories', 'tag',
                 'tags',       'author',
-                'days',       'recently_commented_on',
+                'days',
                 'min_score',  'max_score',
                 'min_rate',    'max_rate',
@@ -7700,5 +7903,17 @@
             }
             $args{offset} = $args->{offset} if $args->{offset};
-            @entries = $class->load(\%terms, \%args);
+
+            if ($args->{recently_commented_on}) {
+                my $entries_iter = _rco_entries_iter(\%terms, \%args,
+                    \%blog_terms, \%blog_args);
+                my $limit = $args->{recently_commented_on};
+                while (my $e = $entries_iter->()) {
+                    push @entries, $e;
+                    last unless --$limit;
+                }
+                $no_resort = 1 unless $args->{sort_order} || $args->{sort_by};
+            } else {
+                @entries = $class->load(\%terms, \%args);
+            }
         } else {
             if (($args->{lastn}) && (!exists $args->{limit})) {
@@ -7710,5 +7925,13 @@
                 $no_resort = 1 unless $args->{sort_by};
             }
-            my $iter = $class->load_iter(\%terms, \%args);
+            my $iter;
+            if ($args->{recently_commented_on}) {
+                $args->{lastn} = $args->{recently_commented_on};
+                $iter = _rco_entries_iter(
+                    \%terms, \%args, \%blog_terms, \%blog_args);
+                $no_resort = 1 unless $args->{sort_order} || $args->{sort_by};
+            } else {
+                $iter = $class->load_iter(\%terms, \%args);
+            }
             my $i = 0; my $j = 0;
             my $off = $args->{offset} || 0;
@@ -7723,11 +7946,4 @@
                 $iter->('finish'), last if $n && $i >= $n;
             }
-        }
-        if ($args->{recently_commented_on}) {
-            my @e = sort {$b->comment_latest->created_on <=>
-                          $a->comment_latest->created_on}
-                    grep {$_->comment_latest} @entries;
-            @entries = splice(@e, 0, $args->{recently_commented_on});
-            $no_resort = 1;
         }
     } else {
@@ -7946,4 +8162,60 @@
 }
 
+# returns an iterator that supplies entries, in the order of last comment
+# date (descending)
+sub _rco_entries_iter {
+    my ($entry_terms, $entry_args, $blog_terms, $blog_args) = @_;
+
+    my $offset = 0;
+    my $limit = $entry_args->{limit} || 20;
+    my @entries;
+    delete $entry_args->{direction}
+        if exists $entry_args->{direction};
+    delete $entry_args->{sort}
+        if exists $entry_args->{sort};
+
+    my $rco_iter = sub {
+        if (@_ && ($_[0] eq 'finish')) {
+            return undef;
+        }
+        if (! @entries) {
+            require MT::Comment;
+            my $iter = MT::Comment->max_group_by({
+                visible => 1,
+                %$blog_terms,
+            }, {
+                join => MT::Entry->join_on(undef,
+                    {
+                        'id' => \'=comment_entry_id',
+                        %$entry_terms,
+                    }, { %$entry_args }),
+                %$blog_args,
+                group => ['entry_id'],
+                max => 'created_on',
+                offset => $offset,
+                limit => $limit,
+            });
+            my @ids;
+            my %order;
+            my $num = 0;
+            while (my ($max, $id) = $iter->()) {
+                push @ids, $id;
+                $order{$id} = $num++;
+            }
+            if ( @ids ) {
+                @entries = MT::Entry->load({ id => \@ids });
+                @entries = sort { $order{$a->id} <=> $order{$b->id} } @entries;
+            }
+        }
+        if ( @entries ) {
+            $offset++;
+            return shift @entries;
+        } else {
+            return undef;
+        }
+    };
+    return $rco_iter;
+}
+
 ###########################################################################
 
@@ -9448,5 +9720,5 @@
         $mo--;
         my $server_offset = $blog->server_offset;
-        if ((localtime (timelocal ($s, $m, $h, $d, $mo, ($y - 1900 >= 0 ? $y - 1900 : 0 ))))[8]) {
+        if ((localtime (timelocal ($s, $m, $h, $d, $mo, $y )))[8]) {
             $server_offset += 1;
         }
@@ -10682,4 +10954,26 @@
     my $a = $ctx->stash('commenter');
     return $a ? $a->name : '';
+}
+
+###########################################################################
+
+=head2 UserSessionCookieTimeout
+
+Returns a JSON-formatted data structure that represents the user that is
+currently logged in.
+
+=for tags comments
+
+=cut
+
+sub _hdlr_user_session_state {
+    my ($ctx, $args, $cond) = @_;
+    my $app = MT->app;
+    return 'null' unless $app->can('session_state');
+
+    my $state = $app->session_state();
+    require JSON;
+    my $json = JSON::objToJson($state);
+    return $json;
 }
 
@@ -15965,2 +16259,4 @@
 
 1;
+
+__END__
Index: /branches/release-39/lib/MT/App/Comments.pm
===================================================================
--- /branches/release-39/lib/MT/App/Comments.pm (revision 2425)
+++ /branches/release-39/lib/MT/App/Comments.pm (revision 2463)
@@ -24,5 +24,5 @@
     $app->SUPER::init(@_) or return;
     $app->add_methods(
-        login            => \&login,
+        login            => \&login_form,
         login_external   => \&login_external,
         do_login         => \&do_login,
@@ -91,9 +91,5 @@
 
 sub load_core_tags {
-    return {
-        function => {
-            UserSessionState => \&_hdlr_user_session_state,
-        },
-    };
+    return {};
 }
 
@@ -109,51 +105,8 @@
 sub _get_commenter_session {
     my $app = shift;
-    my $q   = $app->param;
-
-    my $session_key;
-
-    my $blog = $app->blog;
-    if ($blog) {
-        my $auths = $blog->commenter_authenticators || '';
-        if ( $auths =~ /MovableType/ ) {
-            # First, check for a real MT user login. If one exists,
-            # return that as the commenter identity
-            my ($user, $first_time) = $app->SUPER::login();
-            if ( $user ) {
-                my $sess = $app->session;
-                return ( $sess, $user );
-            }
-        }
-    }
-
-    my %cookies = $app->cookies();
-    my $cookie_name = $app->commenter_cookie;
-    if ( !$cookies{$cookie_name} ) {
-        return ( undef, undef );
-    }
-    $session_key = $cookies{$cookie_name}->value() || "";
-    $session_key =~ y/+/ /;
-    my $cfg = $app->config;
-    require MT::Session;
-    my $sess_obj = MT::Session->load( { id => $session_key, kind => 'SI' } );
-    my $timeout = $cfg->CommentSessionTimeout;
-    my $user_id = $sess_obj->get('author_id') if $sess_obj;
-    my $user = MT::Author->load( $user_id ) if $user_id;
-
-    if (   !$sess_obj
-        || ( $sess_obj->start() + $timeout < time )
-        || ( !$user_id )
-        || ( !$user )
-      )
-    {
-        $app->_invalidate_commenter_session( \%cookies );
-        return ( undef, undef );
-    }
-
-    # session is valid!
-    return ( $sess_obj, $user );
-}
-
-sub login {
+    return $app->get_commenter_session();
+}
+
+sub login_form {
     my $app   = shift;
     my %param = @_;
@@ -1422,11 +1375,9 @@
     my $static = $q->param('static') || $q->param('return_url') || '';
 
-    if ( ($static eq '') || ($static eq 1) ) {
+    if ( ($static eq '') || ($static eq '1') ) {
         require MT::Entry;
         my $entry = MT::Entry->load( $q->param('entry_id') || 0 )
             or return $app->error($app->translate('Can\'t load entry #[_1].', $q->param('entry_id')));
         $target = $entry->archive_url;
-        my $blog = MT::Blog->load( $entry->blog_id );
-        $target = MT::Util::strip_index( $target, $blog );
     }
     elsif ($static ne '') {
@@ -1447,63 +1398,4 @@
 }
 
-sub session_state {
-    my $app = shift;
-    my $blog = $app->blog;
-    my $blog_id = $blog->id if $blog;
-
-    my $c;
-    if ( $blog_id && $blog ) {
-        my ( $sessobj, $commenter ) = $app->_get_commenter_session();
-        if ( $sessobj && $commenter ) {
-            my $blog_perms = $commenter->blog_perm($blog_id);
-            my $banned = $commenter->is_banned($blog_id) ? "1" : "0";
-            $banned = 0 if $blog_perms && $blog_perms->can_administer;
-            $banned ||= 1 if $commenter->status == MT::Author::BANNED();
-
-            if ($banned) {
-                $sessobj->remove;
-            } else {
-                $sessobj->start( time +
-                    $app->config->CommentSessionTimeout); # extend by timeout
-                $sessobj->save();
-            }
-
-            # FIXME: These may not be accurate in 'SingleCommunity' mode...
-            my $can_comment = $banned ? 0 : 1;
-            $can_comment = 0 unless $blog->allow_unreg_comments || $blog->allow_reg_comments;
-            my $can_post = ($blog_perms && $blog_perms->can_create_post) ? "1" : "0";
-            $c = {
-                sid => $sessobj->id,
-                name => $commenter->nickname,
-                url => $commenter->url,
-                email => $commenter->email,
-                userpic => scalar $commenter->userpic_url,
-                profile => "", # profile link url
-                is_authenticated => "1",
-                is_trusted => ($commenter->is_trusted($blog_id) ? "1" : "0"),
-                is_author => ($commenter->type == MT::Author::AUTHOR() ? "1" : "0"),
-                is_anonymous => "0",
-                is_banned => $banned,
-                can_comment => $can_comment,
-                can_post => $can_post,
-            };
-        }
-    }
-
-    unless ($c) {
-        my $can_comment = $blog && $blog->allow_anon_comments ? "1" : "0";
-        $c = {
-            is_authenticated => "0",
-            is_trusted => "0",
-            is_anonymous => "1",
-            can_post => "0", # no anonymous posts
-            can_comment => $can_comment,
-            is_banned => "0",
-        };
-    }
-
-    return $c;
-}
-
 sub session_js {
     my $app = shift;
@@ -1520,12 +1412,4 @@
     $app->print("$jsonp(" . $json . ");\n");
     return undef;
-}
-
-sub _hdlr_user_session_state {
-    my ($ctx, $args, $cond) = @_;
-    my $state = MT->app->session_state();
-    require JSON;
-    my $json = JSON::objToJson($state);
-    return $json;
 }
 
Index: /branches/release-39/lib/MT/App/Search.pm
===================================================================
--- /branches/release-39/lib/MT/App/Search.pm (revision 2438)
+++ /branches/release-39/lib/MT/App/Search.pm (revision 2463)
@@ -496,6 +496,8 @@
     my $blog_id = $app->first_blog_id();
     if ( $blog_id ) {
+        my $blog = $app->model('blog')->load($blog_id);
+        $app->blog( $blog );
         $ctx->stash('blog_id', $blog_id);
-        $ctx->stash('blog',    $app->model('blog')->load($blog_id));
+        $ctx->stash('blog',    $blog);
     }
     $ctx;
