Commit 9508041164c47c02745ffce877414b9cb58495f9

Authored by Rodrigo Souto
2 parents d0b4277a f1e0dc83

Merge branch 'master' into language-selection

Conflicts:
	app/helpers/forms_helper.rb
	app/models/box.rb
	app/views/box_organizer/edit.rhtml
	db/schema.rb
	test/unit/box_test.rb
Showing 273 changed files with 8742 additions and 1192 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 273 files displayed.

INSTALL.varnish
@@ -15,6 +15,10 @@ Noosfero was tested with Varnish 2.x. If you are using a Debian Lenny (and you @@ -15,6 +15,10 @@ Noosfero was tested with Varnish 2.x. If you are using a Debian Lenny (and you
15 should, unless Debian already released Squeeze by now), make sure you install 15 should, unless Debian already released Squeeze by now), make sure you install
16 varnish from the lenny-backports suite. 16 varnish from the lenny-backports suite.
17 17
  18 +Install the RPAF apache module (or skip this step if not using apache):
  19 +
  20 + # apt-get install libapache2-mod-rpaf
  21 +
18 3) Enable varnish logging: 22 3) Enable varnish logging:
19 23
20 3a) Edit /etc/default/varnishncsa and uncomment the line that contains: 24 3a) Edit /etc/default/varnishncsa and uncomment the line that contains:
app/controllers/admin/users_controller.rb
@@ -7,11 +7,12 @@ class UsersController < AdminController @@ -7,11 +7,12 @@ class UsersController < AdminController
7 format.html 7 format.html
8 format.xml do 8 format.xml do
9 @users = User.find(:all, :conditions => {:environment_id => environment.id}, :include => [:person]) 9 @users = User.find(:all, :conditions => {:environment_id => environment.id}, :include => [:person])
10 - render :xml => @users.to_xml(  
11 - :skip_types => true,  
12 - :only => %w[email login created_at updated_at],  
13 - :include => { :person => {:only => %w[name updated_at created_at address birth_date contact_phone identifier lat lng] } }  
14 - ) 10 + send_data @users.to_xml(
  11 + :skip_types => true,
  12 + :only => %w[email login created_at updated_at],
  13 + :include => { :person => {:only => %w[name updated_at created_at address birth_date contact_phone identifier lat lng] } }),
  14 + :type => 'text/xml',
  15 + :disposition => "attachment; filename=users.xml"
15 end 16 end
16 format.csv do 17 format.csv do
17 @users = User.find(:all, :conditions => {:environment_id => environment.id}, :include => [:person]) 18 @users = User.find(:all, :conditions => {:environment_id => environment.id}, :include => [:person])
app/controllers/application_controller.rb
@@ -101,9 +101,10 @@ class ApplicationController < ActionController::Base @@ -101,9 +101,10 @@ class ApplicationController < ActionController::Base
101 end 101 end
102 end 102 end
103 103
  104 + include Noosfero::Plugin::HotSpot
  105 +
104 def init_noosfero_plugins 106 def init_noosfero_plugins
105 - @plugins = Noosfero::Plugin::Manager.new(self)  
106 - @plugins.each do |plugin| 107 + plugins.each do |plugin|
107 prepend_view_path(plugin.class.view_path) 108 prepend_view_path(plugin.class.view_path)
108 end 109 end
109 init_noosfero_plugins_controller_filters 110 init_noosfero_plugins_controller_filters
@@ -112,8 +113,10 @@ class ApplicationController < ActionController::Base @@ -112,8 +113,10 @@ class ApplicationController < ActionController::Base
112 # This is a generic method that initialize any possible filter defined by a 113 # This is a generic method that initialize any possible filter defined by a
113 # plugin to the current controller being initialized. 114 # plugin to the current controller being initialized.
114 def init_noosfero_plugins_controller_filters 115 def init_noosfero_plugins_controller_filters
115 - @plugins.each do |plugin|  
116 - plugin.send(self.class.name.underscore + '_filters').each do |plugin_filter| 116 + plugins.each do |plugin|
  117 + filters = plugin.send(self.class.name.underscore + '_filters')
  118 + filters = [filters] if !filters.kind_of?(Array)
  119 + filters.each do |plugin_filter|
117 self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {})) 120 self.class.send(plugin_filter[:type], plugin.class.name.underscore + '_' + plugin_filter[:method_name], (plugin_filter[:options] || {}))
118 self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block]) 121 self.class.send(:define_method, plugin.class.name.underscore + '_' + plugin_filter[:method_name], plugin_filter[:block])
119 end 122 end
app/controllers/box_organizer_controller.rb
@@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController @@ -68,7 +68,8 @@ class BoxOrganizerController < ApplicationController
68 raise ArgumentError.new("Type %s is not allowed. Go away." % type) 68 raise ArgumentError.new("Type %s is not allowed. Go away." % type)
69 end 69 end
70 else 70 else
71 - @block_types = available_blocks 71 + @center_block_types = Box.acceptable_center_blocks & available_blocks
  72 + @side_block_types = Box.acceptable_side_blocks & available_blocks
72 @boxes = boxes_holder.boxes 73 @boxes = boxes_holder.boxes
73 render :action => 'add_block', :layout => false 74 render :action => 'add_block', :layout => false
74 end 75 end
app/controllers/my_profile/profile_design_controller.rb
@@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController @@ -5,7 +5,7 @@ class ProfileDesignController < BoxOrganizerController
5 protect 'edit_profile_design', :profile 5 protect 'edit_profile_design', :profile
6 6
7 def available_blocks 7 def available_blocks
8 - blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock ] 8 + blocks = [ ArticleBlock, TagsBlock, RecentDocumentsBlock, ProfileInfoBlock, LinkListBlock, MyNetworkBlock, FeedReaderBlock, ProfileImageBlock, LocationBlock, SlideshowBlock, ProfileSearchBlock, HighlightsBlock ]
9 9
10 # blocks exclusive for organizations 10 # blocks exclusive for organizations
11 if profile.has_members? 11 if profile.has_members?
app/controllers/my_profile/spam_controller.rb 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +class SpamController < MyProfileController
  2 +
  3 + protect :moderate_comments, :profile
  4 +
  5 + def index
  6 + if request.post?
  7 + begin
  8 + # FIXME duplicated logic
  9 + #
  10 + # This logic more or less replicates what is already in
  11 + # ContentViewerController#view_page,
  12 + # ContentViewerController#remove_comment and
  13 + # ContentViewerController#mark_comment_as_spam
  14 + if params[:remove_comment]
  15 + profile.comments_received.find(params[:remove_comment]).destroy
  16 + end
  17 + if params[:mark_comment_as_ham]
  18 + profile.comments_received.find(params[:mark_comment_as_ham]).ham!
  19 + end
  20 + if request.xhr?
  21 + json_response(true)
  22 + else
  23 + redirect_to :action => :index
  24 + end
  25 + rescue
  26 + json_response(false)
  27 + end
  28 + return
  29 + end
  30 +
  31 + @spam = profile.comments_received.spam.paginate({:page => params[:page]})
  32 + end
  33 +
  34 + protected
  35 +
  36 + def json_response(status)
  37 + render :text => {'ok' => status }.to_json, :content_type => 'application/json'
  38 + end
  39 +
  40 +end
app/controllers/public/account_controller.rb
@@ -25,11 +25,13 @@ class AccountController &lt; ApplicationController @@ -25,11 +25,13 @@ class AccountController &lt; ApplicationController
25 25
26 # action to perform login to the application 26 # action to perform login to the application
27 def login 27 def login
28 - @user = User.new  
29 - @person = @user.build_person  
30 store_location(request.referer) unless session[:return_to] 28 store_location(request.referer) unless session[:return_to]
31 return unless request.post? 29 return unless request.post?
32 - self.current_user = User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user] 30 +
  31 + self.current_user = plugins_alternative_authentication
  32 +
  33 + self.current_user ||= User.authenticate(params[:user][:login], params[:user][:password], environment) if params[:user]
  34 +
33 if logged_in? 35 if logged_in?
34 if params[:remember_me] == "1" 36 if params[:remember_me] == "1"
35 self.current_user.remember_me 37 self.current_user.remember_me
@@ -41,7 +43,6 @@ class AccountController &lt; ApplicationController @@ -41,7 +43,6 @@ class AccountController &lt; ApplicationController
41 end 43 end
42 else 44 else
43 session[:notice] = _('Incorrect username or password') if redirect? 45 session[:notice] = _('Incorrect username or password') if redirect?
44 - redirect_to :back if redirect?  
45 end 46 end
46 end 47 end
47 48
@@ -56,6 +57,11 @@ class AccountController &lt; ApplicationController @@ -56,6 +57,11 @@ class AccountController &lt; ApplicationController
56 57
57 # action to register an user to the application 58 # action to register an user to the application
58 def signup 59 def signup
  60 + if @plugins.dispatch(:allow_user_registration).include?(false)
  61 + redirect_back_or_default(:controller => 'home')
  62 + session[:notice] = _("This environment doesn't allow user registration.")
  63 + end
  64 +
59 @invitation_code = params[:invitation_code] 65 @invitation_code = params[:invitation_code]
60 begin 66 begin
61 if params[:user] 67 if params[:user]
@@ -125,6 +131,10 @@ class AccountController &lt; ApplicationController @@ -125,6 +131,10 @@ class AccountController &lt; ApplicationController
125 # 131 #
126 # Posts back. 132 # Posts back.
127 def forgot_password 133 def forgot_password
  134 + if @plugins.dispatch(:allow_password_recovery).include?(false)
  135 + redirect_back_or_default(:controller => 'home')
  136 + session[:notice] = _("This environment doesn't allow password recovery.")
  137 + end
128 @change_password = ChangePassword.new(params[:change_password]) 138 @change_password = ChangePassword.new(params[:change_password])
129 139
130 if request.post? 140 if request.post?
@@ -316,4 +326,13 @@ class AccountController &lt; ApplicationController @@ -316,4 +326,13 @@ class AccountController &lt; ApplicationController
316 end 326 end
317 end 327 end
318 328
  329 + def plugins_alternative_authentication
  330 + user = nil
  331 + @plugins.each do |plugin|
  332 + user = plugin.alternative_authentication
  333 + break unless user.nil?
  334 + end
  335 + user
  336 + end
  337 +
319 end 338 end
app/controllers/public/content_viewer_controller.rb
@@ -2,6 +2,8 @@ class ContentViewerController &lt; ApplicationController @@ -2,6 +2,8 @@ class ContentViewerController &lt; ApplicationController
2 2
3 needs_profile 3 needs_profile
4 4
  5 + before_filter :comment_author, :only => :edit_comment
  6 +
5 helper ProfileHelper 7 helper ProfileHelper
6 helper TagsHelper 8 helper TagsHelper
7 9
@@ -19,7 +21,7 @@ class ContentViewerController &lt; ApplicationController @@ -19,7 +21,7 @@ class ContentViewerController &lt; ApplicationController
19 unless @page 21 unless @page
20 page_from_old_path = profile.articles.find_by_old_path(path) 22 page_from_old_path = profile.articles.find_by_old_path(path)
21 if page_from_old_path 23 if page_from_old_path
22 - redirect_to :profile => profile.identifier, :page => page_from_old_path.explode_path 24 + redirect_to profile.url.merge(:page => page_from_old_path.explode_path)
23 return 25 return
24 end 26 end
25 end 27 end
@@ -75,8 +77,14 @@ class ContentViewerController &lt; ApplicationController @@ -75,8 +77,14 @@ class ContentViewerController &lt; ApplicationController
75 @comment = Comment.new 77 @comment = Comment.new
76 end 78 end
77 79
78 - if request.post? && params[:remove_comment]  
79 - remove_comment 80 + if request.post?
  81 + if params[:remove_comment]
  82 + remove_comment
  83 + return
  84 + elsif params[:mark_comment_as_spam]
  85 + mark_comment_as_spam
  86 + return
  87 + end
80 end 88 end
81 89
82 if @page.has_posts? 90 if @page.has_posts?
@@ -107,23 +115,46 @@ class ContentViewerController &lt; ApplicationController @@ -107,23 +115,46 @@ class ContentViewerController &lt; ApplicationController
107 end 115 end
108 end 116 end
109 117
110 - @comments = @page.comments(true).as_thread  
111 - @comments_count = @page.comments.count 118 + comments = @page.comments.without_spam
  119 + @comments = comments.as_thread
  120 + @comments_count = comments.count
112 if params[:slideshow] 121 if params[:slideshow]
113 render :action => 'slideshow', :layout => 'slideshow' 122 render :action => 'slideshow', :layout => 'slideshow'
114 end 123 end
115 end 124 end
116 125
  126 + def edit_comment
  127 + path = params[:page].join('/')
  128 + @page = profile.articles.find_by_path(path)
  129 + @form_div = 'opened'
  130 + @comment = @page.comments.find_by_id(params[:id])
  131 + if @comment
  132 + if request.post?
  133 + begin
  134 + @comment.update_attributes(params[:comment])
  135 + session[:notice] = _('Comment succesfully updated')
  136 + redirect_to :action => 'view_page', :profile => profile.identifier, :page => @comment.article.explode_path
  137 + rescue
  138 + session[:notice] = _('Comment could not be updated')
  139 + end
  140 + end
  141 + else
  142 + redirect_to @page.view_url
  143 + session[:notice] = _('Could not find the comment in the article')
  144 + end
  145 + end
  146 +
117 protected 147 protected
118 148
119 def add_comment 149 def add_comment
120 @comment.author = user if logged_in? 150 @comment.author = user if logged_in?
121 @comment.article = @page 151 @comment.article = @page
122 @comment.ip_address = request.remote_ip 152 @comment.ip_address = request.remote_ip
  153 + @comment.user_agent = request.user_agent
  154 + @comment.referrer = request.referrer
123 plugins_filter_comment(@comment) 155 plugins_filter_comment(@comment)
124 return if @comment.rejected? 156 return if @comment.rejected?
125 if (pass_without_comment_captcha? || verify_recaptcha(:model => @comment, :message => _('Please type the words correctly'))) && @comment.save 157 if (pass_without_comment_captcha? || verify_recaptcha(:model => @comment, :message => _('Please type the words correctly'))) && @comment.save
126 - plugins_comment_saved(@comment)  
127 @page.touch 158 @page.touch
128 @comment = nil # clear the comment form 159 @comment = nil # clear the comment form
129 redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view] 160 redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]
@@ -138,12 +169,6 @@ class ContentViewerController &lt; ApplicationController @@ -138,12 +169,6 @@ class ContentViewerController &lt; ApplicationController
138 end 169 end
139 end 170 end
140 171
141 - def plugins_comment_saved(comment)  
142 - @plugins.each do |plugin|  
143 - plugin.comment_saved(comment)  
144 - end  
145 - end  
146 -  
147 def pass_without_comment_captcha? 172 def pass_without_comment_captcha?
148 logged_in? && !environment.enabled?('captcha_for_logged_users') 173 logged_in? && !environment.enabled?('captcha_for_logged_users')
149 end 174 end
@@ -153,9 +178,24 @@ class ContentViewerController &lt; ApplicationController @@ -153,9 +178,24 @@ class ContentViewerController &lt; ApplicationController
153 @comment = @page.comments.find(params[:remove_comment]) 178 @comment = @page.comments.find(params[:remove_comment])
154 if (user == @comment.author || user == @page.profile || user.has_permission?(:moderate_comments, @page.profile)) 179 if (user == @comment.author || user == @page.profile || user.has_permission?(:moderate_comments, @page.profile))
155 @comment.destroy 180 @comment.destroy
156 - session[:notice] = _('Comment succesfully deleted')  
157 end 181 end
158 - redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view] 182 + finish_comment_handling
  183 + end
  184 +
  185 + def mark_comment_as_spam
  186 + @comment = @page.comments.find(params[:mark_comment_as_spam])
  187 + if logged_in? && (user == @page.profile || user.has_permission?(:moderate_comments, @page.profile))
  188 + @comment.spam!
  189 + end
  190 + finish_comment_handling
  191 + end
  192 +
  193 + def finish_comment_handling
  194 + if request.xhr?
  195 + render :text => {'ok' => true}.to_json, :content_type => 'application/json'
  196 + else
  197 + redirect_to :action => 'view_page', :profile => params[:profile], :page => @page.explode_path, :view => params[:view]
  198 + end
159 end 199 end
160 200
161 def per_page 201 def per_page
@@ -181,4 +221,13 @@ class ContentViewerController &lt; ApplicationController @@ -181,4 +221,13 @@ class ContentViewerController &lt; ApplicationController
181 end 221 end
182 end 222 end
183 223
  224 + def comment_author
  225 + comment = Comment.find_by_id(params[:id])
  226 + if comment
  227 + render_access_denied if comment.author.blank? || comment.author != user
  228 + else
  229 + render_not_found
  230 + end
  231 + end
  232 +
184 end 233 end
app/controllers/public/profile_controller.rb
@@ -212,9 +212,9 @@ class ProfileController &lt; PublicController @@ -212,9 +212,9 @@ class ProfileController &lt; PublicController
212 begin 212 begin
213 scrap = current_user.person.scraps(params[:scrap_id]) 213 scrap = current_user.person.scraps(params[:scrap_id])
214 scrap.destroy 214 scrap.destroy
215 - render :text => _('Scrap successfully removed.') 215 + finish_successful_removal 'Scrap successfully removed.'
216 rescue 216 rescue
217 - render :text => _('You could not remove this scrap') 217 + finish_unsuccessful_removal 'You could not remove this scrap.'
218 end 218 end
219 end 219 end
220 220
@@ -227,9 +227,9 @@ class ProfileController &lt; PublicController @@ -227,9 +227,9 @@ class ProfileController &lt; PublicController
227 else 227 else
228 activity.destroy 228 activity.destroy
229 end 229 end
230 - render :text => _('Activity successfully removed.') 230 + finish_successful_removal 'Activity successfully removed.'
231 rescue 231 rescue
232 - render :text => _('You could not remove this activity') 232 + finish_unsuccessful_removal 'You could not remove this activity.'
233 end 233 end
234 end 234 end
235 235
@@ -244,6 +244,24 @@ class ProfileController &lt; PublicController @@ -244,6 +244,24 @@ class ProfileController &lt; PublicController
244 end 244 end
245 end 245 end
246 246
  247 + def finish_successful_removal(msg)
  248 + if request.xhr?
  249 + render :text => {'ok' => true}.to_json, :content_type => 'application/json'
  250 + else
  251 + session[:notice] = _(msg)
  252 + redirect_to :action => :index
  253 + end
  254 + end
  255 +
  256 + def finish_unsuccessful_removal(msg)
  257 + session[:notice] = _(msg)
  258 + if request.xhr?
  259 + render :text => {'redirect' => url_for(:action => :index)}.to_json, :content_type => 'application/json'
  260 + else
  261 + redirect_to :action => :index
  262 + end
  263 + end
  264 +
247 def profile_info 265 def profile_info
248 begin 266 begin
249 @block = profile.blocks.find(params[:block_id]) 267 @block = profile.blocks.find(params[:block_id])
@@ -303,9 +321,10 @@ class ProfileController &lt; PublicController @@ -303,9 +321,10 @@ class ProfileController &lt; PublicController
303 @comment = Comment.find(params[:comment_id]) 321 @comment = Comment.find(params[:comment_id])
304 if (user == @comment.author || user == profile || user.has_permission?(:moderate_comments, profile)) 322 if (user == @comment.author || user == profile || user.has_permission?(:moderate_comments, profile))
305 @comment.destroy 323 @comment.destroy
306 - session[:notice] = _('Comment successfully deleted') 324 + finish_successful_removal 'Comment successfully removed.'
  325 + else
  326 + finish_unsuccessful_removal 'You could not remove this comment.'
307 end 327 end
308 - redirect_to :action => :index  
309 end 328 end
310 329
311 protected 330 protected
app/controllers/public/search_controller.rb
@@ -4,10 +4,17 @@ class SearchController &lt; PublicController @@ -4,10 +4,17 @@ class SearchController &lt; PublicController
4 include SearchHelper 4 include SearchHelper
5 include ActionView::Helpers::NumberHelper 5 include ActionView::Helpers::NumberHelper
6 6
  7 + before_filter :redirect_asset_param, :except => [:facets_browse, :assets]
7 before_filter :load_category 8 before_filter :load_category
8 before_filter :load_search_assets 9 before_filter :load_search_assets
9 before_filter :load_query 10 before_filter :load_query
10 11
  12 + # Backwards compatibility with old URLs
  13 + def redirect_asset_param
  14 + return unless params.has_key?(:asset)
  15 + redirect_to params.merge(:action => params.delete(:asset))
  16 + end
  17 +
11 no_design_blocks 18 no_design_blocks
12 19
13 def facets_browse 20 def facets_browse
@@ -250,10 +257,9 @@ class SearchController &lt; PublicController @@ -250,10 +257,9 @@ class SearchController &lt; PublicController
250 end 257 end
251 258
252 def limit 259 def limit
253 - searching = @searching.values.select{ |v| v }  
254 - if params[:display] == 'map' 260 + if map_search?
255 MAP_SEARCH_LIMIT 261 MAP_SEARCH_LIMIT
256 - elsif searching.size <= 1 262 + elsif !multiple_search?
257 if [:people, :communities].include? @asset 263 if [:people, :communities].include? @asset
258 BLOCKS_SEARCH_LIMIT 264 BLOCKS_SEARCH_LIMIT
259 elsif @asset == :enterprises and @empty_query 265 elsif @asset == :enterprises and @empty_query
@@ -267,31 +273,34 @@ class SearchController &lt; PublicController @@ -267,31 +273,34 @@ class SearchController &lt; PublicController
267 end 273 end
268 274
269 def paginate_options(page = params[:page]) 275 def paginate_options(page = params[:page])
  276 + page = 1 if multiple_search? or params[:display] == 'map'
