Index: /branches/release-35/lib/MT/Core.pm
===================================================================
--- /branches/release-35/lib/MT/Core.pm (revision 1883)
+++ /branches/release-35/lib/MT/Core.pm (revision 1947)
@@ -235,4 +235,5 @@
                     weblog => 'MT::AtomServer::Weblog::Legacy',
                     '1.0'  => 'MT::AtomServer::Weblog',
+                    comments => 'MT::AtomServer::Comments',
                 },
             },
Index: /branches/release-35/lib/MT/Atom.pm
===================================================================
--- /branches/release-35/lib/MT/Atom.pm (revision 1823)
+++ /branches/release-35/lib/MT/Atom.pm (revision 1947)
@@ -72,4 +72,13 @@
     my ($host) = $blog->site_url =~ m!^https?://([^/:]+)(:\d+)?/!;
 
+    unless ( $entry->atom_id ) {
+        # atom_id is not there - probably because
+        # the entry is in HOLD state
+        $entry->atom_id($entry->make_atom_id());
+        # call update directly because MT::Entry::save
+        # is overkill for the purpose.
+        $entry->update if $entry->atom_id();
+    }
+
     $atom->id($entry->atom_id);
     #$atom->draft('true') if $entry->status != MT::Entry::RELEASE();
@@ -94,4 +103,49 @@
 } 
 
+sub new_with_comment {
+    my $class = shift;
+    my ( $comment, %param ) = @_;
+    my $rfc_compat = $param{Version} && $param{Version} eq '1';
+
+    my $entry = $comment->entry;
+    return unless $entry;
+    my $blog = $comment->blog;
+    return unless $blog;
+
+    my $atom = $class->new(%param);
+    $atom->title(encode_text($entry->title, undef, 'utf-8'));
+    $atom->content(encode_text($comment->text, undef, 'utf-8'));
+    # Old Atom API gets application/xhtml+xml for compatibility -- but why
+    # do we say it's that when all we're guaranteed is it's an opaque blob
+    # of text? So use 'html' for new RFC compatible output.
+    # XML::Atom::Content intelligently determines content-type for rfc compat.
+    unless ( $rfc_compat ) {
+        $atom->content->type('application/xhtml+xml');
+    }
+
+    my $atom_author = new XML::Atom::Person(%param);
+    $atom_author->name(encode_text($comment->author, undef, 'utf-8'));
+    $atom_author->email($comment->email) if $comment->email;
+    my $author_url_field = $rfc_compat ? 'uri' : 'url';
+    $atom_author->$author_url_field($comment->url) if $comment->url;
+    $atom->author($atom_author);
+
+    my $co = _create_issued($comment->created_on, $blog);
+    $atom->issued($co);
+    my $upd = $comment->modified_on;
+    if ( $upd ) {
+        $atom->updated( _create_issued( $upd, $blog ) );
+    }
+    else {
+        $atom->updated( $co );
+    }
+    $atom->add_link({ rel => 'alternate', type => 'text/html',
+                      href => $entry->archive_url . '#comment-' . $comment->id });
+    my ($host) = $blog->site_url =~ m!^https?://([^/:]+)(:\d+)?/!;
+
+    $atom->id($entry->atom_id . '/' . $comment->id);
+    $atom;
+}
+
 1;
 __END__
Index: /branches/release-35/lib/MT/AtomServer.pm
===================================================================
--- /branches/release-35/lib/MT/AtomServer.pm (revision 1915)
+++ /branches/release-35/lib/MT/AtomServer.pm (revision 1947)
@@ -466,5 +466,5 @@
 
         my $e_title = XML::XPath::Node::Element->new('atom:title', 'atom');
-        $e_title->appendChild(XML::XPath::Node::Text->new(MT->translate('Entries')));
+        $e_title->appendChild(XML::XPath::Node::Text->new(MT->translate('[_1]: Entries', $blog->name)));
         $entries->appendChild($e_title);
 
@@ -563,4 +563,5 @@
         my $pub_ts = MT::Util::iso2ts($blog, $iso);
         $entry->authored_on($pub_ts);
