Commit 12b82c088c310e5100cd5c7861709b019da19b97
1 parent
c892d918
Exists in
master
and in
29 other branches
Added RSS feed per tag
* Modularized RSS link in head code; added add_rss_feed_to_head helper method * Modularized RSS generation in FeedWriter class + Refactored RssFeed to use FeedWriter + removed feed_item_description (this fixes ActionItem1463) (ActionItem1518)
Showing
17 changed files
with
130 additions
and
92 deletions
Show diff stats
app/controllers/public/profile_controller.rb
@@ -29,6 +29,19 @@ class ProfileController < PublicController | @@ -29,6 +29,19 @@ class ProfileController < PublicController | ||
29 | end | 29 | end |
30 | end | 30 | end |
31 | 31 | ||
32 | + def tag_feed | ||
33 | + @tag = params[:id] | ||
34 | + tagged = profile.find_tagged_with(@tag).paginate(:per_page => 20, :page => 1) | ||
35 | + feed_writer = FeedWriter.new | ||
36 | + data = feed_writer.write( | ||
37 | + tagged, | ||
38 | + :title => _("%s's contents tagged with \"%s\"") % [profile.name, @tag], | ||
39 | + :description => _("%s's contents tagged with \"%s\"") % [profile.name, @tag], | ||
40 | + :link => url_for(:action => 'tag') | ||
41 | + ) | ||
42 | + render :text => data, :content_type => "text/xml" | ||
43 | + end | ||
44 | + | ||
32 | def communities | 45 | def communities |
33 | if is_cache_expired?(profile.communities_cache_key(params)) | 46 | if is_cache_expired?(profile.communities_cache_key(params)) |
34 | @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage]) | 47 | @communities = profile.communities.paginate(:per_page => per_page, :page => params[:npage]) |
app/helpers/application_helper.rb
@@ -869,12 +869,9 @@ module ApplicationHelper | @@ -869,12 +869,9 @@ module ApplicationHelper | ||
869 | article_helper.cms_label_for_edit | 869 | article_helper.cms_label_for_edit |
870 | end | 870 | end |
871 | 871 | ||
872 | - def meta_tags_for_article(article) | ||
873 | - if @controller.controller_name == 'content_viewer' | ||
874 | - if article and (article.blog? or (article.parent and article.parent.blog?)) | ||
875 | - blog = article.blog? ? article : article.parent | ||
876 | - "<link rel='alternate' type='application/rss+xml' title='#{blog.feed.title}' href='#{url_for blog.feed.url}' />" | ||
877 | - end | 872 | + def add_rss_feed_to_head(title, url) |
873 | + content_for :feeds do | ||
874 | + "<link rel='alternate' type='application/rss+xml' title='#{h(title)}' href='#{url_for(url)}' />" | ||
878 | end | 875 | end |
879 | end | 876 | end |
880 | 877 |
app/models/blog.rb
@@ -6,7 +6,7 @@ class Blog < Folder | @@ -6,7 +6,7 @@ class Blog < Folder | ||
6 | attr_accessor :filter | 6 | attr_accessor :filter |
7 | 7 | ||
8 | after_create do |blog| | 8 | after_create do |blog| |
9 | - blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile, :feed_item_description => 'body') | 9 | + blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile) |
10 | blog.feed = blog.feed_attrs | 10 | blog.feed = blog.feed_attrs |
11 | end | 11 | end |
12 | 12 |
app/models/rss_feed.rb
@@ -41,16 +41,6 @@ class RssFeed < Article | @@ -41,16 +41,6 @@ class RssFeed < Article | ||
41 | end | 41 | end |
42 | validates_inclusion_of :include, :in => [ 'all', 'parent_and_children' ], :if => :include | 42 | validates_inclusion_of :include, :in => [ 'all', 'parent_and_children' ], :if => :include |
43 | 43 | ||
44 | - # determinates what to include in the feed as items' description. Possible | ||
45 | - # values are +:body+ (default) and +:abstract+. | ||
46 | - def feed_item_description | ||
47 | - settings[:feed_item_description] | ||
48 | - end | ||
49 | - def feed_item_description=(value) | ||
50 | - settings[:feed_item_description] = value | ||
51 | - end | ||
52 | - validates_inclusion_of :feed_item_description, :in => [ 'body', 'abstract' ], :if => :feed_item_description | ||
53 | - | ||
54 | # TODO | 44 | # TODO |
55 | def to_html(options = {}) | 45 | def to_html(options = {}) |
56 | end | 46 | end |
@@ -74,38 +64,13 @@ class RssFeed < Article | @@ -74,38 +64,13 @@ class RssFeed < Article | ||
74 | end | 64 | end |
75 | end | 65 | end |
76 | def data | 66 | def data |
77 | - articles = fetch_articles | ||
78 | - result = "" | ||
79 | - xml = Builder::XmlMarkup.new(:target => result) | ||
80 | - | ||
81 | - xml.instruct! :xml, :version=>"1.0" | ||
82 | - xml.rss(:version=>"2.0") do | ||
83 | - xml.channel do | ||
84 | - xml.title(_("%s's RSS feed") % (self.profile.name)) | ||
85 | - xml.link(url_for(self.profile.url)) | ||
86 | - xml.description(_("%s's content published at %s") % [self.profile.name, self.profile.environment.name]) | ||
87 | - xml.language("pt_BR") | ||
88 | - for article in articles | ||
89 | - unless self == article | ||
90 | - xml.item do | ||
91 | - xml.title(article.name) | ||
92 | - if self.feed_item_description == 'body' | ||
93 | - xml.description(article.to_html) | ||
94 | - else | ||
95 | - xml.description(article.abstract) | ||
96 | - end | ||
97 | - # rfc822 | ||
98 | - xml.pubDate(article.created_at.rfc2822) | ||
99 | - # link to article | ||
100 | - xml.link(url_for(article.url)) | ||
101 | - xml.guid(url_for(article.url)) | ||
102 | - end | ||
103 | - end | ||
104 | - end | ||
105 | - end | ||
106 | - end | ||
107 | - | ||
108 | - result | 67 | + articles = fetch_articles.select { |a| a != self } |
68 | + FeedWriter.new.write( | ||
69 | + articles, | ||
70 | + :title => _("%s's RSS feed") % (self.profile.name), | ||
71 | + :description => _("%s's content published at %s") % [self.profile.name, self.profile.environment.name], | ||
72 | + :link => url_for(self.profile.url) | ||
73 | + ) | ||
109 | end | 74 | end |
110 | 75 | ||
111 | def self.short_description | 76 | def self.short_description |
app/views/cms/_blog.rhtml
@@ -56,7 +56,6 @@ | @@ -56,7 +56,6 @@ | ||
56 | 56 | ||
57 | <% f.fields_for 'feed', @article.feed do |feed| %> | 57 | <% f.fields_for 'feed', @article.feed do |feed| %> |
58 | <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %> | 58 | <%= labelled_form_field(_('Limit of posts in RSS Feed'), feed.select(:limit, [5, 10, 20, 50])) %> |
59 | - <%= labelled_form_field(_('Use as description in RSS Feed:'), feed.select(:feed_item_description, [ [ _('Article body'), 'body'], [ _('Article abstract'), 'abstract'] ])) %> | ||
60 | <% end %> | 59 | <% end %> |
61 | 60 | ||
62 | <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> | 61 | <% f.fields_for 'external_feed_builder', @article.external_feed do |efeed| %> |
app/views/content_viewer/blog_page.rhtml
1 | +<% add_rss_feed_to_head(article.name, article.feed.url) if article.blog? && article.feed %> | ||
2 | + | ||
1 | <%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.enabled && article.external_feed.fetched_at.nil? %> | 3 | <%= content_tag('em', _('(external feed was not loaded yet)'), :id => 'external-feed-info', :class => 'metadata') if article.blog? && article.external_feed && article.external_feed.enabled && article.external_feed.fetched_at.nil? %> |
2 | 4 | ||
3 | <div> | 5 | <div> |
app/views/content_viewer/view_page.rhtml
app/views/layouts/application-ng.rhtml
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= html_language %>" lang="<%= html_language %>"> | 2 | <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<%= html_language %>" lang="<%= html_language %>"> |
3 | <head> | 3 | <head> |
4 | <title><%= h page_title %></title> | 4 | <title><%= h page_title %></title> |
5 | - <%= meta_tags_for_article(@page) %> | 5 | + <%= yield(:feeds) %> |
6 | <!--<meta http-equiv="refresh" content="1"/>--> | 6 | <!--<meta http-equiv="refresh" content="1"/>--> |
7 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> | 7 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> |
8 | <meta name="description" content="<%= @environment.name %>" /> | 8 | <meta name="description" content="<%= @environment.name %>" /> |
app/views/layouts/application.rhtml
@@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
6 | <meta name="description" content="<%= @environment.name %>" /> | 6 | <meta name="description" content="<%= @environment.name %>" /> |
7 | <meta name="keywords" content="Noosfero, Community, Open Source" /> | 7 | <meta name="keywords" content="Noosfero, Community, Open Source" /> |
8 | <link rel="shortcut icon" href="<%= image_path('/designs/themes/' + current_theme + '/favicon.ico') %>" type="image/x-icon" /> | 8 | <link rel="shortcut icon" href="<%= image_path('/designs/themes/' + current_theme + '/favicon.ico') %>" type="image/x-icon" /> |
9 | - <!-- ok --><%= meta_tags_for_article(@page) %> | 9 | + <%= yield(:feeds) %> |
10 | 10 | ||
11 | <%= noosfero_javascript %> | 11 | <%= noosfero_javascript %> |
12 | <%= javascript_include_tag 'menu', 'auto-open-menu', 'menu-config', :cache => 'cache-menu' %> | 12 | <%= javascript_include_tag 'menu', 'auto-open-menu', 'menu-config', :cache => 'cache-menu' %> |
app/views/profile/tag.rhtml
1 | +<% add_rss_feed_to_head(_("%s's contents tagged with \"%s\"") % [profile.name, @tag], tag_feed_path) %> | ||
2 | + | ||
1 | <h1><%= _('Content tagged with "%s"') % @tag %></h1> | 3 | <h1><%= _('Content tagged with "%s"') % @tag %></h1> |
2 | 4 | ||
5 | +<p> | ||
6 | +<%= link_to image_tag('icons-mime/rss-feed.png', :alt => _('Feed for this tag'), :title => _('Feed for this tag')), tag_feed_path, :class => 'blog-feed-link'%> | ||
7 | +</p> | ||
8 | + | ||
3 | <% cache_timeout(@tag_cache_key, 4.hour.from_now) do %> | 9 | <% cache_timeout(@tag_cache_key, 4.hour.from_now) do %> |
4 | <div class='search-tagged-items'> | 10 | <div class='search-tagged-items'> |
5 | <ul> | 11 | <ul> |
config/routes.rb
@@ -65,6 +65,9 @@ ActionController::Routing::Routes.draw do |map| | @@ -65,6 +65,9 @@ ActionController::Routing::Routes.draw do |map| | ||
65 | # invite | 65 | # invite |
66 | map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/ | 66 | map.invite 'profile/:profile/invite/:action', :controller => 'invite', :profile => /#{Noosfero.identifier_format}/ |
67 | 67 | ||
68 | + # feeds per tag | ||
69 | + map.tag_feed 'profile/:profile/tag/:id/feed', :controller => 'profile', :action =>'tag_feed', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ | ||
70 | + | ||
68 | # public profile information | 71 | # public profile information |
69 | map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ | 72 | map.profile 'profile/:profile/:action/:id', :controller => 'profile', :action => 'index', :id => /.*/, :profile => /#{Noosfero.identifier_format}/ |
70 | 73 |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +Feature: profile tags | ||
2 | + As a Noosfero user | ||
3 | + I want to to view content tagged | ||
4 | + So that I can follow the subjects I care about | ||
5 | + | ||
6 | + Background: | ||
7 | + Given the following users | ||
8 | + | login | | ||
9 | + | terceiro | | ||
10 | + And the following articles | ||
11 | + | owner | name | body | tag_list | | ||
12 | + | terceiro | text 1 | text 1 content | tag1, tag2 | | ||
13 | + | terceiro | text 2 | text 2 content | tag1, tag3 | | ||
14 | + | ||
15 | + Scenario: tag feed | ||
16 | + When I go to terceiro's profile | ||
17 | + And I follow "tag1" | ||
18 | + And I follow "Feed for this tag" | ||
19 | + Then I should see "text 1" | ||
20 | + And I should see "text 2" |
features/support/paths.rb
@@ -30,6 +30,9 @@ module NavigationHelpers | @@ -30,6 +30,9 @@ module NavigationHelpers | ||
30 | when /^(.*)'s sitemap/ | 30 | when /^(.*)'s sitemap/ |
31 | '/profile/%s/sitemap' % Profile.find_by_name($1).identifier | 31 | '/profile/%s/sitemap' % Profile.find_by_name($1).identifier |
32 | 32 | ||
33 | + when /^(.*)'s profile/ | ||
34 | + '/profile/%s' % Profile.find_by_name($1).identifier | ||
35 | + | ||
33 | when /^login page$/ | 36 | when /^login page$/ |
34 | '/account/login' | 37 | '/account/login' |
35 | 38 |
@@ -0,0 +1,37 @@ | @@ -0,0 +1,37 @@ | ||
1 | +class FeedWriter | ||
2 | + | ||
3 | + include ActionController::UrlWriter | ||
4 | + | ||
5 | + def write(articles, options = {}) | ||
6 | + result = "" | ||
7 | + xml = Builder::XmlMarkup.new(:target => result) | ||
8 | + | ||
9 | + xml.instruct! :xml, :version=>"1.0" | ||
10 | + xml.rss(:version=>"2.0") do | ||
11 | + xml.channel do | ||
12 | + xml.title(options[:title] || _('Feed')) | ||
13 | + if options[:link] | ||
14 | + xml.link(options[:link]) | ||
15 | + end | ||
16 | + if options[:description] | ||
17 | + xml.description(options[:description]) | ||
18 | + end | ||
19 | + for article in articles | ||
20 | + xml.item do | ||
21 | + xml.title(article.name) | ||
22 | + xml.description(article.to_html) | ||
23 | + if article.created_at | ||
24 | + # rfc822 | ||
25 | + xml.pubDate(article.created_at.rfc2822) | ||
26 | + end | ||
27 | + # link to article | ||
28 | + xml.link(url_for(article.url)) | ||
29 | + xml.guid(url_for(article.url)) | ||
30 | + end | ||
31 | + end | ||
32 | + end | ||
33 | + end | ||
34 | + | ||
35 | + result | ||
36 | + end | ||
37 | +end |
test/unit/blog_test.rb
@@ -32,12 +32,6 @@ class BlogTest < ActiveSupport::TestCase | @@ -32,12 +32,6 @@ class BlogTest < ActiveSupport::TestCase | ||
32 | assert_kind_of RssFeed, b.feed | 32 | assert_kind_of RssFeed, b.feed |
33 | end | 33 | end |
34 | 34 | ||
35 | - should 'include articles body in feed by default' do | ||
36 | - p = create_user('testuser').person | ||
37 | - b = Blog.create!(:profile => p, :name => 'blog_feed_test') | ||
38 | - assert_equal 'body', b.feed.feed_item_description | ||
39 | - end | ||
40 | - | ||
41 | should 'save feed options' do | 35 | should 'save feed options' do |
42 | p = create_user('testuser').person | 36 | p = create_user('testuser').person |
43 | p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') | 37 | p.articles << Blog.new(:profile => p, :name => 'blog_feed_test') |
@@ -0,0 +1,26 @@ | @@ -0,0 +1,26 @@ | ||
1 | +require File.dirname(__FILE__) + '/../test_helper' | ||
2 | + | ||
3 | +class FeedWriterTest < Test::Unit::TestCase | ||
4 | + | ||
5 | + should 'generate feeds' do | ||
6 | + articles = [] | ||
7 | + profile = fast_create(:profile, :identifier => "tagger") | ||
8 | + articles << fast_create(:article, :name => 'text 1', :slug => 'text-1', :path => 'text-1', :profile_id => profile.id) | ||
9 | + articles << fast_create(:article, :name => 'text 2', :slug => 'text-2', :path => 'text-2', :profile_id => profile.id) | ||
10 | + writer = FeedWriter.new | ||
11 | + | ||
12 | + feed = writer.write(articles) | ||
13 | + assert_match('text 1', feed) | ||
14 | + assert_match('/tagger/' + articles.first.slug, feed) | ||
15 | + end | ||
16 | + | ||
17 | + should 'use title, link and description' do | ||
18 | + writer = FeedWriter.new | ||
19 | + rss = writer.write([], :title => "my title", :description => "my description", :link => "http://example.com/") | ||
20 | + assert_match("my title", rss) | ||
21 | + assert_match("my description", rss) | ||
22 | + assert_match("http://example.com/", rss) | ||
23 | + end | ||
24 | + | ||
25 | +end | ||
26 | + |
test/unit/rss_feed_test.rb
@@ -62,24 +62,6 @@ class RssFeedTest < Test::Unit::TestCase | @@ -62,24 +62,6 @@ class RssFeedTest < Test::Unit::TestCase | ||
62 | feed.data | 62 | feed.data |
63 | end | 63 | end |
64 | 64 | ||
65 | - should 'be able to choose to put abstract or entire body on feed' do | ||
66 | - profile = create_user('testuser').person | ||
67 | - a1 = profile.articles.build(:name => 'article 1', 'abstract' => 'my abstract', 'body' => 'my text'); a1.save! | ||
68 | - | ||
69 | - feed = RssFeed.new(:name => 'testfeed') | ||
70 | - feed.profile = profile | ||
71 | - feed.save! | ||
72 | - | ||
73 | - rss = feed.data | ||
74 | - assert_match /<description>my abstract<\/description>/, rss | ||
75 | - assert_no_match /<description>my text<\/description>/, rss | ||
76 | - | ||
77 | - feed.feed_item_description = 'body' | ||
78 | - rss = feed.data | ||
79 | - assert_match /<description>my text<\/description>/, rss | ||
80 | - assert_no_match /<description>my abstract<\/description>/, rss | ||
81 | - end | ||
82 | - | ||
83 | should "be able to search only children of feed's parent" do | 65 | should "be able to search only children of feed's parent" do |
84 | profile = create_user('testuser').person | 66 | profile = create_user('testuser').person |
85 | a1 = profile.articles.build(:name => 'article 1'); a1.save! | 67 | a1 = profile.articles.build(:name => 'article 1'); a1.save! |
@@ -201,21 +183,6 @@ class RssFeedTest < Test::Unit::TestCase | @@ -201,21 +183,6 @@ class RssFeedTest < Test::Unit::TestCase | ||
201 | assert !feed.errors.invalid?(:include) | 183 | assert !feed.errors.invalid?(:include) |
202 | end | 184 | end |
203 | 185 | ||
204 | - should 'allow only body and abstract as feed_item_description' do | ||
205 | - feed = RssFeed.new | ||
206 | - feed.feed_item_description = :something_else | ||
207 | - feed.valid? | ||
208 | - assert feed.errors.invalid?(:feed_item_description) | ||
209 | - | ||
210 | - feed.feed_item_description = 'body' | ||
211 | - feed.valid? | ||
212 | - assert !feed.errors.invalid?(:feed_item_description) | ||
213 | - | ||
214 | - feed.feed_item_description = 'abstract' | ||
215 | - feed.valid? | ||
216 | - assert !feed.errors.invalid?(:feed_item_description) | ||
217 | - end | ||
218 | - | ||
219 | should 'provide proper short description' do | 186 | should 'provide proper short description' do |
220 | assert_kind_of String, RssFeed.short_description | 187 | assert_kind_of String, RssFeed.short_description |
221 | end | 188 | end |
@@ -246,7 +213,7 @@ class RssFeedTest < Test::Unit::TestCase | @@ -246,7 +213,7 @@ class RssFeedTest < Test::Unit::TestCase | ||
246 | blog = fast_create(Blog, :profile_id => profile.id) | 213 | blog = fast_create(Blog, :profile_id => profile.id) |
247 | published_article = PublishedArticle.create!(:reference_article => article, :profile => profile) | 214 | published_article = PublishedArticle.create!(:reference_article => article, :profile => profile) |
248 | blog.posts << published_article | 215 | blog.posts << published_article |
249 | - feed = RssFeed.new(:parent => blog, :profile => profile, :feed_item_description => 'body') | 216 | + feed = RssFeed.new(:parent => blog, :profile => profile) |
250 | 217 | ||
251 | assert_match published_article.to_html, feed.data | 218 | assert_match published_article.to_html, feed.data |
252 | end | 219 | end |