Commit 45c48c56b08996e2eac67a828b1e0d84084c189b

Authored by Rodrigo Souto
1 parent 09892926

[tolerance-time] Hotspot to expire or remove article and comment buttons

app/helpers/application_helper.rb
@@ -1329,4 +1329,17 @@ module ApplicationHelper @@ -1329,4 +1329,17 @@ module ApplicationHelper
1329 _("Are you sure that you want to remove the item \"#{article.name}\"?") 1329 _("Are you sure that you want to remove the item \"#{article.name}\"?")
1330 end 1330 end
1331 end 1331 end
  1332 +
  1333 + def expirable_link_to(expired, content, url, options = {})
  1334 + if expired
  1335 + options[:class] = (options[:class] || '') + ' disabled'
  1336 + content_tag('a', ' '+content_tag('span', content), options)
  1337 + else
  1338 + link_to content, url, options
  1339 + end
  1340 + end
  1341 +
  1342 + def remove_content_button(action)
  1343 + @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true)
  1344 + end
1332 end 1345 end
app/helpers/cms_helper.rb
@@ -42,13 +42,25 @@ module CmsHelper @@ -42,13 +42,25 @@ module CmsHelper
42 42
43 def display_spread_button(profile, article) 43 def display_spread_button(profile, article)
44 if profile.person? 44 if profile.person?
45 - button_without_text :spread, _('Spread this'), :action => 'publish', :id => article.id 45 + expirable_button article, :spread, _('Spread this'), :action => 'publish', :id => article.id
46 elsif profile.community? && environment.portal_community 46 elsif profile.community? && environment.portal_community
47 - button_without_text :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id 47 + expirable_button article, :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id
48 end 48 end
49 end 49 end
50 50
51 def display_delete_button(article) 51 def display_delete_button(article)
52 - button_without_text :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article) 52 + expirable_button article, :delete, _('Delete'), { :action => 'destroy', :id => article.id }, :method => :post, :confirm => delete_article_message(article)
  53 + end
  54 +
  55 + def expirable_button(content, action, title, url, options = {})
  56 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  57 + if reason.present?
  58 + options[:class] = (options[:class] || '') + ' disabled'
  59 + options[:disabled] = 'disabled'
  60 + options.delete(:confirm)
  61 + options.delete(:method)
  62 + title = reason
  63 + end
  64 + button_without_text action.to_sym, title, url, options
53 end 65 end
54 end 66 end
app/helpers/content_viewer_helper.rb
@@ -68,4 +68,20 @@ module ContentViewerHelper @@ -68,4 +68,20 @@ module ContentViewerHelper
68 end 68 end
69 end 69 end
70 70
  71 + def expirable_content_reference(content, action, text, url, options = {})
  72 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  73 + options[:title] = reason
  74 + expirable_link_to reason.present?, text, url, options
  75 + end
  76 +
  77 + def expirable_button(content, action, text, url, options = {})
  78 + options[:class] = "button with-text icon-#{action.to_s}"
  79 + expirable_content_reference content, action, text, url, options
  80 + end
  81 +
  82 + def expirable_comment_link(content, action, text, url, options = {})
  83 + options[:class] = "comment-footer comment-footer-link comment-footer-hide"
  84 + expirable_content_reference content, action, text, url, options
  85 + end
  86 +
71 end 87 end
app/models/comment.rb
@@ -25,6 +25,8 @@ class Comment < ActiveRecord::Base @@ -25,6 +25,8 @@ class Comment < ActiveRecord::Base
25 25
26 xss_terminate :only => [ :body, :title, :name ], :on => 'validation' 26 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
27 27
  28 + delegate :environment, :to => :source
  29 +