+        require MT::DateTime;
         if ( 0 < MT::DateTime->compare( blog => $blog,
                 a => $pub_ts,
@@ -654,4 +655,5 @@
         my $pub_ts = MT::Util::iso2ts($blog, $iso);
         $entry->authored_on($pub_ts);
+        require MT::DateTime;
         if ( 0 < MT::DateTime->compare( blog => $blog,
                 a => $pub_ts,
@@ -727,4 +729,7 @@
         $e->add_link({ rel => $app->edit_link_rel, type => $app->atom_x_content_type,
                        href => ($uri . $entry->id), title => encode_text($entry->title, undef,'utf-8') });
+        $e->add_link({ rel => 'replies', type => $app->atom_x_content_type,
+                href => $app->base . $app->app_path . $app->config->AtomScript . '/comments/blog_id=' . $blog->id . '/entry_id=' . $entry->id });
+
         # feed/updated should be added before entries
         # so we postpone adding them until later
@@ -738,4 +743,5 @@
     $feed->add_entry($_) foreach @entries;
     ## xxx add next/prev links
+    $app->run_callbacks( 'get_posts', $feed, $blog );
     $app->response_content_type($app->atom_content_type);
     $feed->as_xml;
@@ -756,5 +762,14 @@
     $uri .= '/entry_id=';
     $atom->add_link({ rel => $app->edit_link_rel, type => $app->atom_x_content_type,
-                      href => ($uri . $entry->id), title => encode_text($entry->title, undef,'utf-8') });
+        href => ($uri . $entry->id), title => encode_text($entry->title, undef,'utf-8') });
+    $atom->add_link({ rel => 'replies', type => $app->atom_x_content_type,
+        href => $app->base
+            . $app->app_path
+            . $app->config->AtomScript
+            . '/comments/blog_id=' . $blog->id
+            . '/entry_id=' . $entry->id
+    });
+    $app->run_callbacks( 'get_post', $atom, $entry );
+    $app->response_content_type($app->atom_content_type);
     $atom->as_xml;
 }
@@ -903,5 +918,4 @@
 use XML::Atom;  # for LIBXML
 use XML::Atom::Feed;
-use base qw( MT::AtomServer );
 use MT::Blog;
 use MT::Permission;
@@ -1011,4 +1025,166 @@
 }
 
