diff --git a/app/controllers/my_profile/profile_design_controller.rb b/app/controllers/my_profile/profile_design_controller.rb
index 4b8a870..791bc90 100644
--- a/app/controllers/my_profile/profile_design_controller.rb
+++ b/app/controllers/my_profile/profile_design_controller.rb
@@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController
protect 'edit_profile_design', :profile
def available_blocks
- blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock ]
+ blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock ]
# blocks exclusive for organizations
if profile.has_members?
diff --git a/app/models/feed_reader_block.rb b/app/models/feed_reader_block.rb
new file mode 100644
index 0000000..654ceea
--- /dev/null
+++ b/app/models/feed_reader_block.rb
@@ -0,0 +1,57 @@
+class FeedReaderBlock < Block
+
+ include DatesHelper
+
+ settings_items :address, :type => :string
+ settings_items :limit, :type => :integer
+ settings_items :fetched_at, :type => :date
+
+ settings_items :feed_title, :type => :string
+ settings_items :feed_items, :type => :array
+
+ before_create do |block|
+ block.limit = 5
+ block.feed_items = []
+ end
+
+ def self.description
+ _('List the latest N posts from a given RSS feed.')
+ end
+
+ def help
+ _('This block can be used to create a list of latest N posts from a given RSS feed. You should only enter the RSS feed address.')
+ end
+
+ def default_title
+ self.feed_title.nil? ? _('Feed Reader') : self.feed_title
+ end
+
+ def formatted_feed_content
+ if self.fetched_at.nil? or self.feed_items.empty?
+ return ("
%s
" % _('Feed content was not loaded yet'))
+ else
+ return "" +
+ "#{_("Updated: %s") % show_date(self.fetched_at)}
"
+ end
+ end
+
+ def add_item(title, link, date, content)
+ self.feed_items << {:title => title, :link => link}
+ end
+
+ def clean
+ self.feed_items = []
+ self.feed_title = nil
+ end
+
+ def content
+ block_title(title) + formatted_feed_content
+ end
+
+ def editable?
+ true
+ end
+
+end
diff --git a/app/views/box_organizer/_feed_reader_block.rhtml b/app/views/box_organizer/_feed_reader_block.rhtml
new file mode 100644
index 0000000..726d51f
--- /dev/null
+++ b/app/views/box_organizer/_feed_reader_block.rhtml
@@ -0,0 +1,4 @@
+
+ <%= labelled_form_field _('Address'), text_field(:block, :address, :size => 40) %>
+ <%= labelled_form_field _('Limit of posts to display'), select(:block, :limit, [5, 10, 20, 50]) %>
+
diff --git a/lib/feed_handler.rb b/lib/feed_handler.rb
new file mode 100644
index 0000000..3a273f2
--- /dev/null
+++ b/lib/feed_handler.rb
@@ -0,0 +1,38 @@
+require 'feedparser'
+require 'open-uri'
+
+class FeedHandler
+
+ def parse(content)
+ raise FeedHandler::ParseError, "Content is nil" if content.nil?
+ begin
+ return FeedParser::Feed::new(content)
+ rescue Exception => ex
+ raise FeedHandler::ParseError, ex.to_s
+ end
+ end
+
+ def fetch(address)
+ begin
+ content = ""
+ open(address) do |s| content = s.read end
+ return content
+ rescue Exception => ex
+ raise FeedHandler::FetchError, ex.to_s
+ end
+ end
+
+ def process(container)
+ content = fetch(container.address)
+ container.fetched_at = Time.now
+ parse = parse(content)
+ container.feed_title = parse.title
+ parse.items[0..container.limit-1].each do |item|
+ container.add_item(item.title, item.link, item.date, item.content)
+ end
+ end
+
+ class ParseError < Exception; end
+ class FetchError < Exception; end
+
+end
diff --git a/public/stylesheets/blocks/feed-reader-block.css b/public/stylesheets/blocks/feed-reader-block.css
new file mode 100644
index 0000000..2058ed1
--- /dev/null
+++ b/public/stylesheets/blocks/feed-reader-block.css
@@ -0,0 +1,5 @@
+.feed-reader-block-fetched-at {
+ color: #CCC;
+ font-size: 11px;
+ text-align: center;
+}
diff --git a/script/feed-updater b/script/feed-updater
new file mode 100755
index 0000000..db2e2bd
--- /dev/null
+++ b/script/feed-updater
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+require File.dirname(__FILE__) + '/../config/environment'
+
+FeedReaderBlock.find(:all).each do |feed_block|
+ unless feed_block.address.nil?
+ begin
+ feed_block.clean
+ handler = FeedHandler.new
+ handler.process(feed_block)
+ feed_block.save!
+ RAILS_DEFAULT_LOGGER.info("%s ID %d fetched at %s" % [feed_block.class.name, feed_block.id, feed_block.fetched_at])
+ rescue FeedHandler::ParseError => ex
+ RAILS_DEFAULT_LOGGER.warn("Error parsing content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s])
+ rescue FeedHandler::FetchError => ex
+ RAILS_DEFAULT_LOGGER.warn("Error fetching content from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s])
+ rescue Exception => ex
+ RAILS_DEFAULT_LOGGER.warn("Unknown error from %s ID %d\n%s" % [feed_block.class.name, feed_block.id, ex.to_s])
+ end
+ end
+end
diff --git a/test/fixtures/files/feed.xml b/test/fixtures/files/feed.xml
new file mode 100644
index 0000000..cdf1d3c
--- /dev/null
+++ b/test/fixtures/files/feed.xml
@@ -0,0 +1,44 @@
+
+
+
+ Feed for unit tests
+ http://localhost/feed-test
+ Feed content
+ pt_BR
+ -
+ Last POST
+
+ Cursus justo nec urna. Suspendisse potenti. In hac
+ habitasse platea dictumst. Cras quis lacus. Vestibulum rhoncus
+ congue lacus.
+
+ Wed, 11 Mar 2009 19:15:57 -0300
+ http://localhost/last-post
+ http://localhost/last-post
+
+ -
+ Second POST
+
+ Suspendisse tincidunt mi vel metus. Vivamus non urna in nisi gravida congue.
+ Aenean semper orci a eros. Praesent dictum. Maecenas pharetra odio ut dui.
+ Pellentesque ut orci. Sed lobortis, velit at laoreet suscipit, quam est
+ sagittis nibh.
+
+ Wed, 18 Feb 2009 11:01:53 -0300
+ http://localhost/second-post
+ http://localhost/second-post
+
+ -
+ First POST
+
+ Lacus at sapien suscipit tempus. Proin pulvinar velit
+ sed nulla. Curabitur aliquet leo ac massa. Praesent posuere lectus
+ vitae odio. Donec imperdiet urna vel ante. In semper accumsan diam.
+ Vestibulum porta justo. Suspendisse egestas commodo eros.
+
+ Sun, 18 Jan 2009 11:34:30 -0300
+ http://localhost/first-post
+ http://localhost/first-post
+
+
+
diff --git a/test/functional/profile_design_controller_test.rb b/test/functional/profile_design_controller_test.rb
index d58ac19..dd97196 100644
--- a/test/functional/profile_design_controller_test.rb
+++ b/test/functional/profile_design_controller_test.rb
@@ -289,4 +289,28 @@ class ProfileDesignControllerTest < Test::Unit::TestCase
assert_equal !state, block.visible?
end
+ should 'offer to create feed reader block' do
+ get :add_block, :profile => 'designtestuser'
+ assert_tag :tag => 'input', :attributes => { :id => 'type_feedreaderblock', :value => 'FeedReaderBlock' }
+ end
+
+ should 'be able to edit FeedReaderBlock' do
+ @box1.blocks << FeedReaderBlock.new(:address => 'feed address')
+
+ get :edit, :profile => 'designtestuser', :id => @box1.blocks[-1].id
+
+ assert_response :success
+ assert_tag :tag => 'input', :attributes => { :name => "block[address]", :value => 'feed address' }
+ assert_tag :tag => 'select', :attributes => { :name => "block[limit]" }
+ end
+
+ should 'be able to save FeedReaderBlock configurations' do
+ @box1.blocks << FeedReaderBlock.new(:address => 'feed address')
+
+ post :save, :profile => 'designtestuser', :id => @box1.blocks[-1].id, :block => {:address => 'new feed address', :limit => '20'}
+
+ assert_equal 'new feed address', @box1.blocks[-1].address
+ assert_equal 20, @box1.blocks[-1].limit
+ end
+
end
diff --git a/test/unit/feed_handler_test.rb b/test/unit/feed_handler_test.rb
new file mode 100644
index 0000000..4e214f3
--- /dev/null
+++ b/test/unit/feed_handler_test.rb
@@ -0,0 +1,83 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class FeedHandlerTest < Test::Unit::TestCase
+
+ class FeedContainer
+ attr_accessor :limit
+ attr_accessor :fetched_at
+ attr_accessor :feed_title
+ attr_accessor :feed_items
+ attr_accessor :address
+ def initialize
+ self.limit = 5
+ self.feed_title = "Feed Container Mocked"
+ self.feed_items = []
+ self.address = 'test/fixtures/files/feed.xml'
+ end
+ def add_item(title, link, date, content)
+ self.feed_items << title
+ end
+ end
+
+ def setup
+ @handler = FeedHandler.new
+ @container = FeedContainer.new
+ end
+ attr_reader :handler, :container
+
+ should 'fetch feed content' do
+ content = handler.fetch(container.address)
+ assert_match /Feed content<\/description>/, content
+ assert_match /Feed for unit tests<\/title>/, content
+ end
+
+ should 'parse feed content' do
+ content = ""
+ open(container.address) do |s| content = s.read end
+ parse = handler.parse(content)
+ assert_equal 'Feed for unit tests', parse.title
+ assert_equal 'http://localhost/feed-test', parse.link
+ assert_equal 'Last POST', parse.items[0].title
+ end
+
+ should 'process feed and populate container' do
+ handler.process(container)
+ assert_equal 'Feed for unit tests', container.feed_title
+ assert_equal ["Last POST", "Second POST", "First POST"], container.feed_items
+ end
+
+ should 'raise exception when parser nil' do
+ handler = FeedHandler.new
+ assert_raise FeedHandler::ParseError do
+ handler.parse(nil)
+ end
+ end
+
+ should 'raise exception when parser invalid content' do
+ handler = FeedHandler.new
+ assert_raise FeedHandler::ParseError do
+ handler.parse('content')
+ end
+ end
+
+ should 'raise exception when fetch nil' do
+ handler = FeedHandler.new
+ assert_raise FeedHandler::FetchError do
+ handler.fetch(nil)
+ end
+ end
+
+ should 'raise exception when fetch invalid address' do
+ handler = FeedHandler.new
+ assert_raise FeedHandler::FetchError do
+ handler.fetch('bli://invalid@address')
+ end
+ end
+
+ should 'save only latest N posts from feed' do
+ container.limit = 1
+ handler.process(container)
+ assert_equal 1, container.feed_items.size
+ end
+
+end
diff --git a/test/unit/feed_reader_block_test.rb b/test/unit/feed_reader_block_test.rb
new file mode 100644
index 0000000..0443274
--- /dev/null
+++ b/test/unit/feed_reader_block_test.rb
@@ -0,0 +1,73 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class FeedReaderBlockTest < ActiveSupport::TestCase
+
+ include DatesHelper
+
+ def setup
+ @feed = FeedReaderBlock.new
+ @fetched_at = Time.now
+ @feed.fetched_at = @fetched_at
+ @feed.save!
+ end
+ attr_reader :feed, :fetched_at
+
+ should 'default describe' do
+ assert_not_equal Block.description, FeedReaderBlock.description
+ end
+
+ should 'have address and limit' do
+ assert_respond_to feed, :address
+ assert_respond_to feed, :limit
+ end
+
+ should 'default value of limit' do
+ assert_equal 5, feed.limit
+ end
+
+ should 'is editable' do
+ assert feed.editable?
+ end
+
+ should 'display feed posts from content' do
+ feed.feed_items = []
+ %w[ last-post second-post first-post ].each do |i|
+ feed.feed_items << {:title => i, :link => "http://localhost/#{i}"}
+ end
+ feed.feed_title = 'Feed for unit tests'
+ feed_content = feed.content
+ assert_tag_in_string feed_content, :tag => 'h3', :content => 'Feed for unit tests'
+ assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/last-post' }, :content => 'last-post'
+ assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/second-post' }, :content => 'second-post'
+ assert_tag_in_string feed_content, :tag => 'a', :attributes => { :href => 'http://localhost/first-post' }, :content => 'first-post'
+ end
+
+ should 'display channel title as title by default' do
+ feed.feed_title = 'Feed for unit tests'
+ assert_equal 'Feed for unit tests', feed.title
+ end
+
+ should 'display default title when hasnt feed_content' do
+ assert_equal 'Feed Reader', feed.title
+ end
+
+ should 'notice when content not fetched yet' do
+ assert_tag_in_string feed.content, :tag => 'p', :content => 'Feed content was not loaded yet'
+ end
+
+ should 'display last fetched date' do
+ feed.feed_items = ['one', 'two']
+ assert_tag_in_string feed.content, :tag => 'div',
+ :content => "Updated: #{show_date(@fetched_at)}",
+ :attributes => {:class => 'feed-reader-block-fetched-at'}
+ end
+
+ should 'clear feed title and items' do
+ feed.feed_items = %w[ last-post second-post first-post ]
+ feed.feed_title = 'Feed Test'
+ feed.clean
+ assert_nil feed.feed_title
+ assert_equal [], feed.feed_items
+ end
+
+end
--
libgit2 0.21.2