270 { :per_page => limit, :page => page } 277 { :per_page => limit, :page => page }
271 end 278 end
272 279
273 def full_text_search(filters = [], options = {}) 280 def full_text_search(filters = [], options = {})
274 paginate_options = paginate_options(params[:page]) 281 paginate_options = paginate_options(params[:page])
275 asset_class = asset_class(@asset) 282 asset_class = asset_class(@asset)
276 -  
277 solr_options = options 283 solr_options = options
278 - if !@results_only and asset_class.respond_to? :facets  
279 - solr_options.merge! asset_class.facets_find_options(params[:facet])  
280 - solr_options[:all_facets] = true  
281 - solr_options[:limit] = 0 if @facets_only  
282 - end  
283 - solr_options[:filter_queries] ||= []  
284 - solr_options[:filter_queries] += filters  
285 - solr_options[:filter_queries] << "environment_id:#{environment.id}"  
286 - solr_options[:filter_queries] << asset_class.facet_category_query.call(@category) if @category  
287 -  
288 - solr_options[:boost_functions] ||= []  
289 - params[:order_by] = nil if params[:order_by] == 'none'  
290 - if params[:order_by]  
291 - order = SortOptions[@asset][params[:order_by].to_sym]  
292 - raise "Unknown order by" if order.nil?  
293 - order[:solr_opts].each do |opt, value|  
294 - solr_options[opt] = value.is_a?(Proc) ? instance_eval(&value) : value 284 + pg_options = paginate_options(params[:page])
  285 +
  286 + if !multiple_search?
  287 + if !@results_only and asset_class.respond_to? :facets
  288 + solr_options.merge! asset_class.facets_find_options(params[:facet])
  289 + solr_options[:all_facets] = true
  290 + end
  291 + solr_options[:filter_queries] ||= []
  292 + solr_options[:filter_queries] += filters
  293 + solr_options[:filter_queries] << "environment_id:#{environment.id}"
  294 + solr_options[:filter_queries] << asset_class.facet_category_query.call(@category) if @category
  295 +
  296 + solr_options[:boost_functions] ||= []
  297 + params[:order_by] = nil if params[:order_by] == 'none'
  298 + if params[:order_by]
  299 + order = SortOptions[@asset][params[:order_by].to_sym]
  300 + raise "Unknown order by" if order.nil?
  301 + order[:solr_opts].each do |opt, value|
  302 + solr_options[opt] = value.is_a?(Proc) ? instance_eval(&value) : value
  303 + end
295 end 304 end
296 end 305 end
297 306
app/helpers/application_helper.rb
@@ -265,9 +265,9 @@ module ApplicationHelper @@ -265,9 +265,9 @@ module ApplicationHelper
265 265
266 VIEW_EXTENSIONS = %w[.rhtml .html.erb] 266 VIEW_EXTENSIONS = %w[.rhtml .html.erb]
267 267
268 - def partial_for_class_in_view_path(klass, view_path) 268 + def partial_for_class_in_view_path(klass, view_path, suffix = nil)
269 return nil if klass.nil? 269 return nil if klass.nil?
270 - name = klass.name.underscore 270 + name = [klass.name.underscore, suffix].compact.map(&:to_s).join('_')
271 271
272 search_name = String.new(name) 272 search_name = String.new(name)
273 if search_name.include?("/") 273 if search_name.include?("/")
@@ -285,28 +285,17 @@ module ApplicationHelper @@ -285,28 +285,17 @@ module ApplicationHelper
285 partial_for_class_in_view_path(klass.superclass, view_path) 285 partial_for_class_in_view_path(klass.superclass, view_path)
286 end 286 end
287 287
288 - def partial_for_class(klass) 288 + def partial_for_class(klass, suffix=nil)
289 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? 289 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?
290 name = klass.name.underscore 290 name = klass.name.underscore
291 @controller.view_paths.each do |view_path| 291 @controller.view_paths.each do |view_path|
292 - partial = partial_for_class_in_view_path(klass, view_path) 292 + partial = partial_for_class_in_view_path(klass, view_path, suffix)
293 return partial if partial 293 return partial if partial
294 end 294 end
295 295
296 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' 296 raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?'
297 end 297 end
298 298
299 - def partial_for_task_class(klass, action)  
300 - raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil?  
301 -  
302 - name = "#{klass.name.underscore}_#{action.to_s}"  
303 - VIEW_EXTENSIONS.each do |ext|  
304 - return name if File.exists?(File.join(RAILS_ROOT, 'app', 'views', params[:controller], '_'+name+ext))  
305 - end  
306 -  
307 - partial_for_task_class(klass.superclass, action)  
308 - end  
309 -  
310 def view_for_profile_actions(klass) 299 def view_for_profile_actions(klass)
311 raise ArgumentError, 'No profile actions view for this class.' if klass.nil? 300 raise ArgumentError, 'No profile actions view for this class.' if klass.nil?
312 301
@@ -1336,6 +1325,19 @@ module ApplicationHelper @@ -1336,6 +1325,19 @@ module ApplicationHelper
1336 end 1325 end
1337 end 1326 end
1338 1327
  1328 + def expirable_link_to(expired, content, url, options = {})
  1329 + if expired
  1330 + options[:class] = (options[:class] || '') + ' disabled'
  1331 + content_tag('a', '&nbsp;'+content_tag('span', content), options)
  1332 + else
  1333 + link_to content, url, options
  1334 + end
  1335 + end
  1336 +
  1337 + def remove_content_button(action)
  1338 + @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true)
  1339 + end
  1340 +
1339 def template_options(klass, field_name) 1341 def template_options(klass, field_name)
1340 return '' if klass.templates.count == 0 1342 return '' if klass.templates.count == 0
1341 return hidden_field_tag("#{field_name}[template_id]", klass.templates.first.id) if klass.templates.count == 1 1343 return hidden_field_tag("#{field_name}[template_id]", klass.templates.first.id) if klass.templates.count == 1
@@ -1401,4 +1403,19 @@ module ApplicationHelper @@ -1401,4 +1403,19 @@ module ApplicationHelper
1401 result 1403 result
1402 end 1404 end
1403 1405
  1406 + def expirable_content_reference(content, action, text, url, options = {})
  1407 + reason = @plugins.dispatch("content_expire_#{action.to_s}", content).first
  1408 + options[:title] = reason
  1409 + expirable_link_to reason.present?, text, url, options
  1410 + end
  1411 +
  1412 + def expirable_button(content, action, text, url, options = {})
  1413 + options[:class] = "button with-text icon-#{action.to_s}"
  1414 + expirable_content_reference content, action, text, url, options
  1415 + end
  1416 +
  1417 + def expirable_comment_link(content, action, text, url, options = {})
  1418 + options[:class] = "comment-footer comment-footer-link comment-footer-hide"
  1419 + expirable_content_reference content, action, text, url, options
  1420 + end
1404 end 1421 end
app/helpers/boxes_helper.rb
@@ -162,9 +162,6 @@ module BoxesHelper @@ -162,9 +162,6 @@ module BoxesHelper
162 # 162 #
163 # +box+ is always needed 163 # +box+ is always needed
164 def block_target(box, block = nil) 164 def block_target(box, block = nil)
165 - # FIXME hardcoded  
166 - return '' if box.position == 1  
167 -  
168 id = 165 id =
169 if block.nil? 166 if block.nil?
170 "end-of-box-#{box.id}" 167 "end-of-box-#{box.id}"
@@ -172,14 +169,11 @@ module BoxesHelper @@ -172,14 +169,11 @@ module BoxesHelper
172 "before-block-#{block.id}" 169 "before-block-#{block.id}"
173 end 170 end
174 171
175 - content_tag('div', '&nbsp;', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => 'block', :hoverclass => 'block-target-hover') 172 + content_tag('div', '&nbsp;', :id => id, :class => 'block-target' ) + drop_receiving_element(id, :url => { :action => 'move_block', :target => id }, :accept => box.acceptable_blocks, :hoverclass => 'block-target-hover')
176 end 173 end
177 174
178 # makes the given block draggable so it can be moved away. 175 # makes the given block draggable so it can be moved away.
179 def block_handle(block) 176 def block_handle(block)
180 - # FIXME hardcoded  
181 - return '' if block.box.position == 1  
182 -  
183 draggable_element("block-#{block.id}", :revert => true) 177 draggable_element("block-#{block.id}", :revert => true)
184 end 178 end
185 179
@@ -211,7 +205,7 @@ module BoxesHelper @@ -211,7 +205,7 @@ module BoxesHelper
211 end 205 end
212 206
213 if block.editable? 207 if block.editable?
214 - buttons << lightbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id }) 208 + buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
215 end 209 end
216 210
217 if !block.main? 211 if !block.main?
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/colorbox_helper.rb
@@ -8,6 +8,10 @@ module ColorboxHelper @@ -8,6 +8,10 @@ module ColorboxHelper
8 button(type, label, url, colorbox_options(options)) 8 button(type, label, url, colorbox_options(options))
9 end 9 end
10 10
  11 + def colorbox_icon_button(type, label, url, options = {})
  12 + icon_button(type, label, url, colorbox_options(options))
  13 + end
  14 +
11 # options must be an HTML options hash as passed to link_to etc. 15 # options must be an HTML options hash as passed to link_to etc.
12 # 16 #
13 # returns a new hash with colorbox class added. Keeps existing classes. 17 # returns a new hash with colorbox class added. Keeps existing classes.
app/helpers/content_viewer_helper.rb
@@ -4,11 +4,11 @@ module ContentViewerHelper @@ -4,11 +4,11 @@ module ContentViewerHelper
4 include ForumHelper 4 include ForumHelper
5 5
6 def number_of_comments(article) 6 def number_of_comments(article)
7 - n = article.comments.size 7 + n = article.comments.without_spam.count
8 if n == 0 8 if n == 0
9 _('No comments yet') 9 _('No comments yet')
10 else 10 else
11 - n_('One comment', '%{comments} comments', n) % { :comments => n } 11 + n_('One comment', '<span class="comment-count">%{comments}</span> comments', n) % { :comments => n }
12 end 12 end
13 end 13 end
14 14
app/helpers/forms_helper.rb
@@ -142,6 +142,119 @@ module FormsHelper @@ -142,6 +142,119 @@ module FormsHelper
142 content_tag('table',rows.join("\n")) 142 content_tag('table',rows.join("\n"))
143 end 143 end
144 144
  145 + def date_field(name, value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  146 + datepicker_options[:disabled] ||= false
  147 + datepicker_options[:alt_field] ||= ''
  148 + datepicker_options[:alt_format] ||= ''
  149 + datepicker_options[:append_text] ||= ''
  150 + datepicker_options[:auto_size] ||= false
  151 + datepicker_options[:button_image] ||= ''
  152 + datepicker_options[:button_image_only] ||= false
  153 + datepicker_options[:button_text] ||= '...'
  154 + datepicker_options[:calculate_week] ||= 'jQuery.datepicker.iso8601Week'
  155 + datepicker_options[:change_month] ||= false
  156 + datepicker_options[:change_year] ||= false
  157 + datepicker_options[:close_text] ||= _('Done')
  158 + datepicker_options[:constrain_input] ||= true
  159 + datepicker_options[:current_text] ||= _('Today')
  160 + datepicker_options[:date_format] ||= 'mm/dd/yy'
  161 + datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')]
  162 + datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')]
  163 + datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')]
  164 + datepicker_options[:default_date] ||= nil
  165 + datepicker_options[:duration] ||= 'normal'
  166 + datepicker_options[:first_day] ||= 0
  167 + datepicker_options[:goto_current] ||= false
  168 + datepicker_options[:hide_if_no_prev_next] ||= false
  169 + datepicker_options[:is_rtl] ||= false
  170 + datepicker_options[:max_date] ||= nil
  171 + datepicker_options[:min_date] ||= nil
  172 + datepicker_options[:month_names] ||= [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')]
  173 + datepicker_options[:month_names_short] ||= [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')]
  174 + datepicker_options[:navigation_as_date_format] ||= false
  175 + datepicker_options[:next_text] ||= _('Next')
  176 + datepicker_options[:number_of_months] ||= 1
  177 + datepicker_options[:prev_text] ||= _('Prev')
  178 + datepicker_options[:select_other_months] ||= false
  179 + datepicker_options[:short_year_cutoff] ||= '+10'
  180 + datepicker_options[:show_button_panel] ||= false
  181 + datepicker_options[:show_current_at_pos] ||= 0
  182 + datepicker_options[:show_month_after_year] ||= false
  183 + datepicker_options[:show_on] ||= 'focus'
  184 + datepicker_options[:show_options] ||= {}
  185 + datepicker_options[:show_other_months] ||= false
  186 + datepicker_options[:show_week] ||= false
  187 + datepicker_options[:step_months] ||= 1
  188 + datepicker_options[:week_header] ||= _('Wk')
  189 + datepicker_options[:year_range] ||= 'c-10:c+10'
  190 + datepicker_options[:year_suffix] ||= ''
  191 +
  192 + element_id = html_options[:id] || 'datepicker-date'
  193 + value = value.strftime(format) if value.present?
  194 + method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker'
  195 + result = text_field_tag(name, value, html_options)
  196 + result +=
  197 + "
  198 + <script type='text/javascript'>
  199 + jQuery('##{element_id}').#{method}({
  200 + disabled: #{datepicker_options[:disabled].to_json},
  201 + altField: #{datepicker_options[:alt_field].to_json},
  202 + altFormat: #{datepicker_options[:alt_format].to_json},
  203 + appendText: #{datepicker_options[:append_text].to_json},
  204 + autoSize: #{datepicker_options[:auto_size].to_json},
  205 + buttonImage: #{datepicker_options[:button_image].to_json},
  206 + buttonImageOnly: #{datepicker_options[:button_image_only].to_json},
  207 + buttonText: #{datepicker_options[:button_text].to_json},
  208 + calculateWeek: #{datepicker_options[:calculate_week].to_json},
  209 + changeMonth: #{datepicker_options[:change_month].to_json},
  210 + changeYear: #{datepicker_options[:change_year].to_json},
  211 + closeText: #{datepicker_options[:close_text].to_json},
  212 + constrainInput: #{datepicker_options[:constrain_input].to_json},
  213 + currentText: #{datepicker_options[:current_text].to_json},
  214 + dateFormat: #{datepicker_options[:date_format].to_json},
  215 + dayNames: #{datepicker_options[:day_names].to_json},
  216 + dayNamesMin: #{datepicker_options[:day_names_min].to_json},
  217 + dayNamesShort: #{datepicker_options[:day_names_short].to_json},
  218 + defaultDate: #{datepicker_options[:default_date].to_json},
  219 + duration: #{datepicker_options[:duration].to_json},
  220 + firstDay: #{datepicker_options[:first_day].to_json},
  221 + gotoCurrent: #{datepicker_options[:goto_current].to_json},
  222 + hideIfNoPrevNext: #{datepicker_options[:hide_if_no_prev_next].to_json},
  223 + isRTL: #{datepicker_options[:is_rtl].to_json},
  224 + maxDate: #{datepicker_options[:max_date].to_json},
  225 + minDate: #{datepicker_options[:min_date].to_json},
  226 + monthNames: #{datepicker_options[:month_names].to_json},
  227 + monthNamesShort: #{datepicker_options[:month_names_short].to_json},
  228 + navigationAsDateFormat: #{datepicker_options[:navigation_as_date_format].to_json},
  229 + nextText: #{datepicker_options[:next_text].to_json},
  230 + numberOfMonths: #{datepicker_options[:number_of_months].to_json},
  231 + prevText: #{datepicker_options[:prev_text].to_json},
  232 + selectOtherMonths: #{datepicker_options[:select_other_months].to_json},
  233 + shortYearCutoff: #{datepicker_options[:short_year_cutoff].to_json},
  234 + showButtonPanel: #{datepicker_options[:show_button_panel].to_json},
  235 + showCurrentAtPos: #{datepicker_options[:show_current_at_pos].to_json},
  236 + showMonthAfterYear: #{datepicker_options[:show_month_after_year].to_json},
  237 + showOn: #{datepicker_options[:show_on].to_json},
  238 + showOptions: #{datepicker_options[:show_options].to_json},
  239 + showOtherMonths: #{datepicker_options[:show_other_months].to_json},
  240 + showWeek: #{datepicker_options[:show_week].to_json},
  241 + stepMonths: #{datepicker_options[:step_months].to_json},
  242 + weekHeader: #{datepicker_options[:week_header].to_json},
  243 + yearRange: #{datepicker_options[:year_range].to_json},
  244 + yearSuffix: #{datepicker_options[:year_suffix].to_json}
  245 + })
  246 + </script>
  247 + "
  248 + result
  249 + end
  250 +
  251 + def date_range_field(from_name, to_name, from_value, to_value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {})
  252 + from_id = html_options[:from_id] || 'datepicker-from-date'
  253 + to_id = html_options[:to_id] || 'datepicker-to-date'
  254 + return _('From') +' '+ date_field(from_name, from_value, format, datepicker_options, html_options.merge({:id => from_id})) +
  255 + ' ' + _('until') +' '+ date_field(to_name, to_value, format, datepicker_options, html_options.merge({:id => to_id}))
  256 + end
  257 +
145 protected 258 protected
146 def self.next_id_number 259 def self.next_id_number
147 if defined? @@id_num 260 if defined? @@id_num
app/helpers/search_helper.rb
@@ -45,6 +45,14 @@ module SearchHelper @@ -45,6 +45,14 @@ module SearchHelper
45 # FIXME remove it after search_controler refactored 45 # FIXME remove it after search_controler refactored
46 include EventsHelper 46 include EventsHelper
47 47
  48 + def multiple_search?
  49 + ['index', 'category_index'].include?(params[:action]) or @results.size > 1
  50 + end
  51 +
  52 + def map_search?
  53 + !@empty_query and !multiple_search? and params[:display] == 'map'
  54 + end
  55 +
48 def search_page_title(title, category = nil) 56 def search_page_title(title, category = nil)
49 title = "<h1>" + title 57 title = "<h1>" + title
50 title += '<small>' + category.name + '</small>' if category 58 title += '<small>' + category.name + '</small>' if category
@@ -58,8 +66,8 @@ module SearchHelper @@ -58,8 +66,8 @@ module SearchHelper
58 :align => 'center', :class => 'search-category-context') if category 66 :align => 'center', :class => 'search-category-context') if category
59 end 67 end
60 68
61 - def display_results(use_map = false)  
62 - if params[:display] == 'map' && use_map 69 + def display_results(map_capable = false)
  70 + if map_capable and map_search?
63 partial = 'google_maps' 71 partial = 'google_maps'
64 klass = 'map' 72 klass = 'map'
65 else 73 else
@@ -99,7 +107,7 @@ module SearchHelper @@ -99,7 +107,7 @@ module SearchHelper
99 @asset_class = asset_class(asset) 107 @asset_class = asset_class(asset)
100 render(:partial => 'facets_unselect_menu') 108 render(:partial => 'facets_unselect_menu')
101 end 109 end
102 - 110 +
103 def facet_javascript(input_id, facet, array) 111 def facet_javascript(input_id, facet, array)
104 array = [] if array.nil? 112 array = [] if array.nil?
105 hintText = _('Type in an option') 113 hintText = _('Type in an option')
@@ -148,6 +156,7 @@ module SearchHelper @@ -148,6 +156,7 @@ module SearchHelper
148 params = params.dup 156 params = params.dup
149 params[:facet].each do |id, value| 157 params[:facet].each do |id, value|
150 facet = klass.facet_by_id(id.to_sym) 158 facet = klass.facet_by_id(id.to_sym)
  159 + next unless facet
151 if value.kind_of?(Hash) 160 if value.kind_of?(Hash)
152 label_hash = facet[:label].call(environment) 161 label_hash = facet[:label].call(environment)
153 value.each do |label_id, value| 162 value.each do |label_id, value|
app/models/box.rb
@@ -6,4 +6,76 @@ class Box &lt; ActiveRecord::Base @@ -6,4 +6,76 @@ class Box &lt; ActiveRecord::Base
6 def environment 6 def environment
7 owner ? (owner.kind_of?(Environment) ? owner : owner.environment) : nil 7 owner ? (owner.kind_of?(Environment) ? owner : owner.environment) : nil
8 end 8 end
  9 +
  10 + def acceptable_blocks
  11 + to_css_class_name central? ? Box.acceptable_center_blocks : Box.acceptable_side_blocks
  12 + end
  13 +
  14 + def central?
  15 + position == 1
  16 + end
  17 +
  18 + def self.acceptable_center_blocks
  19 + [ ArticleBlock,
  20 + BlogArchivesBlock,
  21 + CategoriesBlock,
  22 + CommunitiesBlock,
  23 + EnterprisesBlock,
  24 + EnvironmentStatisticsBlock,
  25 + FansBlock,
  26 + FavoriteEnterprisesBlock,
  27 + FeedReaderBlock,
  28 + FriendsBlock,
  29 + HighlightsBlock,
  30 + LinkListBlock,
  31 + LoginBlock,
  32 + MainBlock,
  33 + MembersBlock,
  34 + MyNetworkBlock,
  35 + PeopleBlock,
  36 + ProfileImageBlock,
  37 + RawHTMLBlock,
  38 + RecentDocumentsBlock,
  39 + SellersSearchBlock,
  40 + TagsBlock ]
  41 + end
  42 +
  43 + def self.acceptable_side_blocks
  44 + [ ArticleBlock,
  45 + BlogArchivesBlock,
  46 + CategoriesBlock,
  47 + CommunitiesBlock,
  48 + DisabledEnterpriseMessageBlock,
  49 + EnterprisesBlock,
  50 + EnvironmentStatisticsBlock,
  51 + FansBlock,
  52 + FavoriteEnterprisesBlock,
  53 + FeaturedProductsBlock,
  54 + FeedReaderBlock,
  55 + FriendsBlock,
  56 + HighlightsBlock,
  57 + LinkListBlock,
  58 + LocationBlock,
  59 + LoginBlock,
  60 + MembersBlock,
  61 + MyNetworkBlock,
  62 + PeopleBlock,
  63 + ProductsBlock,
  64 + ProfileImageBlock,
  65 + ProfileInfoBlock,
  66 + ProfileSearchBlock,
  67 + RawHTMLBlock,
  68 + RecentDocumentsBlock,
  69 + SellersSearchBlock,
  70 + SlideshowBlock,
  71 + TagsBlock
  72 + ]
  73 + end
  74 +
  75 + private
  76 +
  77 + def to_css_class_name(blocks)
  78 + blocks.map{ |block| block.to_s.underscore.tr('_', '-') }
  79 + end
  80 +