28 def action_tracker_target 30 def action_tracker_target
29 self.article.profile 31 self.article.profile
30 end 32 end
app/views/cms/view.rhtml
@@ -49,13 +49,13 @@ @@ -49,13 +49,13 @@
49 <%= article.class.short_description %> 49 <%= article.class.short_description %>
50 </td> 50 </td>
51 <td class="article-controls"> 51 <td class="article-controls">
52 - <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => article.id %> 52 + <%= expirable_button article, :edit, _('Edit'), {:action => 'edit', :id => article.id} if !remove_content_button(:edit) %>
53 <%= button_without_text :eyes, _('Public view'), article.view_url %> 53 <%= button_without_text :eyes, _('Public view'), article.view_url %>
54 - <%= display_spread_button(profile, article) unless article.folder? %>  
55 - <% if !environment.enabled?('cant_change_homepage') %>  
56 - <%= button_without_text :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %> 54 + <%= display_spread_button(profile, article) unless article.folder? || remove_content_button(:spread)%>
  55 + <% if !environment.enabled?('cant_change_homepage') && !remove_content_button(:home) %>
  56 + <%= expirable_button article, :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %>
57 <% end %> 57 <% end %>
58 - <%= display_delete_button(article) %> 58 + <%= display_delete_button(article) if !remove_content_button(:delete) %>
59 </td> 59 </td>
60 </tr> 60 </tr>
61 <% end %> 61 <% end %>
app/views/content_viewer/_article_toolbar.rhtml
1 <div<%= user && " class='logged-in'" %>> 1 <div<%= user && " class='logged-in'" %>>
2 <div id="article-actions"> 2 <div id="article-actions">
3 3
4 - <% if @page.allow_edit?(user) %>  
5 - <%= link_to content_tag( 'span', label_for_edit_article(@page) ),  
6 - profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }),  
7 - :class => 'button with-text icon-edit' %> 4 +
  5 + <% if @page.allow_edit?(user) && !remove_content_button(:edit) %>
  6 + <% content = content_tag('span', label_for_edit_article(@page)) %>
  7 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
  8 + <%= expirable_button @page, :edit, content, url %>
8 <% end %> 9 <% end %>
9 10
10 - <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) %>  
11 - <%= link_to content_tag( 'span', _('Delete') ),  
12 - profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}),  
13 - :method => :post,  
14 - :class => 'button with-text icon-delete',  
15 - :confirm => delete_article_message(@page) %> 11 + <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete)%>
  12 + <% content = content_tag( 'span', _('Delete') ) %>
  13 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page}) %>
  14 + <% options = {:method => :post, :confirm => delete_article_message(@page)} %>
  15 + <%= expirable_button @page, :delete, content, url, options %>
16 <% end %> 16 <% end %>
17 17
18 - <% if !@page.folder? && @page.allow_spread?(user) %> 18 + <% if !@page.folder? && @page.allow_spread?(user) && !remove_content_button(:spread) %>
  19 + <% content = content_tag( 'span', _('Spread this') ) %>
  20 + <% url = nil %>
19 <% if profile.kind_of?(Person) %> 21 <% if profile.kind_of?(Person) %>
20 - <%= link_to content_tag( 'span', _('Spread this') ),  
21 - profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }),  
22 - :class => 'button with-text icon-spread' %> 22 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page }) %>
23 <% elsif profile.kind_of?(Community) && environment.portal_community %> 23 <% elsif profile.kind_of?(Community) && environment.portal_community %>
24 - <%= link_to content_tag( 'span', _('Spread this') ),  
25 - profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }),  
26 - :class => 'button with-text icon-spread' %> 24 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page }) %>
27 <% end %> 25 <% end %>
  26 + <%= expirable_button @page, :spread, content, url if url %>
28 <% end %> 27 <% end %>
29 28
30 <% if !@page.gallery? && @page.allow_create?(user) %> 29 <% if !@page.gallery? && @page.allow_create?(user) %>
31 - <%= link_to _('Add translation'),  
32 - profile.admin_url.merge(:controller => 'cms', :action => 'new',  
33 - :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)),  
34 - :type => @page.type, :article => { :translation_of_id => @page.native_translation.id }),  
35 - :class => 'button with-text icon-locale' if @page.translatable? && !@page.native_translation.language.blank? %> 30 + <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale) %>
  31 + <% content = _('Add translation') %>
  32 + <% parent_id = (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)) %>
  33 + <% url = profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => parent_id, :type => @page.type, :article => { :translation_of_id => @page.native_translation.id })%>
  34 + <%= expirable_button @page, :locale, content, url %>
  35 + <% end %>
  36 +