+package MT::AtomServer::Comments;
+use strict;
+
+use base qw( MT::AtomServer::Weblog );
+use MT::I18N qw( encode_text );
+
+sub script { $_[0]->{cfg}->AtomScript . '/comments' }
+
+sub handle_request {
+    my $app = shift;
+    $app->authenticate || return;
+    if (my $svc = $app->{param}{svc}) {
+        if ($svc eq 'upload') {
+            return $app->handle_upload;
+        } elsif ($svc eq 'categories') {
+            return $app->get_categories;
+        }
+    }
+    my $method = $app->request_method;
+    if ($method eq 'POST') {
+#        return $app->new_comment;
+    } elsif ($method eq 'PUT') {
+#        return $app->edit_comment;
+    } elsif ($method eq 'DELETE') {
+#        return $app->delete_comment;
+    } elsif ($method eq 'GET') {
+        if ($app->{param}{comment_id}) {
+            return $app->get_comment;
+        } elsif ($app->{param}{entry_id}) {
+            return $app->get_comments;
+        } else {
+            return $app->get_blog_comments;
+        }
+    }
+}
+
+sub new_with_comment {
+    my $app = shift;
+    my ($comment) = @_;
+    my $atom = MT::Atom::Entry->new_with_comment( $comment, Version => 1.0 );
+
+    my $mo = MT::Atom::Entry::_create_issued(
+        $comment->modified_on || $comment->created_on, $comment->blog);
+    $atom->set(MT::AtomServer::Weblog::NS_APP(), 'edited', $mo);
+
+    $atom;
+}
+
+sub get_comment {
+    my $app = shift;
+    my $blog = $app->{blog};
+    my $comment_id = $app->{param}{comment_id}
+        or return $app->error(400, "No comment_id");
+    my $comment = MT::Comment->load($comment_id)
+        or return $app->error(400, "Invalid comment_id");
+    my $entry = $comment->entry;
+    my $uri = $app->base . $app->uri . '/blog_id=' . $blog->id;
+    my $c = $app->new_with_comment($comment);
+    $c->add_link({ rel => 'self', type => $app->atom_x_content_type,
+                   href => $uri . '/comment_id=' . $comment->id });
+    # feed/updated should be added before entries
+    # so we postpone adding them until later
+    $c->set('http://purl.org/syndication/thread/1.0', 'in-reply-to',
+        undef,
+        { ref => $entry->atom_id,
+            type => 'text/html',
+            href => $entry->permalink } );
+    $app->run_callbacks( 'get_comment', $c, $comment );
+    $app->response_content_type($app->atom_content_type);
+    $c->as_xml;
+}
+
+sub get_blog_comments {
+    my $app = shift;
+    my $blog = $app->{blog};
+    my %terms = (blog_id => $blog->id, visible => 1);
+    my %arg = (sort => $app->get_posts_order_field, direction => 'descend');
+    my $Limit = 20;
+    $arg{limit} = $Limit + 1;
+    $arg{offset} = $app->{param}{offset} || 0;
+
+    my $feed = $app->new_feed();
+    my $uri = $app->base . $app->uri . '/blog_id=' . $blog->id;
+    my $blogname = encode_text($blog->name, undef, 'utf-8');
+    $feed->add_link({ rel => 'alternate', type => 'text/html',
+                      href => $blog->site_url });
+    $feed->add_link({ rel => 'self', type => $app->atom_x_content_type,
+                      href => $uri });
+    $feed->title($blogname);
+
+    require URI;
+    my $site_uri = URI->new($blog->site_url);
+    if ( $site_uri ) {
+        my $blog_created = MT::Util::format_ts('%Y-%m-%d', $blog->created_on, $blog, 'en', 0);
+        my $id = 'tag:'.$site_uri->host.','.$blog_created.':'.$site_uri->path.'/'.$blog->id;
+        $feed->id($id);
+    }
+    $app->_comments_in_atom($feed, \%terms, \%arg);
+    $app->run_callbacks( 'get_blog_comments', $feed, $blog );
+    ## xxx add next/prev links
+    $app->response_content_type($app->atom_content_type);
+    $feed->as_xml;
+}
+
+sub get_comments {
+    my $app = shift;
+    my $blog = $app->{blog};
+    my $entry_id = $app->{param}{entry_id}
+        or return $app->error(400, "No entry_id");
+    my $entry = MT::Entry->load($entry_id)
+        or return $app->error(400, "Invalid entry_id");
+    my %terms = (blog_id => $blog->id, entry_id => $entry->id, visible => 1);
+    my %arg = (sort => $app->get_posts_order_field, direction => 'descend');
+    my $Limit = 20;
+    $arg{limit} = $Limit + 1;
+    $arg{offset} = $app->{param}{offset} || 0;
+
+    my $feed = $app->new_feed();
+    my $uri = $app->base . $app->uri . '/blog_id=' . $blog->id;
+    my $blogname = encode_text($blog->name, undef, 'utf-8');
+    $feed->add_link({ rel => 'alternate', type => 'text/html',
+                      href => $entry->permalink });
+    $feed->add_link({ rel => 'self', type => $app->atom_x_content_type,
+                      href => $uri . '/entry_id=' . $entry->id });
+    $feed->title($entry->title);
+    $feed->id($entry->atom_id . '/comments');
+    $app->_comments_in_atom($feed, \%terms, \%arg);
+    $app->run_callbacks( 'get_comments', $feed, $entry );
+    ## xxx add next/prev links
+    $app->response_content_type($app->atom_content_type);
+    $feed->as_xml;
+}
+
+sub _comments_in_atom {
+    my $app = shift;
+    my ( $feed, $terms, $args ) = @_;
+    require MT::Comment;
+    my $iter = MT::Comment->load_iter($terms, $args);
+    my $latest_date = 0;
+    my @comments;
+    while (my $comment = $iter->()) {
+        my $c = $app->new_with_comment($comment);
+        # feed/updated should be added before entries
+        # so we postpone adding them until later
+        my $entry = $comment->entry;
+        $c->set('http://purl.org/syndication/thread/1.0', 'in-reply-to',
+            undef,
+            { ref => $entry->atom_id,
+              type => 'text/html',
+              href => $entry->permalink } );
+        push @comments, $c;
+        my $date = $comment->modified_on || $comment->created_on;
+        if ( $latest_date < $date ) {
+            $latest_date = $date;
+            $feed->updated( $c->updated );
+        }
+    }
+    $feed->add_entry($_) foreach @comments;
+    $feed;
+}
+
+
 1;
 __END__
@@ -1110,4 +1286,44 @@
 $original_entry will have an unassigned 'id'.
 