9 end 81 end
app/models/comment.rb
@@ -10,6 +10,9 @@ class Comment &lt; ActiveRecord::Base @@ -10,6 +10,9 @@ class Comment &lt; ActiveRecord::Base
10 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy 10 has_many :children, :class_name => 'Comment', :foreign_key => 'reply_of_id', :dependent => :destroy
11 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id' 11 belongs_to :reply_of, :class_name => 'Comment', :foreign_key => 'reply_of_id'
12 12
  13 + named_scope :without_spam, :conditions => ['spam IS NULL OR spam = ?', false]
  14 + named_scope :spam, :conditions => ['spam = ?', true]
  15 +
13 # unauthenticated authors: 16 # unauthenticated authors:
14 validates_presence_of :name, :if => (lambda { |record| !record.email.blank? }) 17 validates_presence_of :name, :if => (lambda { |record| !record.email.blank? })
15 validates_presence_of :email, :if => (lambda { |record| !record.name.blank? }) 18 validates_presence_of :email, :if => (lambda { |record| !record.name.blank? })
@@ -25,6 +28,8 @@ class Comment &lt; ActiveRecord::Base @@ -25,6 +28,8 @@ class Comment &lt; ActiveRecord::Base
25 28
26 xss_terminate :only => [ :body, :title, :name ], :on => 'validation' 29 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
27 30
  31 + delegate :environment, :to => :source
  32 +
28 def action_tracker_target 33 def action_tracker_target
29 self.article.profile 34 self.article.profile
30 end 35 end
@@ -85,7 +90,28 @@ class Comment &lt; ActiveRecord::Base @@ -85,7 +90,28 @@ class Comment &lt; ActiveRecord::Base
85 end 90 end
86 end 91 end
87 92
88 - after_create :notify_by_mail 93 + after_create :schedule_notification
  94 +
  95 + def schedule_notification
  96 + Delayed::Job.enqueue CommentHandler.new(self.id, :verify_and_notify)
  97 + end
  98 +
  99 + delegate :environment, :to => :profile
  100 + delegate :profile, :to => :source
  101 +
  102 + include Noosfero::Plugin::HotSpot
  103 +
  104 + def verify_and_notify
  105 + check_for_spam
  106 + unless spam?
  107 + notify_by_mail
  108 + end
  109 + end
  110 +
  111 + def check_for_spam
  112 + plugins.dispatch(:check_comment_for_spam, self)
  113 + end
  114 +
89 def notify_by_mail 115 def notify_by_mail
90 if source.kind_of?(Article) && article.notify_comments? 116 if source.kind_of?(Article) && article.notify_comments?
91 if !article.profile.notification_emails.empty? 117 if !article.profile.notification_emails.empty?
@@ -123,10 +149,14 @@ class Comment &lt; ActiveRecord::Base @@ -123,10 +149,14 @@ class Comment &lt; ActiveRecord::Base
123 def self.as_thread 149 def self.as_thread
124 result = {} 150 result = {}
125 root = [] 151 root = []
126 - all.each do |c| 152 + order(:id).each do |c|
127 c.replies = [] 153 c.replies = []
128 result[c.id] ||= c 154 result[c.id] ||= c
129 - c.reply_of_id.nil? ? root << c : result[c.reply_of_id].replies << c 155 + if result[c.reply_of_id]
  156 + result[c.reply_of_id].replies << c
  157 + else
  158 + root << c
  159 + end
130 end 160 end
131 root 161 root
132 end 162 end
@@ -183,4 +213,34 @@ class Comment &lt; ActiveRecord::Base @@ -183,4 +213,34 @@ class Comment &lt; ActiveRecord::Base
183 @rejected = true 213 @rejected = true
184 end 214 end
185 215
  216 + def spam?
  217 + !spam.nil? && spam
  218 + end
  219 +
  220 + def ham?
  221 + !spam.nil? && !spam
  222 + end
  223 +
  224 + def spam!
  225 + self.spam = true
  226 + self.save!
  227 + Delayed::Job.enqueue(CommentHandler.new(self.id, :marked_as_spam))
  228 + self
  229 + end
  230 +
  231 + def ham!
  232 + self.spam = false
  233 + self.save!
  234 + Delayed::Job.enqueue(CommentHandler.new(self.id, :marked_as_ham))
  235 + self
  236 + end
  237 +
  238 + def marked_as_spam
  239 + plugins.dispatch(:comment_marked_as_spam, self)
  240 + end
  241 +
  242 + def marked_as_ham
  243 + plugins.dispatch(:comment_marked_as_ham, self)
  244 + end
  245 +
186 end 246 end
app/models/comment_handler.rb 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +class CommentHandler < Struct.new(:comment_id, :method)
  2 +
  3 + def perform
  4 + comment = Comment.find(comment_id)
  5 + comment.send(method)
  6 + rescue ActiveRecord::RecordNotFound
  7 + # just ignore non-existing comments
  8 + end
  9 +
  10 +end
app/models/community.rb
@@ -88,7 +88,7 @@ class Community &lt; Organization @@ -88,7 +88,7 @@ class Community &lt; Organization
88 end 88 end
89 89
90 def activities 90 def activities
91 - Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.target_id = #{self.id} UNION SELECT at.id, at.updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} at INNER JOIN articles a ON at.target_id = a.id WHERE a.profile_id = #{self.id} AND at.target_type = 'Article' ORDER BY updated_at DESC") 91 + Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.target_id = #{self.id} and action_tracker.verb != 'join_community' and action_tracker.verb != 'leave_scrap' UNION SELECT at.id, at.updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} at INNER JOIN articles a ON at.target_id = a.id WHERE a.profile_id = #{self.id} AND at.target_type = 'Article' ORDER BY updated_at DESC")
92 end 92 end
93 93
94 end 94 end
app/models/person.rb
@@ -22,8 +22,6 @@ class Person &lt; Profile @@ -22,8 +22,6 @@ class Person &lt; Profile
22 super 22 super
23 end 23 end
24 24
25 - acts_as_having_hotspots  
26 -  
27 named_scope :members_of, lambda { |resources| 25 named_scope :members_of, lambda { |resources|
28 resources = [resources] if !resources.kind_of?(Array) 26 resources = [resources] if !resources.kind_of?(Array)
29 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ') 27 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
@@ -32,7 +30,7 @@ class Person &lt; Profile @@ -32,7 +30,7 @@ class Person &lt; Profile
32 30
33 def has_permission_with_plugins?(permission, profile) 31 def has_permission_with_plugins?(permission, profile)
34 permissions = [has_permission_without_plugins?(permission, profile)] 32 permissions = [has_permission_without_plugins?(permission, profile)]
35 - permissions += enabled_plugins.map do |plugin| 33 + permissions += plugins.map do |plugin|
36 plugin.has_permission?(self, permission, profile) 34 plugin.has_permission?(self, permission, profile)
37 end 35 end
38 permissions.include?(true) 36 permissions.include?(true)
@@ -73,10 +71,7 @@ class Person &lt; Profile @@ -73,10 +71,7 @@ class Person &lt; Profile
73 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy } 71 Friendship.find(:all, :conditions => { :friend_id => person.id}).each { |friendship| friendship.destroy }
74 end 72 end
75 73
76 - after_destroy :destroy_user  
77 - def destroy_user  
78 - self.user.destroy if self.user  
79 - end 74 + belongs_to :user, :dependent => :delete
80 75
81 def can_control_scrap?(scrap) 76 def can_control_scrap?(scrap)
82 begin 77 begin
@@ -458,7 +453,7 @@ class Person &lt; Profile @@ -458,7 +453,7 @@ class Person &lt; Profile
458 end 453 end
459 454
460 def activities 455 def activities
461 - Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.user_id = #{self.id} ORDER BY updated_at DESC") 456 + Scrap.find_by_sql("SELECT id, updated_at, '#{Scrap.to_s}' AS klass FROM #{Scrap.table_name} WHERE scraps.receiver_id = #{self.id} AND scraps.scrap_id IS NULL UNION SELECT id, updated_at, '#{ActionTracker::Record.to_s}' AS klass FROM #{ActionTracker::Record.table_name} WHERE action_tracker.user_id = #{self.id} and action_tracker.verb != 'leave_scrap_to_self' and action_tracker.verb != 'add_member_in_community' ORDER BY updated_at DESC")
462 end 457 end
463 458
464 protected 459 protected
app/models/product.rb
1 class Product < ActiveRecord::Base 1 class Product < ActiveRecord::Base
  2 +
2 belongs_to :enterprise 3 belongs_to :enterprise
3 has_one :region, :through => :enterprise 4 has_one :region, :through => :enterprise
4 validates_presence_of :enterprise 5 validates_presence_of :enterprise
@@ -163,7 +164,7 @@ class Product &lt; ActiveRecord::Base @@ -163,7 +164,7 @@ class Product &lt; ActiveRecord::Base
163 164
164 def total_production_cost 165 def total_production_cost
165 return inputs_cost if price_details.empty? 166 return inputs_cost if price_details.empty?
166 - inputs_cost + price_details.map(&:price).inject { |sum,price| sum + price } 167 + inputs_cost + price_details.map(&:price).inject(0){ |sum,price| sum + price }
167 end 168 end
168 169
169 def price_described? 170 def price_described?
app/models/profile.rb
@@ -60,7 +60,8 @@ class Profile &lt; ActiveRecord::Base @@ -60,7 +60,8 @@ class Profile &lt; ActiveRecord::Base
60 } 60 }
61 61
62 acts_as_accessible 62 acts_as_accessible
63 - acts_as_having_hotspots 63 +
  64 + include Noosfero::Plugin::HotSpot
64 65
65 named_scope :memberships_of, lambda { |person| { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.accessor_type = ? AND role_assignments.accessor_id = ?', person.class.base_class.name, person.id ] } } 66 named_scope :memberships_of, lambda { |person| { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.accessor_type = ? AND role_assignments.accessor_id = ?', person.class.base_class.name, person.id ] } }
66 #FIXME: these will work only if the subclass is already loaded 67 #FIXME: these will work only if the subclass is already loaded
@@ -69,7 +70,7 @@ class Profile &lt; ActiveRecord::Base @@ -69,7 +70,7 @@ class Profile &lt; ActiveRecord::Base
69 named_scope :templates, :conditions => {:is_template => true} 70 named_scope :templates, :conditions => {:is_template => true}
70 71
71 def members 72 def members
72 - scopes = dispatch_scopes(:organization_members, self) 73 + scopes = plugins.dispatch_scopes(:organization_members, self)
73 scopes << Person.members_of(self) 74 scopes << Person.members_of(self)
74 scopes.size == 1 ? scopes.first : Person.or_scope(scopes) 75 scopes.size == 1 ? scopes.first : Person.or_scope(scopes)
75 end 76 end
@@ -113,6 +114,8 @@ class Profile &lt; ActiveRecord::Base @@ -113,6 +114,8 @@ class Profile &lt; ActiveRecord::Base
113 has_many :scraps_received, :class_name => 'Scrap', :foreign_key => :receiver_id, :order => "updated_at DESC", :dependent => :destroy 114 has_many :scraps_received, :class_name => 'Scrap', :foreign_key => :receiver_id, :order => "updated_at DESC", :dependent => :destroy
114 belongs_to :template, :class_name => 'Profile', :foreign_key => 'template_id' 115 belongs_to :template, :class_name => 'Profile', :foreign_key => 'template_id'
115 116
  117 + has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
  118 +
116 # FIXME ugly workaround 119 # FIXME ugly workaround
117 def self.human_attribute_name(attrib) 120 def self.human_attribute_name(attrib)
118 _(self.superclass.human_attribute_name(attrib)) 121 _(self.superclass.human_attribute_name(attrib))
@@ -255,7 +258,7 @@ class Profile &lt; ActiveRecord::Base @@ -255,7 +258,7 @@ class Profile &lt; ActiveRecord::Base
255 self.categories(true) 258 self.categories(true)
256 self.solr_save 259 self.solr_save
257 end 260 end
258 - self.categories(reload) 261 + self.categories(reload)
259 end 262 end
260 263
261 def category_ids=(ids) 264 def category_ids=(ids)
app/models/task.rb
@@ -31,7 +31,7 @@ class Task &lt; ActiveRecord::Base @@ -31,7 +31,7 @@ class Task &lt; ActiveRecord::Base
31 end 31 end
32 end 32 end
33 33
34 - belongs_to :requestor, :class_name => 'Person', :foreign_key => :requestor_id 34 + belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id
35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true 35 belongs_to :target, :foreign_key => :target_id, :polymorphic => true
36 36
37 validates_uniqueness_of :code, :on => :create 37 validates_uniqueness_of :code, :on => :create
app/models/uploaded_file.rb
@@ -67,7 +67,7 @@ class UploadedFile &lt; Article @@ -67,7 +67,7 @@ class UploadedFile &lt; Article
67 'upload-file' 67 'upload-file'
68 end 68 end
69 end 69 end
70 - 70 +
71 def mime_type 71 def mime_type
72 content_type 72 content_type
73 end 73 end
@@ -129,6 +129,12 @@ class UploadedFile &lt; Article @@ -129,6 +129,12 @@ class UploadedFile &lt; Article
129 end 129 end
130 end 130 end
131 131
  132 + def extension
  133 + dotindex = self.filename.rindex('.')
  134 + return nil unless dotindex
  135 + self.filename[(dotindex+1)..-1].downcase
  136 + end
  137 +
132 def allow_children? 138 def allow_children?
133 false 139 false
134 end 140 end
@@ -144,4 +150,5 @@ class UploadedFile &lt; Article @@ -144,4 +150,5 @@ class UploadedFile &lt; Article
144 def uploaded_file? 150 def uploaded_file?
145 true 151 true
146 end 152 end
  153 +
147 end 154 end
app/models/user.rb
@@ -30,7 +30,7 @@ class User &lt; ActiveRecord::Base @@ -30,7 +30,7 @@ class User &lt; ActiveRecord::Base
30 30
31 after_create do |user| 31 after_create do |user|
32 user.person ||= Person.new 32 user.person ||= Person.new
33 - user.person.attributes = user.person_data.merge(:identifier => user.login, :user_id => user.id, :environment_id => user.environment_id) 33 + user.person.attributes = user.person_data.merge(:identifier => user.login, :user => user, :environment_id => user.environment_id)
34 user.person.name ||= user.login 34 user.person.name ||= user.login
35 user.person.visible = false unless user.activated? 35 user.person.visible = false unless user.activated?
36 user.person.save! 36 user.person.save!
@@ -88,13 +88,13 @@ class User &lt; ActiveRecord::Base @@ -88,13 +88,13 @@ class User &lt; ActiveRecord::Base
88 attr_protected :activated_at 88 attr_protected :activated_at
89 89
90 # Virtual attribute for the unencrypted password 90 # Virtual attribute for the unencrypted password
91 - attr_accessor :password 91 + attr_accessor :password, :name
92 92
93 validates_presence_of :login, :email 93 validates_presence_of :login, :email
94 validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?}) 94 validates_format_of :login, :with => Profile::IDENTIFIER_FORMAT, :if => (lambda {|user| !user.login.blank?})
95 validates_presence_of :password, :if => :password_required? 95 validates_presence_of :password, :if => :password_required?
96 - validates_presence_of :password_confirmation, :if => :password_required?, :if => (lambda {|user| !user.password.blank?})  
97 - validates_length_of :password, :within => 4..40, :if => :password_required?, :if => (lambda {|user| !user.password.blank?}) 96 + validates_presence_of :password_confirmation, :if => :password_required?
  97 + validates_length_of :password, :within => 4..40, :if => :password_required?
98 validates_confirmation_of :password, :if => :password_required? 98 validates_confirmation_of :password, :if => :password_required?
99 validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?}) 99 validates_length_of :login, :within => 2..40, :if => (lambda {|user| !user.login.blank?})
100 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?}) 100 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?})
@@ -228,7 +228,12 @@ class User &lt; ActiveRecord::Base @@ -228,7 +228,12 @@ class User &lt; ActiveRecord::Base
228 end 228 end
229 229
230 def name 230 def name
231 - person ? person.name : login 231 + name = (self[:name] || login)
  232 + person.nil? ? name : (person.name || name)
  233 + end
  234 +
  235 + def name= name
  236 + self[:name] = name
232 end 237 end
233 238
234 def enable_email! 239 def enable_email!
@@ -274,6 +279,11 @@ class User &lt; ActiveRecord::Base @@ -274,6 +279,11 @@ class User &lt; ActiveRecord::Base
274 15 # in minutes 279 15 # in minutes
275 end 280 end
276 281
  282 +
  283 + def not_require_password!
  284 + @is_password_required = false
  285 + end
  286 +
277 protected 287 protected
278 # before filter 288 # before filter
279 def encrypt_password 289 def encrypt_password
@@ -282,9 +292,13 @@ class User &lt; ActiveRecord::Base @@ -282,9 +292,13 @@ class User &lt; ActiveRecord::Base
282 self.password_type ||= User.system_encryption_method.to_s 292 self.password_type ||= User.system_encryption_method.to_s
283 self.crypted_password = encrypt(password) 293 self.crypted_password = encrypt(password)
284 end 294 end
285 - 295 +
286 def password_required? 296 def password_required?
287 - crypted_password.blank? || !password.blank? 297 + (crypted_password.blank? || !password.blank?) && is_password_required?
  298 + end
  299 +
  300 + def is_password_required?
  301 + @is_password_required.nil? ? true : @is_password_required
288 end 302 end
289 303
290 def make_activation_code 304 def make_activation_code
@@ -292,6 +306,7 @@ class User &lt; ActiveRecord::Base @@ -292,6 +306,7 @@ class User &lt; ActiveRecord::Base
292 end 306 end
293 307
294 def deliver_activation_code 308 def deliver_activation_code
  309 + return if person.is_template?
295 User::Mailer.deliver_activation_code(self) unless self.activation_code.blank? 310 User::Mailer.deliver_activation_code(self) unless self.activation_code.blank?
296 end 311 end
297 312
app/views/account/login.rhtml
@@ -13,6 +13,8 @@ @@ -13,6 +13,8 @@
13 13
14 <%= f.password_field :password %> 14 <%= f.password_field :password %>
15 15
  16 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
  17 +
16 <% button_bar do %> 18 <% button_bar do %>
17 <%= submit_button( 'login', _('Log in') )%> 19 <%= submit_button( 'login', _('Log in') )%>
18 <% if is_thickbox %> 20 <% if is_thickbox %>
@@ -23,8 +25,13 @@ @@ -23,8 +25,13 @@
23 <% end %> 25 <% end %>
24 26
25 <% button_bar do %> 27 <% button_bar do %>
26 - <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>  
27 - <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %> 28 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  29 + <%= button :add, _("New user"), :controller => 'account', :action => 'signup' %>
  30 + <% end %>
  31 +
  32 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  33 + <%= button :help, _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + <% end %>
28 <% end %> 35 <% end %>
29 36
30 </div><!-- end class="login-box" --> 37 </div><!-- end class="login-box" -->
app/views/account/login_block.rhtml
@@ -9,25 +9,30 @@ @@ -9,25 +9,30 @@
9 @user ||= User.new 9 @user ||= User.new
10 %> 10 %>
11 11
12 - <% labelled_form_for :user, @user,  
13 - :url => login_url do |f| %> 12 + <% labelled_form_for :user, @user, :url => login_url do |f| %>
14 13
15 - <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %> 14 + <%= f.text_field :login, :onchange => 'this.value = convToValidLogin( this.value )' %>
16 15
17 - <%= f.password_field :password %> 16 + <%= f.password_field :password %>
  17 +
  18 + <%= @plugins.dispatch(:login_extra_contents).collect { |content| instance_eval(&content) }.join("") %>
18 19
19 <% button_bar do %> 20 <% button_bar do %>
20 <%= submit_button( 'login', _('Log in') )%> 21 <%= submit_button( 'login', _('Log in') )%>
21 - <%= link_to content_tag( 'span', _('New user') ),  
22 - { :controller => 'account', :action => 'signup' },  
23 - :class => 'button with-text icon-add' %> 22 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  23 + <%= link_to content_tag( 'span', _('New user') ),
  24 + { :controller => 'account', :action => 'signup' },
  25 + :class => 'button with-text icon-add' %>
  26 + <% end %>