36 <%= lightbox_remote_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %> 37 <%= lightbox_remote_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) %>
37 <% end %> 38 <% end %>
38 39
@@ -40,8 +41,11 @@ @@ -40,8 +41,11 @@
40 <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %> 41 <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) %>
41 <% end %> 42 <% end %>
42 43
43 - <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) %>  
44 - <%= link_to content_tag( 'span', _('Suggest an article') ), profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}), :id => 'suggest-article-link', :class => 'button with-text icon-new' %> 44 + <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest) %>
  45 + <% content = content_tag( 'span', _('Suggest an article') ) %>
  46 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}) %>
  47 + <% options = {:id => 'suggest-article-link'} %>
  48 + <%= expirable_button @page, :suggest, content, url, options %>
45 <% end %> 49 <% end %>
46 50
47 <%= report_abuse(profile, :link, @page) %> 51 <%= report_abuse(profile, :link, @page) %>
app/views/content_viewer/_comment.rhtml
@@ -57,6 +57,10 @@ @@ -57,6 +57,10 @@
57 </script> 57 </script>
58 <% end %> 58 <% end %>
59 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %> 59 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  60 + <% if comment.author && comment.author == user %>
  61 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  62 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  63 + <% end %>
60 <%= link_to_function _('Reply'), 64 <%= link_to_function _('Reply'),
61 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id, 65 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
62 :class => 'comment-footer comment-footer-link comment-footer-hide', 66 :class => 'comment-footer comment-footer-link comment-footer-hide',
app/views/profile/_comment.rhtml
@@ -46,6 +46,10 @@ @@ -46,6 +46,10 @@
46 </script> 46 </script>
47 <% end %> 47 <% end %>
48 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %> 48 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  49 + <% if comment.author && comment.author == user %>
  50 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  51 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  52 + <% end %>