+=item get_posts
+
+    callback($eh, $app, $feed, $blog)
+
+Called right before get_posts method returns atom feed response.
+I<$feed> is a reference to XML::Atom::Feed object.
+I<$blog> is a reference to the requested MT::Blog object.
+
+=item get_post
+
+    callback($eh, $app, $atom_entry, $entry)
+
+Called right before get_post method returns atom entry response.
+I<$atom_entry> is a reference to XML::Atom::Entry object.
+I<$entry> is a reference to the requested MT::Entry object.
+
+=item get_blog_comments
+
+    callback($eh, $app, $feed, $blog)
+
+Called right before get_blog_comments method returns atom feed response.
+I<$feed> is a reference to XML::Atom::Feed object.
+I<$blog> is a reference to the requested MT::Blog object.
+
+=item get_comments
+
+    callback($eh, $app, $feed, $entry)
+
+Called right before get_comments method returns atom feed response. 
+I<$feed> is a reference to XML::Atom::Feed object.
+I<$entry> is a reference to the requested MT::Entry object.
+
+=item get_comment
+
+    callback($eh, $app, $atom_entry, $comment)
+
+Called right before get_comment method returns atom entry response.
+I<$atom_entry> is a reference to XML::Atom::Entry object.
+I<$comment> is a reference to the requested MT::Comment object.
+
 =back
 
Index: /branches/release-35/t/41-atom.t
===================================================================
--- /branches/release-35/t/41-atom.t (revision 1946)
+++ /branches/release-35/t/41-atom.t (revision 1947)
@@ -11,5 +11,5 @@
 use XML::Atom::Entry;
 
-use Test::More tests => 37;
+use Test::More tests => 97;
 
 # To keep away from being under FastCGI
@@ -118,6 +118,6 @@
     if (ok($resp->is_success)) {
         my $blog_feed_url = $feed_link{$base_uri}->($resp);
-        my $uri = new URI($blog_feed_url);
-        is($uri->path, $base_uri . '/blog_id=1', 'blog feed url is correct');
+        my $blog_feed_uri = new URI($blog_feed_url);
+        is($blog_feed_uri->path, $base_uri . '/blog_id=1', 'blog feed url is correct');
     }
     else {
@@ -152,4 +152,22 @@
         my @entries = $feed->entries;
         is($entry_count, scalar(@entries), 'number of entries is correct');
+
+        # check if entries have replies link relation
+        my $failed = 0;
+        foreach my $entry (@entries) {
+            next if !$entry->id && $entry->title =~ /^I just finished installing Movable Type/;
+            my $mt_entry = MT::Entry->load({
+                atom_id => $entry->id,
+                blog_id => 1,
+            });
+            $failed = 1, last unless $mt_entry;
+            my ($replies) = grep {
+                $_->rel eq 'replies'
+            } $entry->links;
+            $failed = 1, last unless $replies;
+            my $replies_uri = new URI($replies->href);
+            $failed = 1, last unless $replies_uri->path eq '/mt-atom.cgi/comments/blog_id=1/entry_id='.$mt_entry->id;
+        }
+        is($failed, 0, 'all the entries have replies link rel');
     }
     else {
@@ -342,4 +360,145 @@
 } #end foreach
 