24 <% end %> 27 <% end %>
25 28
26 <% end %> 29 <% end %>
27 30
28 - <p class="forgot-passwd">  
29 - <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>  
30 - </p> 31 + <% unless @plugins.dispatch(:allow_password_recovery).include?(false) %>
  32 + <p class="forgot-passwd">
  33 + <%= link_to _("I forgot my password!"), :controller => 'account', :action => 'forgot_password' %>
  34 + </p>
  35 + <% end %>
31 36
32 </div> 37 </div>
33 38
app/views/admin_panel/index.rhtml
1 <h1><%= _('Administrator Panel') %></h1> 1 <h1><%= _('Administrator Panel') %></h1>
2 2
3 -<p><%= _('You, as an environment administrator, has the following options:')%></p> 3 +<h2><%= _('System settings') %></h2>
4 4
5 <table> 5 <table>
6 - <tr><td><%= link_to _('Edit environment settings'), :action => 'site_info' %></td></tr>  
7 - <tr><td><%= link_to __('Edit message for disabled enterprises'), :action => 'message_for_disabled_enterprise' %></td></tr>  
8 - <tr><td><%= link_to _('Enable/disable features'), :controller => 'features' %></td></tr>  
9 - <tr><td><%= link_to _('Enable/disable plugins'), :controller => 'plugins' %></td></tr>  
10 - <tr><td><%= link_to _('Edit sideboxes'), :controller => 'environment_design'%></td></tr>  
11 - <tr><td><%= link_to _('Manage Categories'), :controller => 'categories'%></td></tr>  
12 - <tr><td><%= link_to _('Manage User roles'), :controller => 'role' %></td></tr>  
13 - <tr><td><%= link_to _('Manage users'), :controller => 'users' %></td></tr>  
14 - <tr><td><%= link_to _('Manage Validators by region'), :controller => 'region_validators' %></td></tr>  
15 - <tr><td><%= link_to _('Edit Templates'), :controller => 'templates' %></td></tr>  
16 - <tr><td><%= link_to _('Manage Fields'), :controller => 'features', :action => 'manage_fields' %></td></tr>  
17 - <tr><td><%= link_to _('Set Portal'), :action => 'set_portal_community' %></td></tr>  
18 - <tr><td><%= link_to _('Manage Licenses'), :controller =>'licenses' %></td></tr>  
19 - <% @plugins.dispatch(:admin_panel_links).each do |link| %> 6 + <tr><td><%= link_to _('Environment settings'), :action => 'site_info' %></td></tr>
  7 + <tr><td><%= link_to _('Features'), :controller => 'features' %></td></tr>
  8 + <tr><td><%= link_to _('Plugins'), :controller => 'plugins' %></td></tr>
  9 + <tr><td><%= link_to _('Sideboxes'), :controller => 'environment_design'%></td></tr>
  10 + <tr><td><%= link_to _('Homepage'), :action => 'set_portal_community' %></td></tr>
  11 + <tr><td><%= link_to _('Licenses'), :controller =>'licenses' %></td></tr>
  12 +</table>
  13 +
  14 +<h2><%= _('Profiles') %></h2>
  15 +
  16 +<table>
  17 + <tr><td><%= link_to _('User roles'), :controller => 'role' %></td></tr>
  18 + <tr><td><%= link_to _('Users'), :controller => 'users' %></td></tr>
  19 + <tr><td><%= link_to _('Profile templates'), :controller => 'templates' %></td></tr>
  20 + <tr><td><%= link_to _('Fields'), :controller => 'features', :action => 'manage_fields' %></td></tr>
  21 +</table>
  22 +
  23 +
  24 +<%
  25 + plugin_links = @plugins.dispatch(:admin_panel_links)
  26 +%>
  27 +<% unless plugin_links.empty? %>
  28 + <h2><%= _('Plugins') %></h2>
  29 + <table>
  30 + <% plugin_links.each do |link| %>
20 <tr><td><%= link_to link[:title], link[:url] %></td></tr> 31 <tr><td><%= link_to link[:title], link[:url] %></td></tr>
21 <% end %> 32 <% end %>
  33 + </table>
  34 +<% end %>
  35 +
  36 +<h2><%= _('Enterprise-related settings') %></h2>
  37 +
  38 +<table>
  39 + <tr><td><%= link_to __('Message for disabled enterprises'), :action => 'message_for_disabled_enterprise' %></td></tr>
  40 + <tr><td><%= link_to _('Validators by region'), :controller => 'region_validators' %></td></tr>
  41 + <tr><td><%= link_to _('Categories'), :controller => 'categories'%></td></tr>
22 </table> 42 </table>
app/views/box_organizer/_block_types.rhtml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +<% block_types.in_groups_of(2) do |block1, block2| %>
  2 + <div style='float: left; width: 48%; padding-top: 2px;'>
  3 + <%= labelled_radio_button(block1.description, :type, block1.name) %>
  4 + </div>
  5 + <% if block2 %>
  6 + <div style='float: left; width: 48%; padding-top: 2px;'>
  7 + <%= labelled_radio_button(block2.description, :type, block2.name) %>
  8 + </div>
  9 + <% end %>
  10 +<% end %>
app/views/box_organizer/_highlights_block.rhtml
1 <strong><%= _('Highlights') %></strong> 1 <strong><%= _('Highlights') %></strong>
2 -<div id='edit-highlights-block'> 2 +<div id='edit-highlights-block' style='width:450px'>
3 <table id='highlights' class='noborder'> 3 <table id='highlights' class='noborder'>
4 <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr> 4 <tr><th><%= _('Image') %></th><th><%= _('Address') %></th><th><%= _('Position') %></th><th><%= _('Title') %></th></tr>
5 <% for image in @block.images do %> 5 <% for image in @block.images do %>
app/views/box_organizer/_link_list_block.rhtml
1 <strong><%= _('Links') %></strong> 1 <strong><%= _('Links') %></strong>
2 -<div id='edit-link-list-block'> 2 +<div id='edit-link-list-block' style='width:450px'>
3 <table id='links' class='noborder'> 3 <table id='links' class='noborder'>
4 <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr> 4 <tr><th><%= _('Icon') %></th><th><%= _('Name') %></th><th><%= _('Address') %></th></tr>
5 <% for link in @block.links do %> 5 <% for link in @block.links do %>
app/views/box_organizer/_raw_html_block.rhtml
1 -<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 100%', :rows => 15)) %> 1 +<%= labelled_form_field(_('HTML code'), text_area(:block, :html, :style => 'width: 90%', :rows => 15)) %>
app/views/box_organizer/add_block.rhtml
1 -<% form_tag do %>  
2 -  
3 - <p><%= _('In what area do you want to put your new block?') %></p>  
4 -  
5 - <%# FIXME hardcoded stuff %>  
6 - <%= select_tag('box_id', options_for_select(@boxes.select { |item| item.position != 1 }.map {|item| [ _("Area %d") % item.position, item.id]})) %>  
7 -  
8 - <p><%= _('Select the type of block you want to add to your page.') %></p>  
9 -  
10 - <% @block_types.in_groups_of(2) do |block1, block2| %>  
11 - <div style='float: left; width: 48%; padding-top: 2px;'>  
12 - <%= radio_button_tag('type', block1.name) %>  
13 - <%= label_tag "type_#{block1.name.downcase}", block1.description %> 1 +<div style='height:350px'>
  2 + <% form_tag do %>
  3 +
  4 + <p><%= _('In what area do you want to put your new block?') %></p>
  5 +
  6 + <% @boxes.each do |box| %>
  7 + <%= labelled_radio_button(_("Area %d") % box.position, :box_id, box.id, box.central?, { :class => 'box-position', 'data-position' => box.position }) %>
  8 + <% end %>
  9 +
  10 + <script type="text/javascript">
  11 + (function ($) {
  12 + $(document).ready(function () {
  13 + $(".box-position").live('change', function () {
  14 + if ($(this).attr('data-position') == '1') {
  15 + $('#center-block-types').show();
  16 + $('#side-block-types').hide();
  17 + } else {
  18 + $('#center-block-types').hide();
  19 + $('#side-block-types').show();
  20 + };
  21 + });
  22 + })})(jQuery);
  23 + </script>
  24 +
  25 + <p><%= _('Select the type of block you want to add to your page.') %></p>
  26 +
  27 + <div id='center-block-types'>
  28 + <%= render :partial => 'block_types', :locals => { :block_types => @center_block_types } %>
14 </div> 29 </div>
15 - <% if block2 %>  
16 - <div style='float: left; width: 48%; padding-top: 2px;'>  
17 - <%= radio_button_tag('type', block2.name) %>  
18 - <%= label_tag "type_#{block2.name.downcase}", block2.description %>  
19 - </div> 30 +
  31 + <div id='side-block-types' style='display:none'>
  32 + <%= render :partial => 'block_types', :locals => { :block_types => @side_block_types } %>
  33 + </div>
  34 +
  35 + <br style='clear: both'/>
  36 +
  37 + <% button_bar do %>
  38 + <%= submit_button(:add, _("Add")) %>
  39 + <%= colorbox_close_button(_('Close')) %>
20 <% end %> 40 <% end %>
21 - <% end %>  
22 - <br style='clear: both'/>  
23 -  
24 - <% button_bar do %>  
25 - <%= submit_button(:add, _("Add")) %>  
26 - <%= lightbox_close_button(_('Close')) %>  
27 - <% end %>  
28 41
29 -<% end %> 42 + <% end %>
  43 +</div>
app/views/box_organizer/edit.rhtml
1 -<h2><%= _('Editing block') %></h2> 1 +<div style='width: 500px;'>
  2 + <h2><%= _('Editing block') %></h2>
2 3
3 -<% form_tag(:action => 'save', :id => @block.id) do %> 4 + <% form_tag(:action => 'save', :id => @block.id) do %>
4 5
5 - <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %> 6 + <%= labelled_form_field(_('Custom title for this block: '), text_field(:block, :title, :maxlength => 20)) %>
6 7
7 - <%= render :partial => partial_for_class(@block.class) %> 8 + <%= render :partial => partial_for_class(@block.class) %>
8 9
9 - <%= labelled_form_field _('Display this block:'), '' %>  
10 - <div style='margin-left: 10px'>  
11 - <%= radio_button(:block, :display, 'always') %>  
12 - <%= label_tag('block_display_always', _('In all pages')) %>  
13 - <br/>  
14 - <%= radio_button(:block, :display, 'home_page_only') %>  
15 - <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>  
16 - <br/>  
17 - <%= radio_button(:block, :display, 'except_home_page') %>  
18 - <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>  
19 - <br/>  
20 - <%= radio_button(:block, :display, 'never') %>  
21 - <%= label_tag('block_display_never', _("Don't display")) %>  
22 - </div> 10 + <%= labelled_form_field _('Display this block:'), '' %>
  11 + <div style='margin-left: 10px'>
  12 + <%= radio_button(:block, :display, 'always') %>
  13 + <%= label_tag('block_display_always', _('In all pages')) %>
  14 + <br/>
  15 + <%= radio_button(:block, :display, 'home_page_only') %>
  16 + <%= label_tag('block_display_home_page_only', _('Only in the homepage')) %>
  17 + <br/>
  18 + <%= radio_button(:block, :display, 'except_home_page') %>
  19 + <%= label_tag('block_display_except_home_page', _('In all pages, except in the homepage')) %>
  20 + <br/>
  21 + <%= radio_button(:block, :display, 'never') %>
  22 + <%= label_tag('block_display_never', _("Don't display")) %>
  23 + </div>
23 24
24 <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + environment.locales.map {|key, value| [value, key]} )) %> 25 <%= labelled_form_field(_('Show for:'), select(:block, :language, [ [ _('all languages'), 'all']] + environment.locales.map {|key, value| [value, key]} )) %>
25 26
26 - <% button_bar do %>  
27 - <%= submit_button(:save, _('Save')) %>  
28 - <%= lightbox_close_button(_('Cancel')) %>  
29 - <% end %> 27 + <% button_bar do %>
  28 + <%= submit_button(:save, _('Save')) %>
  29 + <%= colorbox_close_button(_('Cancel')) %>
  30 + <% end %>
30 31
31 -<% end %> 32 + <% end %>
  33 +</div>