49 <%= link_to_function _('Reply'), 53 <%= link_to_function _('Reply'),
50 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id, 54 "var f = add_comment_reply_form(this, %s); f.find('input[name=comment[title]], textarea').val(''); return false" % comment.id,
51 :class => 'comment-footer comment-footer-link comment-footer-hide', 55 :class => 'comment-footer comment-footer-link comment-footer-hide',
lib/noosfero/plugin.rb
@@ -196,28 +196,6 @@ class Noosfero::Plugin @@ -196,28 +196,6 @@ class Noosfero::Plugin
196 nil 196 nil
197 end 197 end
198 198
199 - # This is a generic hotspot for all controllers on Noosfero.  
200 - # If any plugin wants to define filters to run on any controller, the name of  
201 - # the hotspot must be in the following form: <underscored_controller_name>_filters.  
202 - # Example: for ProfileController the hotspot is profile_controller_filters  
203 - #  
204 - # -> Adds a filter to a controller  
205 - # returns = { :type => type,  
206 - # :method_name => method_name,  
207 - # :options => {:opt1 => opt1, :opt2 => opt2},  
208 - # :block => Proc or lambda block}  
209 - # type = 'before_filter' or 'after_filter'  
210 - # method_name = The name of the filter  
211 - # option = Filter options, like :only or :except  
212 - # block = Block that the filter will call  
213 - def method_missing(method, *args, &block)  
214 - if method.to_s =~ /^(.+)_controller_filters$/  
215 - []  
216 - else  
217 - super  
218 - end  
219 - end  
220 -  
221 # This method will be called just before a comment is saved to the database. 199 # This method will be called just before a comment is saved to the database.
222 # 200 #
223 # It can modify the comment in several ways. In special, a plugin can call 201 # It can modify the comment in several ways. In special, a plugin can call
@@ -256,4 +234,40 @@ class Noosfero::Plugin @@ -256,4 +234,40 @@ class Noosfero::Plugin
256 nil 234 nil
257 end 235 end
258 236
  237 + def method_missing(method, *args, &block)
  238 + # This is a generic hotspot for all controllers on Noosfero.
  239 + # If any plugin wants to define filters to run on any controller, the name of
  240 + # the hotspot must be in the following form: <underscored_controller_name>_filters.
  241 + # Example: for ProfileController the hotspot is profile_controller_filters
  242 + #
  243 + # -> Adds a filter to a controller
  244 + # returns = { :type => type,
  245 + # :method_name => method_name,
  246 + # :options => {:opt1 => opt1, :opt2 => opt2},
  247 + # :block => Proc or lambda block}
  248 + # type = 'before_filter' or 'after_filter'
  249 + # method_name = The name of the filter
  250 + # option = Filter options, like :only or :except
  251 + # block = Block that the filter will call
  252 + if method.to_s =~ /^(.+)_controller_filters$/
  253 + []
  254 + # -> Removes the action button from the content
  255 + # returns = boolean
  256 + elsif method.to_s =~ /^content_remove_(#{content_actions.join('|')})$/
  257 + nil
  258 + # -> Expire the action button from the content
  259 + # returns = string with reason of expiration
  260 + elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
  261 + nil
  262 + else
  263 + super
  264 + end
  265 + end
  266 +
  267 + private
  268 +
  269 + def content_actions
  270 + %w[edit delete spread locale suggest home]
  271 + end
  272 +
259 end 273 end
public/designs/icons/tango/ie6.css
1 .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) } 1 .msie6 .icon-edit { background-image: url(ie6/Tango/16x16/apps/text-editor.gif) }
2 .msie6 .icon-home { background-image: url(ie6/Tango/16x16/actions/go-home.gif) } 2 .msie6 .icon-home { background-image: url(ie6/Tango/16x16/actions/go-home.gif) }
3 -.msie6 .icon-new { background-image: url(ie6/Tango/16x16/actions/filenew.gif) } 3 +.msie6 .icon-new,
  4 +.msie6 .icon-suggest { background-image: url(ie6/Tango/16x16/actions/filenew.gif) }
4 .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) } 5 .msie6 .icon-close { background-image: url(ie6/Tango/16x16/actions/gtk-cancel.gif) }
5 .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) } 6 .msie6 .icon-newfolder { background-image: url(ie6/Tango/16x16/actions/folder-new.gif) }
6 .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) } 7 .msie6 .icon-save { background-image: url(ie6/Tango/16x16/actions/filesave.gif) }
public/designs/icons/tango/style.css
@@ -3,7 +3,8 @@ @@ -3,7 +3,8 @@
3 /******************SMALL ICONS********************/ 3 /******************SMALL ICONS********************/
4 .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) } 4 .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) }
5 .icon-home { background-image: url(Tango/16x16/actions/go-home.png) } 5 .icon-home { background-image: url(Tango/16x16/actions/go-home.png) }
6 -.icon-new { background-image: url(Tango/16x16/actions/filenew.png) } 6 +.icon-new,
  7 +.icon-suggest { background-image: url(Tango/16x16/actions/filenew.png) }
7 .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) } 8 .icon-close { background-image: url(Tango/16x16/actions/gtk-cancel.png) }
8 .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) } 9 .icon-newfolder { background-image: url(Tango/16x16/actions/folder-new.png) }
9 .icon-folder { background-image: url(Tango/16x16/places/folder.png) } 10 .icon-folder { background-image: url(Tango/16x16/places/folder.png) }
public/designs/themes/base/style.css
@@ -908,6 +908,16 @@ hr.pre-posts, hr.sep-posts { @@ -908,6 +908,16 @@ hr.pre-posts, hr.sep-posts {
908 #article-actions a.button:hover { 908 #article-actions a.button:hover {
909 color: #555753; 909 color: #555753;
910 } 910 }
  911 +#content a.disabled,
  912 +#content a.disabled:hover {
  913 + color: #888;
  914 + text-decoration: none;
  915 +}
  916 +#content a.button.disabled,
  917 +#content a.button.disabled:hover {
  918 + background-color: #CCC;
  919 + border-color: #CCC;
  920 +}