+COMMENT:
+# comments retrieval
+{
+    my $wsse_header = make_wsse($chuck_token);
+    my $uri = new URI;
+    $uri->path('/mt-atom.cgi/comments/blog_id=1');
+    my $req = new HTTP::Request(GET => $uri);
+    $req->header('Authorization' => 'Atom');
+    $req->header('X-WSSE' => $wsse_header);
+
+    my $resp = $ua->request($req);
+    if (ok($resp->is_success)) {
+        my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
+        my $comments = XML::Atom::Feed->new(\$resp->content());
+        my $count = MT::Comment->count({
+            blog_id => 1, visible => 1
+        });
+        is( $count, scalar($comments->entries), 'comment count' );
+        foreach my $c ( $comments->entries ) {
+            my $id = $c->id;
+            my ( $cmt_id ) = $id =~ m{/([0-9]+)$};
+            die unless $cmt_id;
+            my $mt_comment = MT::Comment->load($cmt_id);
+            die unless $mt_comment;
+            my $mt_entry = $mt_comment->entry;
+            is($c->title, $mt_entry->title, 'comment title == entry title');
+            is( $c->author->name, $mt_comment->author, 'comment author' );
+            is( $c->author->email || '', $mt_comment->email || '', 'commenter email' );
+            is( $c->author->uri || '', $mt_comment->url || '', 'commenter url'  );
+            if ( $XML::Atom::LIBXML ) {
+                my $nodelist = $c->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');    
+                my $irt = $nodelist->shift;
+                ok($irt, 'in-reply-to');
+                is( $irt->ref, $mt_entry->atom_id, 'in-reply-to/ref' );
+                is( $irt->href, $mt_entry->permalink, 'in-reply-to/href' );
+            }
+        }
+    }
+    else {
+        die $resp->content();
+    }
+}
+
+{
+    my $iter = MT::Comment->count_group_by(
+        { blog_id => 1, visible => 1 },
+        { group => ['entry_id'], sort => [ { desc => 'DESC', column => '1' } ]
+        }
+    );
+    #$Data::ObjectDriver::PROFILE = 1;
+    #my $p = Data::ObjectDriver->profiler;
+    #$p->reset;
+    #print "$_\n" foreach @{$p->query_log};
+    my ( $count, $eid ) = $iter->();
+    $iter->('finish');
+    my $entry = MT::Entry->load($eid);
+
+    my $wsse_header = make_wsse($chuck_token);
+    my $uri = new URI;
+    $uri->path('/mt-atom.cgi/1.0/blog_id=1/entry_id=' . $entry->id);
+    my $req = new HTTP::Request(GET => $uri);
+    $req->header('Authorization' => 'Atom');
+    $req->header('X-WSSE' => $wsse_header);
+
+    my $resp = $ua->request($req);
+    if (ok($resp->is_success)) {
+        my $feed = XML::Atom::Entry->new(\$resp->content());
+        my ($replies) = grep {
+            $_->rel eq 'replies'
+        } $feed->links;
+
+        # retrieve comments from replies url
+        my $replies_uri = new URI($replies->href);
+        my $wsse_header = make_wsse($chuck_token);
+        my $uri = new URI;
+        $uri->path($replies_uri->path);
+        my $req = new HTTP::Request(GET => $uri);
+        $req->header('Authorization' => 'Atom');
+        $req->header('X-WSSE' => $wsse_header);
+
+        my $resp = $ua->request($req);
+        my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
+        if (ok($resp->is_success)) {
+            my $comments = XML::Atom::Feed->new(\$resp->content());
+            is( $count, scalar($comments->entries), 'comment count' );
+            foreach my $e ( $comments->entries ) {
+                is($e->title, $entry->title, 'comment title == entry title');
+                my $id = $e->id;
+                my ( $cmt_id ) = $id =~ m{/([0-9]+)$};
+                die unless $cmt_id;
+                my $mt_comment = MT::Comment->load($cmt_id);
+                die unless $mt_comment;
+                is( $e->author->name, $mt_comment->author, 'comment author' );
+                is( $e->author->email || '', $mt_comment->email || '', 'commenter email' );
+                is( $e->author->uri || '', $mt_comment->url || '', 'commenter url'  );
+                if ( $XML::Atom::LIBXML ) {
+                    my $nodelist = $e->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');    
+                    my $irt = $nodelist->shift;
+                    ok($irt, 'in-reply-to');
+                    is( $irt->ref, $entry->atom_id, 'in-reply-to/ref' );
+                    is( $irt->href, $entry->permalink, 'in-reply-to/href' );
+                }
+            }
+        }
+        else {
+            die $resp->content();
+        }
+    }
+}
+
+{
+    my $thr_ns = XML::Atom::Namespace->new(prefix => undef, uri => 'http://purl.org/syndication/thread/1.0');
+    my $wsse_header = make_wsse($chuck_token);
+    my $uri = new URI;
+    $uri->path('/mt-atom.cgi/comments/blog_id=1/comment_id=1');
+    my $req = new HTTP::Request(GET => $uri);
+    $req->header('Authorization' => 'Atom');
+    $req->header('X-WSSE' => $wsse_header);
+
+    my $resp = $ua->request($req);
+    if (ok($resp->is_success)) {
+        my $c = XML::Atom::Entry->new(\$resp->content());
+        my $mt_comment = MT::Comment->load(1);
+        die unless $mt_comment;
+        my $entry = $mt_comment->entry;
+        is( $c->title, $entry->title, 'comment title == entry title' );
+        is( $c->author->name, $mt_comment->author, 'comment author' );
+        is( $c->author->email || '', $mt_comment->email || '', 'commenter email' );
+        is( $c->author->uri || '', $mt_comment->url || '', 'commenter url'  );
+        if ( $XML::Atom::LIBXML ) {
+            my $nodelist = $c->elem->getElementsByTagNameNS('http://purl.org/syndication/thread/1.0', 'in-reply-to');    
+            my $irt = $nodelist->shift;
+            ok($irt, 'in-reply-to');
+            is( $irt->ref, $entry->atom_id, 'in-reply-to/ref' );
+            is( $irt->href, $entry->permalink, 'in-reply-to/href' );
+        }
+    }
+    else {
+        die $resp->content();
+    }
+}
 
 END {