app/views/box_organizer/index.rhtml
1 <h1><%= _('Editing sideboxes')%></h1> 1 <h1><%= _('Editing sideboxes')%></h1>
2 2
3 <% button_bar do %> 3 <% button_bar do %>
4 - <%= lightbox_button('add', _('Add a block'), { :action => 'add_block' }) %> 4 + <%= colorbox_button('add', _('Add a block'), { :action => 'add_block' }) %>
5 <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %> 5 <%= button(:back, _('Back to control panel'), :controller => (profile.nil? ? 'admin_panel': 'profile_editor')) %>
6 <% end %> 6 <% 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 <%= colorbox_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 <%= colorbox_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
1 <li id="<%= comment.anchor %>" class="article-comment"> 1 <li id="<%= comment.anchor %>" class="article-comment">
2 <div class="article-comment-inner"> 2 <div class="article-comment-inner">
3 3
4 - <div class="comment-content comment-logged-<%= comment.author ? 'in' : 'out' %> <%= 'comment-from-owner' if ( comment.author && (@page.profile.name == comment.author.name) ) %>"> 4 + <div class="comment-content comment-logged-<%= comment.author ? 'in' : 'out' %> <%= 'comment-from-owner' if ( comment.author && (profile == comment.author) ) %>">
5 5
6 <% if comment.author %> 6 <% if comment.author %>
7 <%= link_to image_tag(profile_icon(comment.author, :minor)) + 7 <%= link_to image_tag(profile_icon(comment.author, :minor)) +
@@ -29,17 +29,12 @@ @@ -29,17 +29,12 @@
29 <% end %> 29 <% end %>
30 30
31 <% comment_balloon do %> 31 <% comment_balloon do %>
32 - <% if logged_in? && (user == @page.profile || user == comment.author || user.has_permission?(:moderate_comments, @page.profile)) %>  
33 - <% button_bar(:style => 'float: right; margin-top: 0px;') do %>  
34 - <%= icon_button(:delete, _('Remove this comment and all its replies'), { :profile => params[:profile], :remove_comment => comment.id, :view => params[:view] }, :method => :post, :confirm => _('Are you sure you want to remove this comment and all its replies?')) %>  
35 - <% end %>  
36 - <% end %>  
37 32
38 <div class="comment-details"> 33 <div class="comment-details">
39 <div class="comment-created-at"> 34 <div class="comment-created-at">
40 <%= show_time(comment.created_at) %> 35 <%= show_time(comment.created_at) %>
41 </div> 36 </div>
42 - <h4><%= comment.title %></h4> 37 + <h4><%= comment.title.blank? && '&nbsp;' || comment.title %></h4>
43 <div class="comment-text"> 38 <div class="comment-text">
44 <p/> 39 <p/>
45 <%= txt2html comment.body %> 40 <%= txt2html comment.body %>
@@ -57,18 +52,42 @@ @@ -57,18 +52,42 @@
57 </script> 52 </script>
58 <% end %> 53 <% end %>
59 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %> 54 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
60 - <%= 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, 55 +
  56 + <% if comment.spam? %>
  57 + &nbsp;
  58 + <%= link_to_function(_('Mark as NOT SPAM'), 'remove_comment(this, %s); return false;' % url_for(:mark_comment_as_ham => comment.id).to_json, :class => 'comment-footer comment-footer-link comment-footer-hide') %>
  59 + <% else %>
  60 + <% if (logged_in? && (user == profile || user.has_permission?(:moderate_comments, profile))) %>
  61 + &nbsp;
  62 + <%= link_to_function(_('Mark as SPAM'), 'remove_comment(this, %s, %s); return false;' % [url_for(:mark_comment_as_spam => comment.id).to_json, _('Are you sure you want to mark this comment as SPAM?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide') %>
  63 + <% end %>
  64 + <% end %>
  65 +
  66 + <% if comment.author && comment.author == user %>
  67 + &nbsp;
  68 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  69 + <% end %>
  70 +
  71 + <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %>
  72 + &nbsp;
  73 + <%= link_to_function(_('Remove'), 'remove_comment(this, %s, %s); return false ;' % [url_for(:profile => params[:profile], :remove_comment => comment.id, :view => params[:view]).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'comment-footer comment-footer-link comment-footer-hide remove-children') %>
  74 + <% end %>
  75 +
  76 + <% unless comment.spam? %>
  77 + &nbsp;
  78 + <%= link_to_function _('Reply'),
  79 + "var f = add_comment_reply_form(this, %s); f.find('comment_title, textarea').val(''); return false" % comment.id,
62 :class => 'comment-footer comment-footer-link comment-footer-hide', 80 :class => 'comment-footer comment-footer-link comment-footer-hide',
63 :id => 'comment-reply-to-' + comment.id.to_s 81 :id => 'comment-reply-to-' + comment.id.to_s
64 - %> 82 + %>
  83 + <% end %>
65 </div> 84 </div>
66 85
67 <% end %> 86 <% end %>
68 87
69 </div> 88 </div>
70 89
71 - <% unless comment.replies.blank? %> 90 + <% unless comment.replies.blank? || comment.spam? %>
72 <ul class="comment-replies"> 91 <ul class="comment-replies">
73 <% comment.replies.each do |reply| %> 92 <% comment.replies.each do |reply| %>
74 <%= render :partial => 'comment', :locals => { :comment => reply } %> 93 <%= render :partial => 'comment', :locals => { :comment => reply } %>
app/views/content_viewer/_comment_form.rhtml
@@ -32,15 +32,17 @@ function submit_comment_form(button) { @@ -32,15 +32,17 @@ function submit_comment_form(button) {
32 32
33 <div class="post_comment_box <%= @form_div %>"> 33 <div class="post_comment_box <%= @form_div %>">
34 34
35 -<h4 onclick="var d = jQuery(this).parent('.post_comment_box');  
36 - if (d.hasClass('closed')) {  
37 - d.removeClass('closed');  
38 - d.addClass('opened');  
39 - d.find('input[name=comment[title]], textarea').val('');  
40 - d.find('.comment_form input[name=comment[<%= focus_on %>]]').focus();  
41 - }">  
42 - <%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %>  
43 -</h4> 35 +<% if display_link %>
  36 + <h4 onclick="var d = jQuery(this).parent('.post_comment_box');
  37 + if (d.hasClass('closed')) {
  38 + d.removeClass('closed');
  39 + d.addClass('opened');
  40 + d.find('input[name=comment[title]], textarea').val('');
  41 + d.find('.comment_form input[name=comment[<%= focus_on %>]]').focus();
  42 + }">
  43 + <%= content_tag('a', '', :name => 'comment_form') + _('Post a comment') %>
  44 + </h4>
  45 +<% end %>
44 46
45 <% unless pass_without_comment_captcha? %> 47 <% unless pass_without_comment_captcha? %>
46 <div id="recaptcha-container" style="display: none"> 48 <div id="recaptcha-container" style="display: none">
@@ -59,7 +61,7 @@ function submit_comment_form(button) { @@ -59,7 +61,7 @@ function submit_comment_form(button) {
59 </script> 61 </script>
60 <% end %> 62 <% end %>
61 63
62 -<% form_tag( url_for(@page.view_url.merge({:only_path => true})), { :class => 'comment_form' } ) do %> 64 +<% form_tag( url, { :class => 'comment_form' } ) do %>
63 <%= hidden_field_tag(:confirm, 'false') %> 65 <%= hidden_field_tag(:confirm, 'false') %>
64 66
65 <%= required_fields_message %> 67 <%= required_fields_message %>
@@ -84,7 +86,11 @@ function submit_comment_form(button) { @@ -84,7 +86,11 @@ function submit_comment_form(button) {
84 86
85 <% button_bar do %> 87 <% button_bar do %>
86 <%= submit_button('add', _('Post comment'), :onclick => "submit_comment_form(this); return false") %> 88 <%= submit_button('add', _('Post comment'), :onclick => "submit_comment_form(this); return false") %>
87 - <%= button_to_function :cancel, _('Cancel'), "f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %> 89 + <% if cancel_triggers_hide %>
  90 + <%= button_to_function :cancel, _('Cancel'), "f=jQuery(this).parents('.post_comment_box'); f.removeClass('opened'); f.addClass('closed'); return false" %>
  91 + <% else %>
  92 + <%= button('cancel', _('Cancel'), {:action => 'view_page', :profile => profile.identifier, :page => @comment.article.explode_path})%>
  93 + <% end %>
88 <% end %> 94 <% end %>
89 <% end %> 95 <% end %>
90 96
app/views/content_viewer/edit_comment.html.erb 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +<h1><%= _('Edit comment') %></h1>
  2 +
  3 +<%= render :partial => 'comment_form', :locals => {:url => {:action => 'edit_comment', :profile => profile.identifier}, :display_link => false, :cancel_triggers_hide => false} %>
app/views/content_viewer/view_page.rhtml
@@ -98,13 +98,7 @@ @@ -98,13 +98,7 @@
98 </ul> 98 </ul>
99 99
100 <% if @page.accept_comments? %> 100 <% if @page.accept_comments? %>
101 - <div id="page-comment-form"><%= render :partial => 'comment_form' %></div>  
102 - <script type="text/javascript">  
103 - jQuery( function() {  
104 - jQuery('.article-comment').live('mouseover', function() { jQuery(this).find('.icon-delete:first').show(); });  
105 - jQuery('.article-comment').live('mouseout', function() { jQuery(this).find('.icon-delete').hide(); });  
106 - });  
107 - </script> 101 + <div id="page-comment-form"><%= render :partial => 'comment_form', :locals => {:url => url_for(@page.view_url.merge({:only_path => true})), :display_link => true, :cancel_triggers_hide => true}%></div>
108 <% end %> 102 <% end %>
109 </div><!-- end class="comments" --> 103 </div><!-- end class="comments" -->
110 104
app/views/friends/index.rhtml
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 :class => 'button icon-remove', 31 :class => 'button icon-remove',
32 :title => _('remove') %> 32 :title => _('remove') %>
33 <%= link_to content_tag('span',_('contact')), 33 <%= link_to content_tag('span',_('contact')),
34 - friend.url.merge(:controller => 'contact', :action => 'new'), 34 + friend.url.merge(:controller => 'contact', :action => 'new', :profile => friend.identifier),
35 :class => 'button icon-menu-mail', 35 :class => 'button icon-menu-mail',
36 :title => _('contact') %> 36 :title => _('contact') %>
37 </div><!-- end class="controll" --> 37 </div><!-- end class="controll" -->
app/views/layouts/_javascript.rhtml
@@ -2,7 +2,8 @@ @@ -2,7 +2,8 @@
2 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', 2 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox',
3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', 3 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 4 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
5 -'add-and-join', 'report-abuse', 'catalog', 'manage-products', :cache => 'cache-general' %> 5 +'add-and-join', 'report-abuse', 'catalog', 'manage-products',
  6 +'jquery-ui-timepicker-addon', :cache => 'cache-general' %>
6 7
7 <% language = FastGettext.locale %> 8 <% language = FastGettext.locale %>
8 <% %w{messages methods}.each do |type| %> 9 <% %w{messages methods}.each do |type| %>
app/views/layouts/application-ng.rhtml
@@ -56,10 +56,18 @@ @@ -56,10 +56,18 @@
56 <%= usermenu_logged_in %> 56 <%= usermenu_logged_in %>
57 </span> 57 </span>
58 <span class='not-logged-in' style='display: none'> 58 <span class='not-logged-in' style='display: none'>
59 - <%= _("<span class='login'>%s</span> <span class='or'>or</span> <span class='signup'>%s</span>") % [thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login'), link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup') ] %> 59 +
  60 + <%= _("<span class='login'>%s</span>") % thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login') %>
  61 + <%= @plugins.dispatch(:alternative_authentication_link).collect { |content| instance_eval(&content) }.join("") %>
  62 +
60 <div id='inlineLoginBox' style='display: none;'> 63 <div id='inlineLoginBox' style='display: none;'>
61 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %> 64 <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
62 </div> 65 </div>
  66 +
  67 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  68 + <%= _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup')%>
  69 + <% end %>
  70 +
63 </span> 71 </span>
64 <form action="/search" class="search_form" method="get" class="clean"> 72 <form action="/search" class="search_form" method="get" class="clean">
65 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" /> 73 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" />
app/views/plugins/index.rhtml
1 <h1><%= _('Manage plugins') %></h1> 1 <h1><%= _('Manage plugins') %></h1>
2 -<%= _('Here you can enable or disable any plugin of your environment.')%> 2 +
  3 +<p>
  4 +<%= _('Select which plugins you want to enable in your environment') %>
  5 +</p>
3 6
4 <% labelled_form_for(:environment, @environment, :url => {:action => 'update'}) do |f| %> 7 <% labelled_form_for(:environment, @environment, :url => {:action => 'update'}) do |f| %>
5 8
6 -<table>  
7 - <tr>  
8 - <th><%= _('Plugin') %></th>  
9 - <th><%= _('Description') %></th>  
10 - <th><%= _('Enabled?') %></th>  
11 - </tr>  
12 - <%= hidden_field_tag('environment[enabled_plugins][]', '') %>  
13 - <% @active_plugins.each do |plugin| %>  
14 - <tr>  
15 - <td><%= plugin.has_admin_url? ? link_to(plugin.plugin_name, plugin.admin_url) : plugin.plugin_name %></td>  
16 - <td><%= plugin.plugin_description %></td>  
17 - <td><%= check_box_tag "environment[enabled_plugins][]", plugin, @environment.enabled_plugins.include?(plugin.to_s), :id => plugin.plugin_name %></td>  
18 - </tr>  
19 - <% end %>  
20 -</table> 9 + <table>
  10 + <% @active_plugins.sort_by(&:plugin_name).each do |plugin| %>
  11 + <tr>
  12 + <td style='vertical-align: top'><%= check_box_tag "environment[enabled_plugins][]", plugin, @environment.enabled_plugins.include?(plugin.to_s), :id => plugin.plugin_name %></td>
  13 + <td>
  14 + <%= hidden_field_tag('environment[enabled_plugins][]', '') %>
  15 + <strong><%= plugin.plugin_name %></strong>
  16 + <br/>
  17 + <%= plugin.plugin_description %>
  18 + <% if plugin.has_admin_url? %>
  19 + <br/>
  20 + <br/>
  21 + <%= link_to(_('Configuration'), plugin.admin_url) %>
  22 + <% end %>
  23 + </td>
  24 + </tr>
  25 + <% end %>
  26 + </table>
21 27
22 <div> 28 <div>
23 <% button_bar do %> 29 <% button_bar do %>
app/views/profile/_comment.rhtml
@@ -5,19 +5,35 @@ @@ -5,19 +5,35 @@
5 <li class="article-comment" style='border-bottom:none;'> 5 <li class="article-comment" style='border-bottom:none;'>
6 <div class="article-comment-inner"> 6 <div class="article-comment-inner">
7 7
8 - <div class="comment-content comment-logged-in"> 8 + <div class="comment-content comment-logged-<%= comment.author ? 'in' : 'out' %>">
9 9
10 <% if comment.author %> 10 <% if comment.author %>
11 <%= link_to image_tag(profile_icon(comment.author, :minor)), 11 <%= link_to image_tag(profile_icon(comment.author, :minor)),
12 - Person.find(comment.author_id).url, 12 + comment.author_url,
13 :class => 'comment-picture', 13 :class => 'comment-picture',
14 :title => comment.author_name 14 :title => comment.author_name
15 %> 15 %>
  16 + <% else %>
  17 + <% url_image, status_class = comment.author_id ?
  18 + [comment.removed_user_image, 'icon-user-removed'] :
  19 + [str_gravatar_url_for( comment.email ), 'icon-user-unknown'] %>
  20 +
  21 + <%= link_to(
  22 + image_tag(url_image, :onerror=>'gravatarCommentFailback(this)',
  23 + 'data-gravatar'=>str_gravatar_url_for(comment.email)) +
  24 + content_tag('span', comment.author_name, :class => 'comment-info') +
  25 + content_tag('span', comment.message,
  26 + :class => 'comment-user-status comment-user-status-wall ' + status_class),
  27 + gravatar_profile_url(comment.email),
  28 + :target => '_blank',
  29 + :class => 'comment-picture',
  30 + :title => '%s %s' % [comment.author_name, comment.message]
  31 + )%>
16 <% end %> 32 <% end %>
17 33
18 <div class="comment-details"> 34 <div class="comment-details">
19 <div class="comment-text"> 35 <div class="comment-text">
20 - <%= link_to(comment.author_name, comment.author.url) %> 36 + <%= comment.author.present? ? link_to(comment.author_name, comment.author.url) : content_tag('strong', comment.author_name) %>
21 <% unless comment.title.blank? %> 37 <% unless comment.title.blank? %>
22 <span class="comment-title"><%= comment.title %></span><br/> 38 <span class="comment-title"><%= comment.title %></span><br/>
23 <% end %> 39 <% end %>
@@ -30,7 +46,7 @@ @@ -30,7 +46,7 @@
30 46
31 <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %> 47 <% if logged_in? && (user == profile || user == comment.author || user.has_permission?(:moderate_comments, profile)) %>
32 <% button_bar(:style => 'float: right; margin-top: 0px;') do %> 48 <% button_bar(:style => 'float: right; margin-top: 0px;') do %>
33 - <%= icon_button(:delete, _('Remove'), { :action => :remove_comment, :comment_id => comment.id }, :method => :get, :confirm => _('Are you sure you want to remove this comment and all its replies?')) %> 49 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.article-comment'", url_for(:profile => params[:profile], :action => :remove_comment, :comment_id => comment.id, :view => params[:view]).to_json, _('Are you sure you want to remove this comment and all its replies?').to_json], :class => 'button icon-button icon-delete') %>
34 <% end %> 50 <% end %>
35 <% end %> 51 <% end %>
36 <br style="clear: both;" /> 52 <br style="clear: both;" />
@@ -46,6 +62,10 @@ @@ -46,6 +62,10 @@
46 </script> 62 </script>
47 <% end %> 63 <% end %>
48 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %> 64 <%= report_abuse(comment.author, :comment_link, comment) if comment.author %>
  65 + <% if comment.author && comment.author == user %>
  66 + <%= expirable_comment_link comment, :edit, _('Edit'), {:action => 'edit_comment', :id => comment.id, :profile => profile.identifier} %>
  67 + <%= content_tag('span', ' | ', :class => 'comment-footer comment-footer-hide') %>
  68 + <% end %>
49 <%= link_to_function _('Reply'), 69 <%= 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, 70 "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', 71 :class => 'comment-footer comment-footer-link comment-footer-hide',
app/views/profile/_create_article.rhtml
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p> 15 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
16 <div class='profile-wall-actions'> 16 <div class='profile-wall-actions'>
17 <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %> 17 <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %>
18 - <%= link_to_remote(content_tag(:span, _('Remove')), :url =>{:action => 'remove_activity', :activity_id => activity.id, :only_hide => true}, :confirm => _('Are you sure?'), :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %> 18 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.profile-activity-item'", url_for(:profile => params[:profile], :action => :remove_activity, :activity_id => activity.id, :only_hide => true, :view => params[:view]).to_json, _('Are you sure you want to remove this activity and all its replies?').to_json]) if logged_in? && current_person == @profile %>
19 </div> 19 </div>
20 </div> 20 </div>
21 21
app/views/profile/_default_activity.rhtml
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p> 6 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
7 <div class='profile-wall-actions'> 7 <div class='profile-wall-actions'>
8 <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %> 8 <%= link_to s_('profile|Comment'), '#', { :class => 'focus-on-comment'} %>
9 - <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %> 9 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.profile-activity-item'", url_for(:profile => params[:profile], :action => :remove_activity, :activity_id => activity.id, :view => params[:view]).to_json, _('Are you sure you want to remove this activity and all its replies?').to_json]) if logged_in? && current_person == @profile %>
10 </div> 10 </div>
11 </div> 11 </div>
12 12
app/views/profile/_leave_scrap.rhtml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p> 5 <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
6 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p> 6 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
7 <div class='profile-wall-actions'> 7 <div class='profile-wall-actions'>
8 - <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %> 8 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.profile-activity-item'", url_for(:profile => params[:profile], :action => :remove_activity, :activity_id => activity.id, :view => params[:view]).to_json, _('Are you sure you want to remove this activity and all its replies?').to_json]) if logged_in? && current_person == @profile %>
9 </div> 9 </div>
10 </div> 10 </div>
11 11
app/views/profile/_profile_scrap.rhtml
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 <%= link_to_function s_('profile|Comment'), "hide_and_show(['#profile-wall-message-response-#{scrap.id}'],['#profile-wall-reply-#{scrap.id}', '#profile-wall-reply-form-#{scrap.id}']);$('reply_content_#{scrap.id}').value='';$('reply_content_#{scrap.id}').focus();return false", :class => "profile-send-reply" %> 12 <%= link_to_function s_('profile|Comment'), "hide_and_show(['#profile-wall-message-response-#{scrap.id}'],['#profile-wall-reply-#{scrap.id}', '#profile-wall-reply-form-#{scrap.id}']);$('reply_content_#{scrap.id}').value='';$('reply_content_#{scrap.id}').focus();return false", :class => "profile-send-reply" %>
13 </span> 13 </span>
14 <% end %> 14 <% end %>
15 - <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_scrap', :scrap_id => scrap.id}, :update => "profile-activity-item-#{scrap.id}") if logged_in? && user.can_control_scrap?(scrap) %> 15 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.profile-activity-item'", url_for(:profile => params[:profile], :action => :remove_scrap, :scrap_id => scrap.id, :view => params[:view]).to_json, _('Are you sure you want to remove this scrap and all its replies?').to_json]) if logged_in? && user.can_control_scrap?(scrap) %>
16 </div> 16 </div>
17 </div> 17 </div>
18 18
app/views/profile/_upload_image.rhtml
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p> 6 <p class='profile-activity-text'><%= link_to activity.user.name, activity.user.url %> <%= describe activity %></p>
7 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p> 7 <p class='profile-activity-time'><%= time_ago_as_sentence(activity.created_at) %></p>
8 <div class='profile-wall-actions'> 8 <div class='profile-wall-actions'>
9 - <%= link_to_remote(content_tag(:span, _('Remove')), :confirm => _('Are you sure?'), :url =>{:action => 'remove_activity', :activity_id => activity.id}, :update => "profile-activity-item-#{activity.id}") if logged_in? && current_person == @profile %> 9 + <%= link_to_function(_('Remove'), 'remove_item_wall(this, %s, %s, %s); return false ;' % ["'.profile-activity-item'", url_for(:profile => params[:profile], :action => :remove_activity, :activity_id => activity.id, :view => params[:view]).to_json, _('Are you sure you want to remove this activity and all its replies?').to_json]) if logged_in? && current_person == @profile %>
10 </div> 10 </div>
11 </div> 11 </div>
12 </div> 12 </div>
app/views/profile_editor/index.rhtml
@@ -66,6 +66,8 @@ @@ -66,6 +66,8 @@
66 66
67 <%= control_panel_button(_('Manage my groups'), 'groups', :controller => 'memberships') if profile.person? %> 67 <%= control_panel_button(_('Manage my groups'), 'groups', :controller => 'memberships') if profile.person? %>
68 68
  69 + <%= control_panel_button(_('Manage SPAM'), 'manage-spam', :controller => 'spam', :action => 'index') %>
  70 +
69 <% @plugins.dispatch(:control_panel_buttons).each do |button| %> 71 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
70 <%= control_panel_button(button[:title], button[:icon], button[:url]) %> 72 <%= control_panel_button(button[:title], button[:icon], button[:url]) %>
71 <% end %> 73 <% end %>
app/views/search/_display_results.rhtml
1 -<div id="search-results" class="<%= @results.size == 1 ? 'only-one-result-box' : 'multiple-results-boxes' %>"> 1 +<div id="search-results" class="<%= !multiple_search? ? 'only-one-result-box' : 'multiple-results-boxes' %>">
2 <% @order.each do |name| %> 2 <% @order.each do |name| %>
3 <% results = @results[name] %> 3 <% results = @results[name] %>
4 <% empty = results.nil? || results.empty? %> 4 <% empty = results.nil? || results.empty? %>
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 <% if not empty %> 7 <% if not empty %>
8 <% partial = partial_for_class(results.first.class.class_name.constantize) %> 8 <% partial = partial_for_class(results.first.class.class_name.constantize) %>
9 9
10 - <% if @results.size > 1 %> 10 + <% if multiple_search? %>
11 <h3><%= @names[name] %></h3> 11 <h3><%= @names[name] %></h3>
12 <% if results.total_entries > SearchController::MULTIPLE_SEARCH_LIMIT %> 12 <% if results.total_entries > SearchController::MULTIPLE_SEARCH_LIMIT %>
13 <%= link_to(_('see all (%d)') % results.total_entries, params.merge(:action => name), :class => 'see-more' ) %> 13 <%= link_to(_('see all (%d)') % results.total_entries, params.merge(:action => name), :class => 'see-more' ) %>
@@ -22,9 +22,10 @@ @@ -22,9 +22,10 @@
22 </ul> 22 </ul>
23 </div> 23 </div>
24 <% else %> 24 <% else %>
25 - <% if @results.size > 1 %> 25 + <% if multiple_search? %>
26 <h3><%= @names[name] %></h3> 26 <h3><%= @names[name] %></h3>
27 <% end %> 27 <% end %>
  28 +
28 <div class="search-results-innerbox search-results-type-empty"> 29 <div class="search-results-innerbox search-results-type-empty">
29 <div> <%= _('None') %> </div> 30 <div> <%= _('None') %> </div>
30 </div> 31 </div>
app/views/search/_image.rhtml
1 <div class="search-image-container"> 1 <div class="search-image-container">
2 2
3 <% if image.is_a? UploadedFile and image.filename %> 3 <% if image.is_a? UploadedFile and image.filename %>
4 - <% extension = image.filename[(image.filename.rindex('.')+1)..-1].downcase %> 4 + <% extension = image.extension %>
5 <% if ['jpg', 'jpeg', 'gif', 'png', 'tiff', 'svg'].include? extension %> 5 <% if ['jpg', 'jpeg', 'gif', 'png', 'tiff', 'svg'].include? extension %>
6 <%= link_to '', image.view_url, :class => "search-image-pic", :style => 'background-image: url(%s)'% image.public_filename(:thumb) %> 6 <%= link_to '', image.view_url, :class => "search-image-pic", :style => 'background-image: url(%s)'% image.public_filename(:thumb) %>
7 <% if image.width && image.height %> 7 <% if image.width && image.height %>
app/views/search/_profile.rhtml
1 <li class="search-profile-item"> 1 <li class="search-profile-item">
2 -<% if @empty_query or @results.size > 1 or !profile.enterprise? %> 2 +<% if @empty_query or multiple_search? or !profile.enterprise? %>
3 <%= profile_image_link profile, :portrait, 'div', 3 <%= profile_image_link profile, :portrait, 'div',
4 @filter == 'more_recent' ? profile.send(@filter + '_label') + show_date(profile.created_at) : profile.send(@filter + '_label') %> 4 @filter == 'more_recent' ? profile.send(@filter + '_label') + show_date(profile.created_at) : profile.send(@filter + '_label') %>
5 <% else %> 5 <% else %>
app/views/search/communities.rhtml
@@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
4 <% if logged_in? %> 4 <% if logged_in? %>
5 <% button_bar do %> 5 <% button_bar do %>
6 <%# FIXME shouldn't the user create the community in the current environment instead of going to its home environment? %> 6 <%# FIXME shouldn't the user create the community in the current environment instead of going to its home environment? %>
7 - <%= button(:add, __('New community'), user.url.merge(:controller => 'memberships', :action => 'new_community')) %> 7 + <%= button(:add, __('New community'), user.url.merge(:controller => 'memberships', :action => 'new_community', :profile => user.identifier)) %>
8 <% end %> 8 <% end %>
9 <% end %> 9 <% end %>
10 10
app/views/spam/index.rhtml 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +<h1><%= _('Manage SPAM') %></h1>
  2 +
  3 +<% button_bar do %>
  4 + <%= button :back, _('Back to control panel'), :controller => :profile_editor %>
  5 +<% end %>
  6 +
  7 +<%# FIXME should not need to replicate the article structure like this to be able to use the same formatting as the comments listing %>
  8 +<div id='article'>
  9 + <div class="comments" id="comments_list">
  10 + <ul class="article-comments-list">
  11 + <%= render :partial => 'content_viewer/comment', :collection => @spam %>
  12 + </ul>
  13 + </div>
  14 +</div>
  15 +
  16 +<%= pagination_links @spam %>
  17 +
  18 +<% button_bar do %>
  19 + <%= button :back, _('Back to control panel'), :controller => :profile_editor %>
  20 +<% end %>
app/views/tasks/_task.rhtml
@@ -50,13 +50,13 @@ @@ -50,13 +50,13 @@
50 <% fields_for "tasks[#{task.id}][task]", task do |f| %> 50 <% fields_for "tasks[#{task.id}][task]", task do |f| %>
51 <% if task.accept_details %> 51 <% if task.accept_details %>
52 <div id="on-accept-information-<%=task.id%>" style="display: none"> 52 <div id="on-accept-information-<%=task.id%>" style="display: none">
53 - <%= render :partial => partial_for_task_class(task.class, :accept_details), :locals => {:task => task, :f => f} %> 53 + <%= render :partial => partial_for_class(task.class, :accept_details), :locals => {:task => task, :f => f} %>
54 </div> 54 </div>
55 <% end %> 55 <% end %>
56 56
57 <% if task.reject_details %> 57 <% if task.reject_details %>
58 <div id="on-reject-information-<%=task.id%>" style="display: none"> 58 <div id="on-reject-information-<%=task.id%>" style="display: none">
59 - <%= render :partial => partial_for_task_class(task.class, :reject_details), :locals => {:task => task, :f => f} %> 59 + <%= render :partial => partial_for_class(task.class, :reject_details), :locals => {:task => task, :f => f} %>
60 </div> 60 </div>
61 <% end %> 61 <% end %>
62 <% end %> 62 <% end %>
app/views/tasks/processed.rhtml
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 <ul> 7 <ul>
8 <% @tasks.each do |item| %> 8 <% @tasks.each do |item| %>
9 <li> 9 <li>
10 - <strong><%= item.information %></strong> <br/> 10 + <strong><%= task_information(item) %></strong> <br/>
11 <small> 11 <small>
12 <%= _('Created:') +' '+ show_date(item.created_at) %> 12 <%= _('Created:') +' '+ show_date(item.created_at) %>
13 &nbsp; &#151; &nbsp; 13 &nbsp; &#151; &nbsp;
config/initializers/action_tracker.rb
@@ -23,7 +23,28 @@ ActionTrackerConfig.verbs = { @@ -23,7 +23,28 @@ ActionTrackerConfig.verbs = {
23 }, 23 },
24 24
25 :upload_image => { 25 :upload_image => {
26 - :description => lambda { n_('uploaded 1 image<br />%{thumbnails}<br style="clear: both;" />', 'uploaded %{num} images<br />%{thumbnails}<br style="clear: both;" />', get_view_url.size) % { :num => get_view_url.size, :thumbnails => '{{ta.collect_group_with_index(:thumbnail_path){ |t,i| content_tag(:span, link_to(image_tag(t), ta.get_view_url[i]))}.last(3).join}}' } }, 26 + :description => lambda do
  27 + total = get_view_url.size
  28 + n_('uploaded 1 image', 'uploaded %d images', total) % total +
  29 + '<br />{{'+
  30 + 'ta.collect_group_with_index(:thumbnail_path) { |t,i|' +
  31 + " if ( #{total} == 1 );" +
  32 + ' link_to( image_tag(t), ta.get_view_url[i], :class => \'upimg\' );' +
  33 + ' else;' +
  34 + " pos = #{total}-i;" +
  35 + ' morethen2 = pos>2 ? \'morethen2\' : \'\';' +
  36 + ' morethen5 = pos>5 ? \'morethen5\' : \'\';' +
  37 + ' t = t.gsub(/(.*)(display)(.*)/, \'\\1thumb\\3\');' +
  38 + ' link_to( \'&nbsp;\', ta.get_view_url[i],' +
  39 + ' :style => "background-image:url(#{t})",' +
  40 + ' :class => "upimg pos#{pos} #{morethen2} #{morethen5}" );' +
  41 + ' end' +
  42 + '}.reverse.join}}' +
  43 + ( total > 5 ?
  44 + '<span class="more" onclick="this.parentNode.className+=\' show-all\'">' +
  45 + '&hellip;</span>' : '' ) +
  46 + '<br style="clear: both;" />'
  47 + end,
27 :type => :groupable 48 :type => :groupable
28 }, 49 },
29 50
config/initializers/plugins.rb
1 require 'noosfero/plugin' 1 require 'noosfero/plugin'
2 -require 'noosfero/plugin/acts_as_having_hotspots' 2 +require 'noosfero/plugin/hot_spot'
3 require 'noosfero/plugin/manager' 3 require 'noosfero/plugin/manager'
4 -require 'noosfero/plugin/context'  
5 require 'noosfero/plugin/active_record' 4 require 'noosfero/plugin/active_record'
6 require 'noosfero/plugin/mailer_base' 5 require 'noosfero/plugin/mailer_base'
7 Noosfero::Plugin.init_system if $NOOSFERO_LOAD_PLUGINS 6 Noosfero::Plugin.init_system if $NOOSFERO_LOAD_PLUGINS
config/routes.rb
@@ -19,6 +19,7 @@ ActionController::Routing::Routes.draw do |map| @@ -19,6 +19,7 @@ ActionController::Routing::Routes.draw do |map|
19 19
20 # -- just remember to delete public/index.html. 20 # -- just remember to delete public/index.html.
21 # You can have the root of your site routed by hooking up '' 21 # You can have the root of your site routed by hooking up ''
  22 + map.root :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
22 map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } 23 map.connect '', :controller => "home", :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
23 map.home 'site/:action', :controller => 'home' 24 map.home 'site/:action', :controller => 'home'
24 25
@@ -121,9 +122,12 @@ ActionController::Routing::Routes.draw do |map| @@ -121,9 +122,12 @@ ActionController::Routing::Routes.draw do |map|
121 # cache stuff - hack 122 # cache stuff - hack
122 map.cache 'public/:action/:id', :controller => 'public' 123 map.cache 'public/:action/:id', :controller => 'public'
123 124
  125 + map.connect ':profile/edit_comment/:id/*page', :controller => 'content_viewer', :action => 'edit_comment', :profile => /#{Noosfero.identifier_format}/
  126 +
124 # match requests for profiles that don't have a custom domain 127 # match requests for profiles that don't have a custom domain
125 map.homepage ':profile/*page', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } 128 map.homepage ':profile/*page', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } }
126 129
  130 +
127 # match requests for content in domains hosted for profiles 131 # match requests for content in domains hosted for profiles
128 map.connect '*page', :controller => 'content_viewer', :action => 'view_page' 132 map.connect '*page', :controller => 'content_viewer', :action => 'view_page'
129 133
db/migrate/20120825185219_add_user_agent_and_referrer_to_comments.rb 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +class AddUserAgentAndReferrerToComments < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :comments, :user_agent, :string
  4 + add_column :comments, :referrer, :string
  5 + end
  6 +
  7 + def self.down
  8 + remove_column :comments, :user_agent
  9 + remove_column :comments, :referrer
  10 + end
  11 +end
@@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
9 # 9 #
10 # It's strongly recommended to check this file into your version control system. 10 # It's strongly recommended to check this file into your version control system.
11 11
12 -ActiveRecord::Schema.define(:version => 20120823215007) do 12 +ActiveRecord::Schema.define(:version => 20120825185219) do
13 13
14 create_table "abuse_reports", :force => true do |t| 14 create_table "abuse_reports", :force => true do |t|
15 t.integer "reporter_id" 15 t.integer "reporter_id"
@@ -213,6 +213,8 @@ ActiveRecord::Schema.define(:version =&gt; 20120823215007) do @@ -213,6 +213,8 @@ ActiveRecord::Schema.define(:version =&gt; 20120823215007) do
213 t.string "ip_address" 213 t.string "ip_address"
214 t.boolean "spam" 214 t.boolean "spam"
215 t.string "source_type" 215 t.string "source_type"
  216 + t.string "user_agent"
  217 + t.string "referrer"
216 end 218 end
217 219
218 create_table "contact_lists", :force => true do |t| 220 create_table "contact_lists", :force => true do |t|
debian/changelog
  1 +noosfero (0.39.0~1) UNRELEASED; urgency=low
  2 +
  3 + * Pre-release to test the antispam mechanism.
  4 +
  5 + -- Antonio Terceiro <terceiro@debian.org> Thu, 30 Aug 2012 14:55:10 -0300
  6 +
  7 +noosfero (0.38.2) unstable; urgency=low
  8 +
  9 + * Bugfixes release
  10 +
  11 + -- Antonio Terceiro <terceiro@colivre.coop.br> Wed, 05 Sep 2012 10:07:58 -0300
  12 +
1 noosfero (0.38.1) unstable; urgency=low 13 noosfero (0.38.1) unstable; urgency=low
2 14
3 * Bugfixes release 15 * Bugfixes release
debian/control
@@ -62,8 +62,7 @@ Description: free web-based platform for social networks @@ -62,8 +62,7 @@ Description: free web-based platform for social networks
62 62
63 Package: noosfero-apache 63 Package: noosfero-apache
64 Architecture: all 64 Architecture: all
65 -Depends: apache2, debconf  
66 -Suggests: noosfero 65 +Depends: apache2, debconf, noosfero
67 Description: free web-based platform for social networks (apache frontend) 66 Description: free web-based platform for social networks (apache frontend)
68 Noosfero is a web platform for social and solidarity economy networks with 67 Noosfero is a web platform for social and solidarity economy networks with
69 blog, e-Porfolios, CMS, RSS, thematic discussion, events agenda and collective 68 blog, e-Porfolios, CMS, RSS, thematic discussion, events agenda and collective
debian/noosfero-console
@@ -2,4 +2,7 @@ @@ -2,4 +2,7 @@
2 2
3 set -e 3 set -e
4 4
5 -cd /usr/share/noosfero && su noosfero -c "./script/console production" 5 +environment="$1"
  6 +test -z "$environment" && environment=production
  7 +
  8 +cd /usr/share/noosfero && su noosfero -c "./script/console $environment"
etc/noosfero/varnish-noosfero.vcl
1 sub vcl_recv { 1 sub vcl_recv {
  2 + if (req.request == "GET" || req.request == "HEAD") {
2 if (req.http.Cookie) { 3 if (req.http.Cookie) {
3 - # We only care about the "_noosfero_session.*" cookie, used for  
4 - # authentication.  
5 - if (req.http.Cookie ~ "_noosfero_session.*" ) {  
6 - return (pass);  
7 - }  
8 - # Else strip all cookies 4 + # We only care about the "_noosfero_session.*" cookie, used for
  5 + # authentication.
  6 + if (req.http.Cookie !~ "_noosfero_session.*" ) {
  7 + # strip all cookies
9 unset req.http.Cookie; 8 unset req.http.Cookie;
  9 + }
10 } 10 }
  11 + }
11 } 12 }
12 13
13 sub vcl_error { 14 sub vcl_error {
features/edit_environment_templates.feature
@@ -8,7 +8,7 @@ Feature: edit environment templates @@ -8,7 +8,7 @@ Feature: edit environment templates
8 Scenario: See links to edit all templates 8 Scenario: See links to edit all templates
9 Given I am logged in as admin 9 Given I am logged in as admin
10 When I follow "Administration" 10 When I follow "Administration"
11 - And I follow "Edit Templates" 11 + And I follow "Profile templates"
12 Then I should see "Person template" link 12 Then I should see "Person template" link
13 And I should see "Community template" link 13 And I should see "Community template" link
14 And I should see "Enterprise template" link 14 And I should see "Enterprise template" link
@@ -17,28 +17,28 @@ Feature: edit environment templates @@ -17,28 +17,28 @@ Feature: edit environment templates
17 Scenario: Go to control panel of person template 17 Scenario: Go to control panel of person template
18 Given I am logged in as admin 18 Given I am logged in as admin
19 When I follow "Administration" 19 When I follow "Administration"
20 - And I follow "Edit Templates" 20 + And I follow "Profile templates"
21 And I follow "Person template" 21 And I follow "Person template"
22 Then I should be on Person template's control panel 22 Then I should be on Person template's control panel
23 23
24 Scenario: Go to control panel of enterprise template 24 Scenario: Go to control panel of enterprise template
25 Given I am logged in as admin 25 Given I am logged in as admin
26 When I follow "Administration" 26 When I follow "Administration"
27 - And I follow "Edit Templates" 27 + And I follow "Profile templates"
28 And I follow "Enterprise template" 28 And I follow "Enterprise template"
29 Then I should be on Enterprise template's control panel 29 Then I should be on Enterprise template's control panel
30 30
31 Scenario: Go to control panel of inactive enterprise template 31 Scenario: Go to control panel of inactive enterprise template
32 Given I am logged in as admin 32 Given I am logged in as admin
33 When I follow "Administration" 33 When I follow "Administration"
34 - And I follow "Edit Templates" 34 + And I follow "Profile templates"
35 And I follow "Inactive enterprise template" 35 And I follow "Inactive enterprise template"
36 Then I should be on Inactive Enterprise template's control panel 36 Then I should be on Inactive Enterprise template's control panel
37 37
38 Scenario: Go to control panel of community template 38 Scenario: Go to control panel of community template
39 Given I am logged in as admin 39 Given I am logged in as admin
40 When I follow "Administration" 40 When I follow "Administration"
41 - And I follow "Edit Templates" 41 + And I follow "Profile templates"
42 And I follow "Community template" 42 And I follow "Community template"
43 Then I should be on Community template's control panel 43 Then I should be on Community template's control panel
44 44
@@ -46,7 +46,7 @@ Feature: edit environment templates @@ -46,7 +46,7 @@ Feature: edit environment templates
46 Given that the default environment have no Inactive Enterprise template 46 Given that the default environment have no Inactive Enterprise template
47 And I am logged in as admin 47 And I am logged in as admin
48 When I follow "Administration" 48 When I follow "Administration"
49 - And I follow "Edit Templates" 49 + And I follow "Profile templates"
50 Then I should see "Person template" link 50 Then I should see "Person template" link
51 And I should see "Community template" link 51 And I should see "Community template" link
52 And I should see "Enterprise template" link 52 And I should see "Enterprise template" link
features/environment_name.feature
@@ -6,7 +6,7 @@ Feature: setting environment name @@ -6,7 +6,7 @@ Feature: setting environment name
6 Scenario: setting environment name through administration panel 6 Scenario: setting environment name through administration panel
7 Given I am logged in as admin 7 Given I am logged in as admin
8 When I follow "Administration" 8 When I follow "Administration"
9 - And I follow "Edit environment settings" 9 + And I follow "Environment settings"
10 And I fill in "Site name" with "My environment" 10 And I fill in "Site name" with "My environment"
11 And I press "Save" 11 And I press "Save"
12 Then I should see "My environment" within "title" 12 Then I should see "My environment" within "title"
features/export_users.feature
@@ -10,14 +10,14 @@ Feature: export users @@ -10,14 +10,14 @@ Feature: export users
10 Scenario: Export users as XML 10 Scenario: Export users as XML
11 Given I am logged in as admin 11 Given I am logged in as admin
12 When I follow "Administration" 12 When I follow "Administration"
13 - And I follow "Manage users" 13 + And I follow "Users"
14 And I follow "[XML]" 14 And I follow "[XML]"
15 Then I should see "ultraje" 15 Then I should see "ultraje"
16 16
17 Scenario: Export users as CSV 17 Scenario: Export users as CSV
18 Given I am logged in as admin 18 Given I am logged in as admin
19 When I follow "Administration" 19 When I follow "Administration"
20 - And I follow "Manage users" 20 + And I follow "Users"
21 And I follow "[CSV]" 21 And I follow "[CSV]"
22 Then I should see "name;email" 22 Then I should see "name;email"
23 And I should see "ultraje" 23 And I should see "ultraje"
features/manage_categories.feature
@@ -14,7 +14,7 @@ Feature: manage categories @@ -14,7 +14,7 @@ Feature: manage categories
14 | Development | services | 14 | Development | services |
15 And I am logged in as admin 15 And I am logged in as admin
16 And I am on the environment control panel 16 And I am on the environment control panel
17 - And I follow "Manage categories" 17 + And I follow "Categories"
18 18
19 Scenario: load only top level categories 19 Scenario: load only top level categories
20 Then I should see "Products" 20 Then I should see "Products"
features/plugins.feature
@@ -49,7 +49,7 @@ Feature: plugins @@ -49,7 +49,7 @@ Feature: plugins
49 When I go to the profile 49 When I go to the profile
50 Then I should see "Test plugin tab" 50 Then I should see "Test plugin tab"
51 And I go to the environment control panel 51 And I go to the environment control panel
52 - And I follow "Enable/disable plugins" 52 + And I follow "Plugins"
53 And I uncheck "Test plugin" 53 And I uncheck "Test plugin"
54 And I press "Save changes" 54 And I press "Save changes"
55 When I go to the Control panel 55 When I go to the Control panel
features/roles.feature
@@ -5,26 +5,26 @@ Feature: manage roles @@ -5,26 +5,26 @@ Feature: manage roles
5 Scenario: create new role 5 Scenario: create new role
6 Given I am logged in as admin 6 Given I am logged in as admin
7 And I go to the environment control panel 7 And I go to the environment control panel
8 - And I follow "Manage User roles" 8 + And I follow "User roles"
9 Then I should not see "My new role" 9 Then I should not see "My new role"
10 And I follow "Create a new role" 10 And I follow "Create a new role"
11 And I fill in "Name" with "My new role" 11 And I fill in "Name" with "My new role"
12 And I check "Publish content" 12 And I check "Publish content"
13 And I press "Create role" 13 And I press "Create role"
14 And I go to the environment control panel 14 And I go to the environment control panel
15 - And I follow "Manage User roles" 15 + And I follow "User roles"
16 Then I should see "My new role" 16 Then I should see "My new role"
17 17
18 Scenario: edit a role 18 Scenario: edit a role
19 Given I am logged in as admin 19 Given I am logged in as admin
20 And I go to the environment control panel 20 And I go to the environment control panel
21 - And I follow "Manage User roles" 21 + And I follow "User roles"
22 Then I should not see "My new role" 22 Then I should not see "My new role"
23 And I follow "Profile Administrator" 23 And I follow "Profile Administrator"
24 And I follow "Edit" 24 And I follow "Edit"
25 And I fill in "Name" with "My new role" 25 And I fill in "Name" with "My new role"
26 And I press "Save changes" 26 And I press "Save changes"
27 And I go to the environment control panel 27 And I go to the environment control panel
28 - And I follow "Manage User roles" 28 + And I follow "User roles"
29 Then I should see "My new role" 29 Then I should see "My new role"
30 And I should not see "Profile Administrator" 30 And I should not see "Profile Administrator"
features/send_email_to_environment_members.feature
@@ -18,7 +18,7 @@ Feature: send emails to environment members users @@ -18,7 +18,7 @@ Feature: send emails to environment members users
18 Scenario: Send e-mail to members 18 Scenario: Send e-mail to members
19 Given I am logged in as admin 19 Given I am logged in as admin
20 When I follow "Administration" 20 When I follow "Administration"
21 - And I follow "Manage users" 21 + And I follow "Users"
22 And I follow "Send e-mail to users" 22 And I follow "Send e-mail to users"
23 And I fill in "Subject" with "Hello, user!" 23 And I fill in "Subject" with "Hello, user!"
24 And I fill in "body" with "We have some news" 24 And I fill in "body" with "We have some news"
@@ -28,7 +28,7 @@ Feature: send emails to environment members users @@ -28,7 +28,7 @@ Feature: send emails to environment members users
28 Scenario: Not send e-mail to members if subject is blank 28 Scenario: Not send e-mail to members if subject is blank
29 Given I am logged in as admin 29 Given I am logged in as admin
30 When I follow "Administration" 30 When I follow "Administration"
31 - And I follow "Manage users" 31 + And I follow "Users"
32 And I follow "Send e-mail to users" 32 And I follow "Send e-mail to users"
33 And I fill in "body" with "We have some news" 33 And I fill in "body" with "We have some news"
34 When I press "Send" 34 When I press "Send"
@@ -37,7 +37,7 @@ Feature: send emails to environment members users @@ -37,7 +37,7 @@ Feature: send emails to environment members users
37 Scenario: Not send e-mail to members if body is blank 37 Scenario: Not send e-mail to members if body is blank
38 Given I am logged in as admin 38 Given I am logged in as admin
39 When I follow "Administration" 39 When I follow "Administration"
40 - And I follow "Manage users" 40 + And I follow "Users"
41 And I follow "Send e-mail to users" 41 And I follow "Send e-mail to users"
42 And I fill in "Subject" with "Hello, user!" 42 And I fill in "Subject" with "Hello, user!"
43 When I press "Send" 43 When I press "Send"
@@ -46,7 +46,7 @@ Feature: send emails to environment members users @@ -46,7 +46,7 @@ Feature: send emails to environment members users
46 Scenario: Cancel creation of mailing 46 Scenario: Cancel creation of mailing
47 Given I am logged in as admin 47 Given I am logged in as admin
48 When I follow "Administration" 48 When I follow "Administration"
49 - And I follow "Manage users" 49 + And I follow "Users"
50 And I follow "Send e-mail to users" 50 And I follow "Send e-mail to users"
51 Then I should be on /admin/users/send_mail 51 Then I should be on /admin/users/send_mail
52 When I follow "Cancel e-mail" 52 When I follow "Cancel e-mail"
lib/acts_as_faceted.rb
@@ -8,7 +8,7 @@ module ActsAsFaceted @@ -8,7 +8,7 @@ module ActsAsFaceted
8 # 8 #
9 #acts_as_faceted :fields => { 9 #acts_as_faceted :fields => {
10 # :f_type => {:label => _('Type'), :proc => proc{|klass| f_type_proc(klass)}}, 10 # :f_type => {:label => _('Type'), :proc => proc{|klass| f_type_proc(klass)}},
11 - # :f_published_at => {:type => :date, :label => _('Published date'), :queries => {'[* TO NOW-1YEARS/DAY]' => _("Older than one year"), 11 + # :f_published_at => {:type => :date, :label => _('Published date'), :queries => {'[* TO NOW-1YEARS/DAY]' => _("Older than one year"),
12 # '[NOW-1YEARS TO NOW/DAY]' => _("Last year"), '[NOW-1MONTHS TO NOW/DAY]' => _("Last month"), '[NOW-7DAYS TO NOW/DAY]' => _("Last week"), '[NOW-1DAYS TO NOW/DAY]' => _("Last day")}}, 12 # '[NOW-1YEARS TO NOW/DAY]' => _("Last year"), '[NOW-1MONTHS TO NOW/DAY]' => _("Last month"), '[NOW-7DAYS TO NOW/DAY]' => _("Last week"), '[NOW-1DAYS TO NOW/DAY]' => _("Last day")}},
13 # :f_profile_type => {:label => _('Author'), :proc => proc{|klass| f_profile_type_proc(klass)}}, 13 # :f_profile_type => {:label => _('Author'), :proc => proc{|klass| f_profile_type_proc(klass)}},
14 # :f_category => {:label => _('Categories')}}, 14 # :f_category => {:label => _('Categories')}},
@@ -36,7 +36,7 @@ module ActsAsFaceted @@ -36,7 +36,7 @@ module ActsAsFaceted
36 self.facets_order = options[:order] || self.facets.keys 36 self.facets_order = options[:order] || self.facets.keys
37 self.facets_results_containers = {:fields => 'facet_fields', :queries => 'facet_queries', :ranges => 'facet_ranges'} 37 self.facets_results_containers = {:fields => 'facet_fields', :queries => 'facet_queries', :ranges => 'facet_ranges'}
38 self.facets_option_for_solr = Hash[facets.select{ |id,data| ! data.has_key?(:queries) }].keys 38 self.facets_option_for_solr = Hash[facets.select{ |id,data| ! data.has_key?(:queries) }].keys
39 - self.facets_fields_for_solr = facets.map{ |id,data| {id => data[:type] || :facet} } 39 + self.facets_fields_for_solr = facets.map{ |id,data| {id => data[:type] || :facet} }
40 self.solr_fields_names = facets.map{ |id,data| id.to_s + '_' + get_solr_field_type(data[:type] || :facet) } 40 self.solr_fields_names = facets.map{ |id,data| id.to_s + '_' + get_solr_field_type(data[:type] || :facet) }
41 self.facet_category_query = options[:category_query] 41 self.facet_category_query = options[:category_query]
42 42
@@ -67,6 +67,7 @@ module ActsAsFaceted @@ -67,6 +67,7 @@ module ActsAsFaceted
67 raise 'Use map_facets_for before this method' if facet[:solr_field].nil? 67 raise 'Use map_facets_for before this method' if facet[:solr_field].nil?
68 facets_data = {} if facets_data.blank? # could be empty array 68 facets_data = {} if facets_data.blank? # could be empty array
69 solr_facet = to_solr_fields_names[facet[:solr_field]] 69 solr_facet = to_solr_fields_names[facet[:solr_field]]
  70 + unfiltered_facets_data ||= {}
70 71
71 if facet[:queries] 72 if facet[:queries]
72 container = facets_data[facets_results_containers[:queries]] 73 container = facets_data[facets_results_containers[:queries]]
@@ -158,13 +159,15 @@ module ActsAsFaceted @@ -158,13 +159,15 @@ module ActsAsFaceted
158 end 159 end
159 160
160 def facet_label(facet) 161 def facet_label(facet)
161 - _ facet[:label] 162 + return nil unless facet
  163 + _(facet[:label])
162 end 164 end
163 165
164 def facets_find_options(facets_selected = {}, options = {}) 166 def facets_find_options(facets_selected = {}, options = {})
165 browses = [] 167 browses = []
166 facets_selected ||= {} 168 facets_selected ||= {}
167 facets_selected.map do |id, value| 169 facets_selected.map do |id, value|
  170 + next unless facets[id.to_sym]
168 if value.kind_of?(Hash) 171 if value.kind_of?(Hash)
169 value.map do |label_id, value| 172 value.map do |label_id, value|
170 value.to_a.each do |value| 173 value.to_a.each do |value|
lib/needs_profile.rb
@@ -14,12 +14,12 @@ module NeedsProfile @@ -14,12 +14,12 @@ module NeedsProfile
14 profile || environment # prefers profile, but defaults to environment 14 profile || environment # prefers profile, but defaults to environment
15 end 15 end
16 16
17 - protected  
18 -  
19 def profile 17 def profile
20 @profile 18 @profile
21 end 19 end
22 20
  21 + protected
  22 +
23 def load_profile 23 def load_profile
24 @profile ||= environment.profiles.find_by_identifier(params[:profile]) 24 @profile ||= environment.profiles.find_by_identifier(params[:profile])
25 if @profile 25 if @profile
lib/noosfero.rb
@@ -2,7 +2,7 @@ require &#39;fast_gettext&#39; @@ -2,7 +2,7 @@ require &#39;fast_gettext&#39;
2 2
3 module Noosfero 3 module Noosfero
4 PROJECT = 'noosfero' 4 PROJECT = 'noosfero'
5 - VERSION = '0.38.1' 5 + VERSION = '0.39.0~1'
6 6
7 def self.pattern_for_controllers_in_directory(dir) 7 def self.pattern_for_controllers_in_directory(dir)
8 disjunction = controllers_in_directory(dir).join('|') 8 disjunction = controllers_in_directory(dir).join('|')
lib/noosfero/plugin.rb
@@ -12,17 +12,41 @@ class Noosfero::Plugin @@ -12,17 +12,41 @@ class Noosfero::Plugin
12 end 12 end
13 13
14 def init_system 14 def init_system
15 - Dir.glob(File.join(Rails.root, 'config', 'plugins', '*')).select do |entry| 15 + enabled_plugins = Dir.glob(File.join(Rails.root, 'config', 'plugins', '*'))
  16 + if Rails.env.test? && !enabled_plugins.include?(File.join(Rails.root, 'config', 'plugins', 'foo'))
  17 + enabled_plugins << File.join(Rails.root, 'plugins', 'foo')
  18 + end
  19 + enabled_plugins.select do |entry|
16 File.directory?(entry) 20 File.directory?(entry)
17 end.each do |dir| 21 end.each do |dir|
18 - Rails.configuration.controller_paths << File.join(dir, 'controllers')  
19 - ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers')  
20 - [ ActiveSupport::Dependencies.load_paths, $:].each do |path|  
21 - path << File.join(dir, 'models')  
22 - path << File.join(dir, 'lib') 22 + plugin_name = File.basename(dir)
  23 +
  24 + plugin_dependencies_ok = true
  25 + plugin_dependencies_file = File.join(dir, 'dependencies.rb')
  26 + if File.exists?(plugin_dependencies_file)
  27 + begin
  28 + require plugin_dependencies_file
  29 + rescue LoadError => ex
  30 + plugin_dependencies_ok = false
  31 + $stderr.puts "W: Noosfero plugin #{plugin_name} failed to load (#{ex})"
  32 + end
23 end 33 end
24 34
25 - klass(File.basename(dir)) 35 + if plugin_dependencies_ok
  36 + Rails.configuration.controller_paths << File.join(dir, 'controllers')
  37 + ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers')
  38 + controllers_folders = %w[public profile myprofile admin]
  39 + controllers_folders.each do |folder|
  40 + Rails.configuration.controller_paths << File.join(dir, 'controllers', folder)
  41 + ActiveSupport::Dependencies.load_paths << File.join(dir, 'controllers', folder)
  42 + end
  43 + [ ActiveSupport::Dependencies.load_paths, $:].each do |path|
  44 + path << File.join(dir, 'models')
  45 + path << File.join(dir, 'lib')
  46 + end
  47 +
  48 + klass(plugin_name)
  49 + end
26 end 50 end
27 end 51 end
28 52
@@ -196,28 +220,6 @@ class Noosfero::Plugin @@ -196,28 +220,6 @@ class Noosfero::Plugin
196 nil 220 nil
197 end 221 end
198 222
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. 223 # This method will be called just before a comment is saved to the database.
222 # 224 #
223 # It can modify the comment in several ways. In special, a plugin can call 225 # It can modify the comment in several ways. In special, a plugin can call
@@ -226,16 +228,53 @@ class Noosfero::Plugin @@ -226,16 +228,53 @@ class Noosfero::Plugin
226 # example: 228 # example:
227 # 229 #
228 # def filter_comment(comment) 230 # def filter_comment(comment)
229 - # comment.reject! if anti_spam_service.is_spam?(comment) 231 + # if user_not_logged_in
  232 + # comment.reject!
  233 + # end
230 # end 234 # end
231 # 235 #
232 def filter_comment(comment) 236 def filter_comment(comment)
233 end 237 end
234 238
235 - # This method will be called just after a comment has been saved to the  
236 - # database, so that a plugin can perform some action on it. 239 + # This method is called by the CommentHandler background job before sending
  240 + # the notification email. If the comment is marked as spam (i.e. by calling
  241 + # <tt>comment.spam!</tt>), then the notification email will *not* be sent.
  242 + #
  243 + # example:
  244 + #
  245 + # def check_comment_for_spam(comment)
  246 + # if anti_spam_service.is_spam?(comment)
  247 + # comment.spam!
  248 + # end
  249 + # end
  250 + #
  251 + def check_comment_for_spam(comment)
  252 + end
  253 +
  254 + # This method is called when the user manually marks a comment as SPAM. A
  255 + # plugin implementing this method should train its spam detection mechanism
  256 + # by submitting this comment as a confirmed spam.
  257 + #
  258 + # example:
  259 + #
  260 + # def comment_marked_as_spam(comment)
  261 + # anti_spam_service.train_with_spam(comment)
  262 + # end
  263 + #
  264 + def comment_marked_as_spam(comment)
  265 + end
  266 +
  267 + # This method is called when the user manually marks a comment a NOT SPAM. A
  268 + # plugin implementing this method should train its spam detection mechanism
  269 + # by submitting this coimment as a confirmed ham.
  270 + #
  271 + # example:
  272 + #
  273 + # def comment_marked_as_ham(comment)
  274 + # anti_spam_service.train_with_ham(comment)
  275 + # end
237 # 276 #
238 - def comment_saved(comment) 277 + def comment_marked_as_ham(comment)
239 end 278 end
240 279
241 # -> Adds fields to the signup form 280 # -> Adds fields to the signup form
@@ -281,4 +320,71 @@ class Noosfero::Plugin @@ -281,4 +320,71 @@ class Noosfero::Plugin
281 nil 320 nil
282 end 321 end
283 322
  323 + # -> Add an alternative authentication method.
  324 + # Your plugin have to make the access control and return the logged user.
  325 + # returns = User
  326 + def alternative_authentication
  327 + nil
  328 + end
  329 +
  330 + # -> Adds adicional link to make the user authentication
  331 + # returns = lambda block that creates html code
  332 + def alternative_authentication_link
  333 + nil
  334 + end
  335 +
  336 + # -> Allow or not user registration
  337 + # returns = boolean
  338 + def allow_user_registration
  339 + true
  340 + end
  341 +
  342 + # -> Allow or not password recovery by users
  343 + # returns = boolean
  344 + def allow_password_recovery
  345 + true
  346 + end
  347 +
  348 + # -> Adds fields to the login form
  349 + # returns = lambda block that creates html code
  350 + def login_extra_contents
  351 + nil
  352 + end
  353 +
  354 + def method_missing(method, *args, &block)
  355 + # This is a generic hotspot for all controllers on Noosfero.
  356 + # If any plugin wants to define filters to run on any controller, the name of
  357 + # the hotspot must be in the following form: <underscored_controller_name>_filters.
  358 + # Example: for ProfileController the hotspot is profile_controller_filters
  359 + #
  360 + # -> Adds a filter to a controller
  361 + # returns = { :type => type,
  362 + # :method_name => method_name,
  363 + # :options => {:opt1 => opt1, :opt2 => opt2},
  364 + # :block => Proc or lambda block}
  365 + # type = 'before_filter' or 'after_filter'
  366 + # method_name = The name of the filter
  367 + # option = Filter options, like :only or :except
  368 + # block = Block that the filter will call
  369 + if method.to_s =~ /^(.+)_controller_filters$/
  370 + []
  371 + # -> Removes the action button from the content
  372 + # returns = boolean
  373 + elsif method.to_s =~ /^content_remove_(#{content_actions.join('|')})$/
  374 + nil
  375 + # -> Expire the action button from the content
  376 + # returns = string with reason of expiration
  377 + elsif method.to_s =~ /^content_expire_(#{content_actions.join('|')})$/
  378 + nil
  379 + else
  380 + super
  381 + end
  382 + end
  383 +
  384 + private
  385 +
  386 + def content_actions
  387 + %w[edit delete spread locale suggest home]
  388 + end
  389 +
284 end 390 end
lib/noosfero/plugin/acts_as_having_hotspots.rb
@@ -1,44 +0,0 @@ @@ -1,44 +0,0 @@
1 -module ActsAsHavingHotspots  
2 - module ClassMethods  
3 - # Adding this feature to a class demands that it defines an instance method  
4 - # 'environment' that returns the environment associated with the instance.  
5 - def acts_as_having_hotspots  
6 - send :include, InstanceMethods  
7 - end  
8 -  
9 - module InstanceMethods  
10 - # Dispatches +event+ to each enabled plugin and collect the results.  
11 - #  
12 - # Returns an Array containing the objects returned by the event method in  
13 - # each plugin. This array is compacted (i.e. nils are removed) and flattened  
14 - # (i.e. elements of arrays are added to the resulting array). For example, if  
15 - # the enabled plugins return 1, 0, nil, and [1,2,3], then this method will  
16 - # return [1,0,1,2,3]  
17 - #  
18 - def dispatch(event, *args)  
19 - enabled_plugins.map { |plugin| plugin.send(event, *args) }.compact.flatten  
20 - end  
21 -  
22 - # Dispatch without flatten since scopes are executed if you run flatten on them  
23 - def dispatch_scopes(event, *args)  
24 - enabled_plugins.map { |plugin| plugin.send(event, *args) }.compact  
25 - end  
26 -  
27 - def enabled_plugins  
28 - Thread.current[:enabled_plugins] ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin_name|  
29 - plugin = plugin_name.constantize.new  
30 - plugin.context = context  
31 - plugin  
32 - end  
33 - end  
34 -  
35 - if !method_defined?(:context)  
36 - define_method(:context) do  
37 - Noosfero::Plugin::Context.new  
38 - end  
39 - end  
40 - end  
41 - end  
42 -end  
43 -  
44 -ActiveRecord::Base.send(:extend, ActsAsHavingHotspots::ClassMethods)  
lib/noosfero/plugin/context.rb
@@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
1 -# This class defines the interface to important context information from the  
2 -# controller that can be accessed by plugins  
3 -class Noosfero::Plugin::Context  
4 -  
5 - def initialize(controller = ApplicationController.new)  
6 - @controller = controller  
7 - end  
8 -  
9 - delegate :profile, :request, :response, :environment, :params, :session, :user, :logged_in?, :to => :controller  
10 -  
11 - protected  
12 -  
13 - attr_reader :controller  
14 -  
15 -end  
lib/noosfero/plugin/hot_spot.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +# This module must be included by classes that contain Noosfero plugin
  2 +# hotspots.
  3 +#
  4 +# Classes that include this module *must* provide a method called
  5 +# <tt>environment</tt> which returns an intance of Environment. This
  6 +# Environment will be used to determine which plugins are enabled and therefore
  7 +# which plugins should be instantiated.
  8 +module Noosfero::Plugin::HotSpot
  9 +
  10 + # Returns an instance of Noosfero::Plugin::Manager.
  11 + #
  12 + # This which is intantiated on the first call and just returned in subsequent
  13 + # calls.
  14 + def plugins
  15 + @plugins ||= Noosfero::Plugin::Manager.new(environment, self)
  16 + end
  17 +
  18 +end
lib/noosfero/plugin/manager.rb
1 class Noosfero::Plugin::Manager 1 class Noosfero::Plugin::Manager
2 2
3 - extend ActsAsHavingHotspots::ClassMethods  
4 - acts_as_having_hotspots  
5 - 3 + attr_reader :environment
6 attr_reader :context 4 attr_reader :context
7 5
8 - delegate :environment, :to => :context 6 + def initialize(environment, context)
  7 + @environment = environment
  8 + @context = context
  9 + end
  10 +
9 delegate :each, :to => :enabled_plugins 11 delegate :each, :to => :enabled_plugins
10 include Enumerable 12 include Enumerable
11 13
12 - def initialize(controller)  
13 - @context = Noosfero::Plugin::Context.new(controller)  
14 - Thread.current[:enabled_plugins] = (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin_name|  
15 - plugin = plugin_name.constantize.new  
16 - plugin.context = context  
17 - plugin 14 + # Dispatches +event+ to each enabled plugin and collect the results.
  15 + #
  16 + # Returns an Array containing the objects returned by the event method in
  17 + # each plugin. This array is compacted (i.e. nils are removed) and flattened
  18 + # (i.e. elements of arrays are added to the resulting array). For example, if
  19 + # the enabled plugins return 1, 0, nil, and [1,2,3], then this method will
  20 + # return [1,0,1,2,3]
  21 + #
  22 + def dispatch(event, *args)
  23 + dispatch_without_flatten(event, *args).flatten
  24 + end
  25 +
  26 + def dispatch_without_flatten(event, *args)
  27 + map { |plugin| plugin.send(event, *args) }.compact
  28 + end
  29 +
  30 + alias :dispatch_scopes :dispatch_without_flatten
  31 +
  32 + def enabled_plugins
  33 + @enabled_plugins ||= (Noosfero::Plugin.all & environment.enabled_plugins).map do |plugin|
  34 + p = plugin.constantize.new
  35 + p.context = context
  36 + p
18 end 37 end
19 end 38 end
20 39
lib/noosfero/plugin/routes.rb
1 -Dir.glob(File.join(Rails.root, 'config', 'plugins', '*', 'controllers')) do |dir|  
2 - plugin_name = File.basename(File.dirname(dir)) 1 +plugins_root = Rails.env.test? ? 'plugins' : File.join('config', 'plugins')
  2 +
  3 +Dir.glob(File.join(Rails.root, plugins_root, '*', 'controllers')) do |controllers_dir|
  4 + prefixes_by_folder = {'public' => 'plugin',
  5 + 'profile' => 'profile/:profile/plugin',
  6 + 'myprofile' => 'myprofile/:profile/plugin',
  7 + 'admin' => 'admin/plugin'}
  8 +
  9 + controllers_by_folder = prefixes_by_folder.keys.inject({}) do |hash, folder|
  10 + hash.merge!({folder => Dir.glob(File.join(controllers_dir, folder, '*')).map {|full_names| File.basename(full_names).gsub(/_controller.rb$/,'')}})
  11 + end
  12 +
  13 + plugin_name = File.basename(File.dirname(controllers_dir))
  14 +
  15 + controllers_by_folder.each do |folder, controllers|
  16 + controllers.each do |controller|
  17 + controller_name = controller.gsub("#{plugin_name}_plugin_",'')
  18 + map.connect "#{prefixes_by_folder[folder]}/#{plugin_name}/#{controller_name}/:action/:id", :controller => controller
  19 + end
  20 + end
  21 +
3 map.connect 'plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin' 22 map.connect 'plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin'
4 - map.connect 'profile/:profile/plugins/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_profile' 23 + map.connect 'profile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_profile'
5 map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile' 24 map.connect 'myprofile/:profile/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_myprofile'
6 map.connect 'admin/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_admin' 25 map.connect 'admin/plugin/' + plugin_name + '/:action/:id', :controller => plugin_name + '_plugin_admin'
7 end 26 end
8 -  
lib/tasks/data.rake
@@ -3,7 +3,7 @@ namespace :db do @@ -3,7 +3,7 @@ namespace :db do
3 task :minimal do 3 task :minimal do
4 sh './script/runner', "Environment.create!(:name => 'Noosfero', :is_default => true)" 4 sh './script/runner', "Environment.create!(:name => 'Noosfero', :is_default => true)"
5 unless ENV['NOOSFERO_DOMAIN'].blank? 5 unless ENV['NOOSFERO_DOMAIN'].blank?
6 - sh './script/runner', "environment.domains << Domain.new(:name => ENV['NOOSFERO_DOMAIN'])" 6 + sh './script/runner', "Environment.default.domains << Domain.new(:name => ENV['NOOSFERO_DOMAIN'])"
7 end 7 end
8 end 8 end
9 end 9 end
lib/tasks/plugins_tests.rake
1 -@disabled_plugins = Dir.glob(File.join(Rails.root, 'plugins', '*')).map { |file| File.basename(file)} - Dir.glob(File.join(Rails.root, 'config', 'plugins', '*')).map { |file| File.basename(file)}  
2 -@disabled_plugins.delete('template')  
3 -  
4 -def define_task(test, plugins_folder='plugins', plugin = '*')  
5 - test_files = Dir.glob(File.join(Rails.root, plugins_folder, plugin, 'test', test[:folder], '**', '*_test.rb'))  
6 - desc 'Runs ' + (plugin != '*' ? plugin : 'plugins') + ' ' + test[:name] + ' tests'  
7 - Rake::TestTask.new(test[:name].to_sym => 'db:test:plugins:prepare') do |t|  
8 - t.libs << 'test'  
9 - t.test_files = test_files  
10 - t.verbose = true  
11 - end 1 +all_plugins = Dir.glob('plugins/*').map { |f| File.basename(f) } - ['template']
  2 +def enabled_plugins
  3 + Dir.glob('config/plugins/*').map { |f| File.basename(f) } - ['README']
12 end 4 end
  5 +disabled_plugins = all_plugins - enabled_plugins
13 6
14 task 'db:test:plugins:prepare' do 7 task 'db:test:plugins:prepare' do
15 - Rake::Task['db:test:prepare'].invoke  
16 - sh 'rake db:migrate RAILS_ENV=test SCHEMA=/dev/null' 8 + if Dir.glob('config/plugins/*/db/migrate/*.rb').empty?
  9 + puts "I: skipping database setup, enabled plugins have no migrations"
  10 + else
  11 + Rake::Task['db:test:prepare'].invoke
  12 + sh 'rake db:migrate RAILS_ENV=test SCHEMA=/dev/null'
  13 + end
17 end 14 end
18 15
19 -namespace :test do  
20 - namespace :noosfero_plugins do  
21 - tasks = [  
22 - {:name => :available, :folder => 'plugins'},  
23 - {:name => :enabled, :folder => File.join('config', 'plugins')}  
24 - ]  
25 - tests = [  
26 - {:name => 'units', :folder => 'unit'},  
27 - {:name => 'functionals', :folder => 'functional'},  
28 - {:name => 'integration', :folder => 'integration'}  
29 - ]  
30 -  
31 - tasks.each do |t|  
32 - namespace t[:name] do  
33 - tests.each do |test|  
34 - define_task(test, t[:folder])  
35 - end  
36 - end  
37 - end 16 +def plugin_name(plugin)
  17 + "#{plugin} plugin"
  18 +end
  19 +
  20 +def run_tests(name, files_glob)
  21 + files = Dir.glob(files_glob)
  22 + if files.empty?
  23 + puts "I: no tests to run (#{name})"
  24 + else
  25 + sh 'testrb', '-Itest', *files
  26 + end
  27 +end
38 28
39 - plugins = Dir.glob(File.join(Rails.root, 'plugins', '*')).map {|path| File.basename(path)} 29 +def run_cucumber(name, profile, files_glob)
  30 + files = Dir.glob(files_glob)
  31 + if files.empty?
  32 + puts "I: no tests to run #{name}"
  33 + else
  34 + sh 'xvfb-run', 'ruby', '-S', 'cucumber', '--profile', profile, '--format', ENV['CUCUMBER_FORMAT'] || 'progress' , *features
  35 + end
  36 +end
40 37
41 - plugins.each do |plugin_name|  
42 - namespace plugin_name do  
43 - tests.each do |test|  
44 - define_task(test, 'plugins', plugin_name)  
45 - end  
46 - end 38 +def plugin_test_task(name, plugin, files_glob)
  39 + desc "Run #{name} tests for #{plugin_name(plugin)}"
  40 + task name => 'db:test:plugins:prepare' do |t|
  41 + run_tests t.name, files_glob
  42 + end
  43 +end
  44 +
  45 +def plugin_cucumber_task(plugin, files_glob)
  46 + task :cucumber => 'db:test:plugins:prepare' do |t|
  47 + run_cucumber t.name, :default, files_glob
  48 + end
  49 +end
47 50
48 - dependencies = []  
49 - tests.each do |test|  
50 - dependencies << plugin_name+':'+test[:name] 51 +def plugin_selenium_task(plugin, files_glob)
  52 + task :selenium => 'db:test:plugins:prepare' do |t|
  53 + run_cucumber t.name, :selenium, files_glob
  54 + end
  55 +end
  56 +
  57 +def test_sequence_task(name, plugin, *tasks)
  58 + desc "Run all tests for #{plugin_name(plugin)}"
  59 + task name do
  60 + failed = []
  61 + tasks.each do |task|
  62 + begin
  63 + Rake::Task['test:noosfero_plugins:' + task.to_s].invoke
  64 + rescue Exception => ex
  65 + puts ex
  66 + failed << task
51 end 67 end
52 - task plugin_name => dependencies  
53 end 68 end
54 -  
55 - task :temp_enable_plugins do  
56 - system('./script/noosfero-plugins enableall') 69 + unless failed.empty?
  70 + fail 'Tests failed: ' + failed.join(', ')
57 end 71 end
  72 + end
  73 +end
58 74
59 - task :rollback_temp_enable_plugins do  
60 - @disabled_plugins.each { |plugin| system('./script/noosfero-plugins disable ' + plugin)} 75 +namespace :test do
  76 + namespace :noosfero_plugins do
  77 + all_plugins.each do |plugin|
  78 + namespace plugin do
  79 + plugin_test_task :units, plugin, "plugins/#{plugin}/test/unit/**/*.rb"
  80 + plugin_test_task :functionals, plugin, "plugins/#{plugin}/test/functional/**/*.rb"
  81 + plugin_test_task :integration, plugin, "plugins/#{plugin}/test/integration/**/*.rb"
  82 + plugin_cucumber_task plugin, "plugins/#{plugin}/features/**/*.feature"
  83 + plugin_selenium_task plugin, "plugins/#{plugin}/features/**/*.feature"
  84 + end
  85 +
  86 + test_sequence_task(plugin, plugin, "#{plugin}:units", "#{plugin}:functionals", "#{plugin}:integration", "#{plugin}:cucumber", "#{plugin}:selenium") # FIXME missing cucumber and selenium
61 end 87 end
62 88
63 - task :units => 'available:units'  
64 - task :functionals => 'available:functionals'  
65 - task :integration => 'available:integration'  
66 - task :available do  
67 - Rake::Task['test:noosfero_plugins:temp_enable_plugins'].invoke  
68 - begin  
69 - Rake::Task['test:noosfero_plugins:units'].invoke  
70 - Rake::Task['test:noosfero_plugins:functionals'].invoke  
71 - Rake::Task['test:noosfero_plugins:integration'].invoke  
72 - rescue 89 + { :units => :unit , :functionals => :functional , :integration => :integration }.each do |taskname,folder|
  90 + task taskname => 'db:test:plugins:prepare' do |t|
  91 + run_tests t.name, "plugins/{#{enabled_plugins.join(',')}}/test/#{folder}/**/*.rb"
73 end 92 end
74 - Rake::Task['test:noosfero_plugins:rollback_temp_enable_plugins'].invoke  
75 end 93 end
76 - task :enabled => ['enabled:units', 'enabled:functionals', 'enabled:integration']  
77 94
  95 + task :cucumber => 'db:test:plugins:prepare' do |t|
  96 + run_cucumber t.name, :default, "plugins/{#{enabled_plugins.join(',')}}/features/**/*.features"
  97 + end
78 98
79 - namespace :cucumber do  
80 - task :enabled do  
81 - features = Dir.glob('config/plugins/*/features/*.feature')  
82 - if features.empty?  
83 - puts "No acceptance tests for enabled plugins, skipping"  
84 - else  
85 - ruby '-S', 'cucumber', '--format', ENV['CUCUMBER_FORMAT'] || 'progress' , *features  
86 - end  
87 - end 99 + task :selenium => 'db:test:plugins:prepare' do |t|
  100 + run_cucumber t.name, :selenium, "plugins/{#{enabled_plugins.join(',')}}/features/**/*.features"
88 end 101 end
89 102
90 - namespace :selenium do  
91 - task :enabled do  
92 - features = Dir.glob('config/plugins/*/features/*.feature')  
93 - if features.empty?  
94 - puts "No acceptance tests for enabled plugins, skipping"  
95 - else  
96 - sh 'xvfb-run', 'ruby', '-S', 'cucumber', '--profile', 'selenium', '--format', ENV['CUCUMBER_FORMAT'] || 'progress' , *features  
97 - end  
98 - end 103 + task :temp_enable_all_plugins do
  104 + sh './script/noosfero-plugins', 'enableall'
99 end 105 end
100 106
  107 + task :rollback_enable_all_plugins do
  108 + sh './script/noosfero-plugins', 'disable', *disabled_plugins
  109 + end
101 end 110 end
102 111
103 - task :noosfero_plugins => %w[ noosfero_plugins:available noosfero_plugins:cucumber:enabled noosfero_plugins:selenium:enabled ] 112 + test_sequence_task(:noosfero_plugins, '*', :temp_enable_all_plugins, :units, :functionals, :integration, :cucumber, :selenium, :rollback_enable_all_plugins)
104 113
105 end 114 end
106 -  
lib/tasks/release.rake
@@ -95,6 +95,9 @@ EOF @@ -95,6 +95,9 @@ EOF
95 sh "cd #{target} && dpkg-buildpackage -us -uc -b" 95 sh "cd #{target} && dpkg-buildpackage -us -uc -b"
96 end 96 end
97 97
  98 + desc "Build Debian packages (shorcut)"
  99 + task :deb => :debian_packages
  100 +
98 desc 'Test Debian package' 101 desc 'Test Debian package'
99 task 'debian:test' => :debian_packages do 102 task 'debian:test' => :debian_packages do
100 Dir.chdir 'pkg' do 103 Dir.chdir 'pkg' do
plugins/anti_spam/controllers/anti_spam_plugin_admin_controller.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +class AntiSpamPluginAdminController < AdminController
  2 + append_view_path File.join(File.dirname(__FILE__) + '/../views')
  3 +
  4 + def index
  5 + @settings = AntiSpamPlugin::Settings.new(environment, params[:settings])
  6 + if request.post?
  7 + @settings.save!
  8 + redirect_to :action => 'index'
  9 + end
  10 + end
  11 +
  12 +end
plugins/anti_spam/dependencies.rb 0 → 100644
@@ -0,0 +1 @@ @@ -0,0 +1 @@
  1 +require 'rakismet'
plugins/anti_spam/lib/anti_spam_plugin.rb 0 → 100644
@@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
  1 +class AntiSpamPlugin < Noosfero::Plugin
  2 +
  3 + def self.plugin_name
  4 + "AntiSpam"
  5 + end
  6 +
  7 + def self.plugin_description
  8 + _("Checks comments against a spam checking service compatible with the Akismet API")
  9 + end
  10 +
  11 + def check_comment_for_spam(comment)
  12 + if rakismet_call(comment, :spam?)
  13 + comment.spam = true
  14 + comment.save!
  15 + end
  16 + end
  17 +
  18 + def comment_marked_as_spam(comment)
  19 + rakismet_call(comment, :spam!)
  20 + end
  21 +
  22 + def comment_marked_as_ham(comment)
  23 + rakismet_call(comment, :ham!)
  24 + end
  25 +
  26 + protected
  27 +
  28 + def rakismet_call(comment, op)
  29 + settings = AntiSpamPlugin::Settings.new(comment.environment)
  30 +
  31 + Rakismet.host = settings.host
  32 + Rakismet.key = settings.api_key
  33 + Rakismet.url = comment.environment.top_url
  34 +
  35 + submission = AntiSpamPlugin::CommentWrapper.new(comment)
  36 + submission.send(op)
  37 + end
  38 +
  39 +end
plugins/anti_spam/lib/anti_spam_plugin/comment_wrapper.rb 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +class AntiSpamPlugin::CommentWrapper < Struct.new(:comment)
  2 +
  3 + delegate :author_name, :author_email, :title, :body, :ip_address, :user_agent, :referrer, :to => :comment
  4 +
  5 + include Rakismet::Model
  6 +
  7 + alias :author :author_name
  8 + alias :user_ip :ip_address
  9 + alias :content :body
  10 +
  11 +end
plugins/anti_spam/lib/anti_spam_plugin/settings.rb 0 → 100644
@@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
  1 +class AntiSpamPlugin::Settings
  2 +
  3 + def initialize(environment, attributes = nil)
  4 + @environment = environment
  5 + attributes ||= {}
  6 + attributes.each do |k,v|
  7 + self.send("#{k}=", v)
  8 + end
  9 + end
  10 +
  11 + def settings
  12 + @environment.settings[:anti_spam_plugin] ||= {}
  13 + end
  14 +
  15 + def host
  16 + settings[:host] ||= 'api.antispam.typepad.com'
  17 + end
  18 +
  19 + def host=(value)
  20 + settings[:host] = value
  21 + end
  22 +
  23 + def api_key
  24 + settings[:api_key]
  25 + end
  26 +
  27 + def api_key=(value)
  28 + settings[:api_key] = value
  29 + end
  30 +
  31 + def save!
  32 + @environment.save!
  33 + end
  34 +
  35 +end
plugins/anti_spam/lib/anti_spam_plugin/spaminator.rb 0 → 100644
@@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
  1 +require 'benchmark'
  2 +
  3 +class AntiSpamPlugin::Spaminator
  4 +
  5 + class << self
  6 + def run(environment)
  7 + instance = new(environment)
  8 + instance.run
  9 + end
  10 +
  11 + def benchmark(environment)
  12 + puts Benchmark.measure { run(environment) }
  13 + end
  14 + end
  15 +
  16 +
  17 + def initialize(environment)
  18 + @environment = environment
  19 + end
  20 +
  21 + def run
  22 + start_time = Time.now
  23 +
  24 + process_all_comments
  25 + process_all_people
  26 + process_people_without_network
  27 +
  28 + finish(start_time)
  29 + end
  30 +
  31 + protected
  32 +
  33 + def finish(start_time)
  34 + @environment.settings[:spaminator_last_run] = start_time
  35 + @environment.save!
  36 + end
  37 +
  38 + def conditions(table)
  39 + last_run = @environment.settings[:spaminator_last_run]
  40 + if last_run
  41 + ["profiles.environment_id = ? AND #{table}.created_at > ?", @environment.id, last_run]
  42 + else
  43 + [ "profiles.environment_id = ?", @environment.id]
  44 + end
  45 + end
  46 +
  47 + def process_all_comments
  48 + puts 'Processing comments ...'
  49 + i = 0
  50 + comments = Comment.joins("JOIN articles ON (comments.source_id = articles.id AND comments.source_type = 'Article') JOIN profiles ON (profiles.id = articles.profile_id)").where(conditions(:comments))
  51 + total = comments.count
  52 + comments.find_each do |comment|
  53 + puts "Comment #{i += 1}/#{total} (#{100*i/total}%)"
  54 + process_comment(comment)
  55 + end
  56 + end
  57 +
  58 + def process_all_people
  59 + puts 'Processing people ...'
  60 + i = 0
  61 + people = Person.where(conditions(:profiles))
  62 + total = people.count
  63 + people.find_each do |person|
  64 + puts "Person #{i += 1}/#{total} (#{100*i/total}%)"
  65 + process_person(person)
  66 + end
  67 + end
  68 +
  69 + def process_comment(comment)
  70 + comment.check_for_spam
  71 +
  72 + # TODO several comments with the same content:
  73 + # → disable author
  74 + # → mark all of them as spam
  75 +
  76 + # TODO check comments that contains URL's
  77 + end
  78 +
  79 + def process_person(person)
  80 + # person is author of more than 2 comments marked as spam
  81 + # → burn
  82 + #
  83 + number_of_spam_comments = Comment.spam.where(author_id => person.id).count
  84 + if number_of_spam_comments > 2
  85 + mark_as_spammer(person)
  86 + end
  87 + end
  88 +
  89 + def process_people_without_network
  90 + # people who signed up more than one month ago, have no friends and <= 1
  91 + # communities
  92 + #
  93 + # → burn
  94 + # → mark their comments as spam
  95 + #
  96 + Person.where(:environment_id => @environment.id).where(['created_at < ?', Time.now - 1.month]).find_each do |person|
  97 + # TODO progress indicator - see process_all_people above
  98 + number_of_friends = person.friends.count
  99 + number_of_communities = person.communities.count
  100 + if number_of_friends == 0 && number_of_communities <= 1
  101 + mark_as_spammer(person)
  102 + Comment.where(:author_id => person.id).find_each do |comment|
  103 + comment.spam!
  104 + end
  105 + end
  106 + end
  107 + end
  108 +
  109 + def mark_as_spammer(person)
  110 + # FIXME create an AbuseComplaint and finish instead of calling
  111 + # Person#disable directly
  112 + person.disable
  113 + end
  114 +
  115 +end
plugins/anti_spam/test/unit/anti_spam_plugin/comment_wrapper_test.rb 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +require 'test_helper'
  2 +
  3 +class AntiSpamPluginCommentWrapperTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @comment = Comment.new(
  7 + :title => 'comment title',
  8 + :body => 'comment body',
  9 + :name => 'foo',
  10 + :email => 'foo@example.com',
  11 + :ip_address => '1.2.3.4',
  12 + :user_agent => 'Some Good Browser (I hope)',
  13 + :referrer => 'http://noosfero.org/'
  14 + )
  15 + @wrapper = AntiSpamPlugin::CommentWrapper.new(@comment)
  16 + end
  17 +
  18 + should 'use Rakismet::Model' do
  19 + assert_includes @wrapper.class.included_modules, Rakismet::Model
  20 + end
  21 +
  22 + should 'get contents' do
  23 + assert_equal @comment.body, @wrapper.content
  24 + end
  25 +
  26 + should 'get author name' do
  27 + assert_equal @comment.author_name, @wrapper.author
  28 + end
  29 +
  30 + should 'get author email' do
  31 + assert_equal @comment.author_email, @wrapper.author_email
  32 + end
  33 +
  34 + should 'get IP address' do
  35 + assert_equal @comment.ip_address, @wrapper.user_ip
  36 + end
  37 +
  38 + should 'get User-Agent' do
  39 + assert_equal @comment.user_agent, @wrapper.user_agent
  40 + end
  41 +
  42 + should 'get get Referrer' do
  43 + assert_equal @comment.referrer, @wrapper.referrer
  44 + end
  45 +
  46 +end
plugins/anti_spam/test/unit/anti_spam_plugin/settings_test.rb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +require 'test_helper'
  2 +
  3 +class AntiSpamSettingsTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.new
  7 + @settings = AntiSpamPlugin::Settings.new(@environment)
  8 + end
  9 +
  10 + should 'store setttings in environment' do
  11 + @settings.host = 'foo.com'
  12 + @settings.api_key = '1234567890'
  13 + assert_equal 'foo.com', @environment.settings[:anti_spam_plugin][:host]
  14 + assert_equal '1234567890', @environment.settings[:anti_spam_plugin][:api_key]
  15 + assert_equal 'foo.com', @settings.host
  16 + assert_equal '1234567890', @settings.api_key
  17 + end
  18 +
  19 + should 'save environment on save' do
  20 + @environment.expects(:save!)
  21 + @settings.save!
  22 + end
  23 +
  24 + should 'use TypePad AntiSpam by default' do
  25 + assert_equal 'api.antispam.typepad.com', @settings.host
  26 + end
  27 +
  28 +
  29 +end
plugins/anti_spam/test/unit/anti_spam_plugin/spaminator_test.rb 0 → 100644
@@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
  1 +require 'test_helper'
  2 +
  3 +class AntiSpamPluginSpaminatorTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + @environment = Environment.new
  7 + @environment.id = 99
  8 + @spaminator = AntiSpamPlugin::Spaminator.new(@environment)
  9 + @spaminator.stubs(:puts)
  10 + @now = Time.now
  11 + Time.stubs(:now).returns(@now)
  12 + end
  13 +
  14 + should 'search everything in the first run' do
  15 + assert_equal(['profiles.environment_id = ?',99], @spaminator.send(:conditions, nil))
  16 + end
  17 +
  18 + should 'search using recorded last date' do
  19 + @environment.settings[:spaminator_last_run] = @now
  20 + assert_equal(['profiles.environment_id = ? AND table.created_at > ?', 99, @now], @spaminator.send(:conditions, 'table'))
  21 + end
  22 +
  23 + should 'record time of last run in environment' do
  24 + @spaminator.expects(:process_all_comments)
  25 + @spaminator.expects(:process_all_people)
  26 + @environment.stubs(:save!)
  27 + @spaminator.run
  28 + assert_equal @now, @environment.settings[:spaminator_last_run]
  29 + end
  30 +
  31 + should 'find all comments' do
  32 + @spaminator.stubs(:process_comment)
  33 + @spaminator.send :process_all_comments
  34 + end
  35 +
  36 + should 'find all people' do
  37 + @spaminator.stubs(:process_person)
  38 + @spaminator.send :process_all_people
  39 + end
  40 +
  41 + should 'find all comments newer than a date' do
  42 + @environment.settings[:spaminator_last_run] = Time.now - 1.month
  43 + @spaminator.stubs(:process_comment)
  44 + @spaminator.send :process_all_comments
  45 + end
  46 +
  47 + should 'find all people newer than a date' do
  48 + @environment.settings[:spaminator_last_run] = Time.now - 1.month
  49 + @spaminator.stubs(:process_person)
  50 + @spaminator.send :process_all_people
  51 + end
  52 +
  53 +end
plugins/anti_spam/test/unit/anti_spam_plugin_test.rb 0 → 100644
@@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
  1 +require 'test_helper'
  2 +
  3 +class AntiSpamPluginTest < ActiveSupport::TestCase
  4 +
  5 + def setup
  6 + profile = fast_create(Profile)
  7 + article = fast_create(TextileArticle, :profile_id => profile.id)
  8 + @comment = fast_create(Comment, :source_id => article.id, :source_type => 'Article')
  9 +
  10 + @settings = AntiSpamPlugin::Settings.new(@comment.environment)
  11 + @settings.api_key = 'b8b80ddb8084062d0c9119c945ce3bc3'
  12 + @settings.save!
  13 +
  14 + @plugin = AntiSpamPlugin.new
  15 + @plugin.context = @comment
  16 + end
  17 +
  18 + should 'check for spam and mark comment as spam if server says it is spam' do
  19 + AntiSpamPlugin::CommentWrapper.any_instance.expects(:spam?).returns(true)
  20 + @comment.expects(:save!)
  21 +
  22 + @plugin.check_comment_for_spam(@comment)
  23 + assert @comment.spam
  24 + end
  25 +
  26 + should 'report spam' do
  27 + AntiSpamPlugin::CommentWrapper.any_instance.expects(:spam!)
  28 + @plugin.comment_marked_as_spam(@comment)
  29 + end
  30 +
  31 + should 'report ham' do
  32 + AntiSpamPlugin::CommentWrapper.any_instance.expects(:ham!)
  33 + @plugin.comment_marked_as_ham(@comment)
  34 + end
  35 +
  36 +end
plugins/anti_spam/views/anti_spam_plugin_admin/index.rhtml 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +<h1><%= _('AntiSpam settings')%></h1>
  2 +
  3 +<% form_for(:settings) do |f| %>
  4 +
  5 + <%= labelled_form_field _('Host'), f.text_field(:host) %>
  6 +
  7 + <%= labelled_form_field _('API key'), f.text_field(:api_key, :size => 40) %>
  8 +
  9 + <% button_bar do %>
  10 + <%= submit_button(:save, _('Save'), :cancel => {:controller => 'plugins', :action => 'index'}) %>
  11 + <% end %>
  12 +
  13 +<% end %>
  14 +