911 921
912 #addThis { 922 #addThis {
913 text-align: right; 923 text-align: right;
public/stylesheets/application.css
@@ -1487,7 +1487,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with- @@ -1487,7 +1487,13 @@ a.button:hover, body.noosfero a.button:hover, input.button:hover, a.button.with-
1487 body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none { 1487 body.noosfero a.button.with-text.icon-none, body.noosfero input.button.with-text.icon-none {
1488 padding-left: 2px; 1488 padding-left: 2px;
1489 } 1489 }
1490 -a.button.disabled, input.disabled { 1490 +a.disabled{
  1491 + filter: url(filters.svg#grayscale); /* Firefox 3.5+ */
  1492 + filter: gray; /* IE6-9 */
  1493 + -webkit-filter: grayscale(1); /* Google Chrome & Safari 6+ */
  1494 + cursor: default;
  1495 +}
  1496 +input.disabled {
1491 opacity: 0.5; 1497 opacity: 0.5;
1492 filter-opacity: 50%; 1498 filter-opacity: 50%;
1493 } 1499 }
test/functional/content_viewer_controller_test.rb
@@ -1414,4 +1414,36 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -1414,4 +1414,36 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
1414 1414
1415 end 1415 end
1416 1416
  1417 + should 'not display article actions button if any plugins says so' do
  1418 + class Plugin1 < Noosfero::Plugin
  1419 + def content_viewer_remove_edit(content); true; end
  1420 + end
  1421 + class Plugin2 < Noosfero::Plugin
  1422 + def content_viewer_remove_edit(content); false; end
  1423 + end
  1424 +
  1425 + environment.enable_plugin(Plugin1.name)
  1426 + environment.enable_plugin(Plugin2.name)
  1427 +
  1428 + login_as('testinguser')
  1429 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1430 + assert_no_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{profile.home_page.id}" } }
  1431 + end
  1432 +
  1433 + should 'expire article actions button if any plugins says so' do
  1434 + class Plugin1 < Noosfero::Plugin
  1435 + def content_viewer_expire_edit(content); 'This button is expired.'; end
  1436 + end
  1437 + class Plugin2 < Noosfero::Plugin
  1438 + def content_viewer_expire_edit(content); nil; end
  1439 + end
  1440 +
  1441 + environment.enable_plugin(Plugin1.name)
  1442 + environment.enable_plugin(Plugin2.name)
  1443 +
  1444 + login_as('testinguser')
  1445 + xhr :get, :view_page, :profile => 'testinguser', :page => [], :toolbar => true
  1446 + assert_tag :tag => 'div', :attributes => { :id => 'article-actions' }, :descendant => { :tag => 'a', :attributes => { :href => "/myprofile/testinguser/cms/edit/#{profile.home_page.id}", :title => 'This button is expired.', :class => 'button with-text icon-edit disabled', :onclick => 'return false' } }
  1447 + end
  1448 +
1417 end 1449 end
test/unit/comment_test.rb
@@ -378,4 +378,12 @@ class CommentTest &lt; ActiveSupport::TestCase @@ -378,4 +378,12 @@ class CommentTest &lt; ActiveSupport::TestCase
378 assert_not_nil article.activity 378 assert_not_nil article.activity
379 end 379 end
380 380
  381 + should 'delegate environment to article' do
  382 + profile = fast_create(Profile, :environment_id => Environment.default)
  383 + article = fast_create(Article, :profile_id => profile.id)
  384 + comment = fast_create(Comment, :source_id => article.id, :source_type => 'Article')
  385 +
  386 + assert_equal Environment.default, comment.environment
  387 + end
  388 +
381 end 389 end