Commit c70a99e99358023a5b07efa84bd7756c5ab3f5e3

Authored by Leandro Santos
2 parents 6c96c297 c5b5a296

Merge branch 'master' into AI3205-comment-paragraph

Showing 1052 changed files with 54048 additions and 21919 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 1052 files displayed.

1 source "https://rubygems.org" 1 source "https://rubygems.org"
2 -gem 'rails', '~> 3.2.19' 2 +gem 'rails', '~> 3.2.21'
  3 +gem 'minitest', '~> 3.2.0'
3 gem 'fast_gettext', '~> 0.6.8' 4 gem 'fast_gettext', '~> 0.6.8'
4 -gem 'acts-as-taggable-on', '~> 3.0.2'  
5 -gem 'prototype-rails', '~> 3.2.1'  
6 -gem 'prototype_legacy_helper', '0.0.0', :path => 'vendor/prototype_legacy_helper' 5 +gem 'acts-as-taggable-on', '~> 3.4.2'
7 gem 'rails_autolink', '~> 1.1.5' 6 gem 'rails_autolink', '~> 1.1.5'
8 gem 'pg', '~> 0.13.2' 7 gem 'pg', '~> 0.13.2'
9 gem 'rmagick', '~> 2.13.1' 8 gem 'rmagick', '~> 2.13.1'
@@ -12,16 +11,14 @@ gem 'will_paginate', '~> 3.0.3' @@ -12,16 +11,14 @@ gem 'will_paginate', '~> 3.0.3'
12 gem 'ruby-feedparser', '~> 0.7' 11 gem 'ruby-feedparser', '~> 0.7'
13 gem 'daemons', '~> 1.1.5' 12 gem 'daemons', '~> 1.1.5'
14 gem 'thin', '~> 1.3.1' 13 gem 'thin', '~> 1.3.1'
15 -gem 'hpricot', '~> 0.8.6'  
16 gem 'nokogiri', '~> 1.5.5' 14 gem 'nokogiri', '~> 1.5.5'
17 gem 'rake', :require => false 15 gem 'rake', :require => false
18 gem 'rest-client', '~> 1.6.7' 16 gem 'rest-client', '~> 1.6.7'
19 gem 'exception_notification', '~> 4.0.1' 17 gem 'exception_notification', '~> 4.0.1'
20 gem 'gettext', '~> 2.2.1', :require => false, :group => :development 18 gem 'gettext', '~> 2.2.1', :require => false, :group => :development
21 gem 'locale', '~> 2.0.5' 19 gem 'locale', '~> 2.0.5'
22 -  
23 -# FIXME list here all actual dependencies (i.e. the ones in debian/control),  
24 -# with their GEM names (not the Debian package names) 20 +gem 'whenever', :require => false
  21 +gem 'eita-jrails', '>= 0.9.5', :require => 'jrails'
25 22
26 group :production do 23 group :production do
27 gem 'dalli', '~> 2.7.0' 24 gem 'dalli', '~> 2.7.0'
@@ -41,6 +38,9 @@ group :cucumber do @@ -41,6 +38,9 @@ group :cucumber do
41 gem 'selenium-webdriver', '~> 2.39.0' 38 gem 'selenium-webdriver', '~> 2.39.0'
42 end 39 end
43 40
  41 +# Requires custom dependencies
  42 +eval(File.read('config/Gemfile'), binding) rescue nil
  43 +
44 # include gemfiles from enabled plugins 44 # include gemfiles from enabled plugins
45 # plugins in baseplugins/ are not included on purpose. They should not have any 45 # plugins in baseplugins/ are not included on purpose. They should not have any
46 # dependencies. 46 # dependencies.
@@ -21,7 +21,7 @@ Noosfero is written in Ruby with the "[Rails framework](http://www.rubyonrails.o @@ -21,7 +21,7 @@ Noosfero is written in Ruby with the "[Rails framework](http://www.rubyonrails.o
21 You need to install some packages Noosfero depends on. On Debian GNU/Linux or Debian-based systems, all of these packages are available through the Debian archive. You can install them with the following command: 21 You need to install some packages Noosfero depends on. On Debian GNU/Linux or Debian-based systems, all of these packages are available through the Debian archive. You can install them with the following command:
22 22
23 # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby1.8 \ 23 # apt-get install ruby rake po4a libgettext-ruby-util libgettext-ruby1.8 \
24 - libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby libhpricot-ruby \ 24 + libsqlite3-ruby rcov librmagick-ruby libredcloth-ruby \
25 libwill-paginate-ruby iso-codes libfeedparser-ruby libdaemons-ruby thin \ 25 libwill-paginate-ruby iso-codes libfeedparser-ruby libdaemons-ruby thin \
26 tango-icon-theme 26 tango-icon-theme
27 27
@@ -40,7 +40,6 @@ On other systems, they may or may not be available through your regular package @@ -40,7 +40,6 @@ On other systems, they may or may not be available through your regular package
40 * Daemons - http://daemons.rubyforge.org 40 * Daemons - http://daemons.rubyforge.org
41 * Thin: http://code.macournoyer.com/thin 41 * Thin: http://code.macournoyer.com/thin
42 * tango-icon-theme: http://tango.freedesktop.org/Tango_Icon_Library 42 * tango-icon-theme: http://tango.freedesktop.org/Tango_Icon_Library
43 -* Hpricot: http://hpricot.com  
44 43
45 If you manage to install Noosfero successfully on other systems than Debian, 44 If you manage to install Noosfero successfully on other systems than Debian,
46 please feel free to contact the Noosfero development mailing with the 45 please feel free to contact the Noosfero development mailing with the
INSTALL.multitenancy.md
@@ -26,7 +26,7 @@ The file config/database.yml must follow a structure in order to achieve multite @@ -26,7 +26,7 @@ The file config/database.yml must follow a structure in order to achieve multite
26 26
27 Each "hosted" environment must have an entry like this: 27 Each "hosted" environment must have an entry like this:
28 28
29 - env1_production: 29 + env1_production: &DEFAULT
30 adapter: postgresql 30 adapter: postgresql
31 encoding: unicode 31 encoding: unicode
32 database: noosfero 32 database: noosfero
@@ -61,7 +61,7 @@ The "hosted" environments define, besides the `schema_search_path`, a list of do @@ -61,7 +61,7 @@ The "hosted" environments define, besides the `schema_search_path`, a list of do
61 You must also tell the application which is the default environment. 61 You must also tell the application which is the default environment.
62 62
63 production: 63 production:
64 - env1_production 64 + <<: *DEFAULT
65 65
66 On the example above there are only three hosted environments, but it can be more than three. The schemas `env2` and `env3` must already exist in the same database of the hosting environment. As postgres user, you can create them typing: 66 On the example above there are only three hosted environments, but it can be more than three. The schemas `env2` and `env3` must already exist in the same database of the hosting environment. As postgres user, you can create them typing:
67 67
MIGRATION_ISSUES
@@ -1,41 +0,0 @@ @@ -1,41 +0,0 @@
1 -* ruby-get-text incmopatible with rails3. Maybe we can use it's gem  
2 -  
3 -* all js code is inside miscellaneous.js. Would be nice to refactor this  
4 -  
5 -* rails 2 uses prototype instead of jquery  
6 -  
7 -* config/environment.rb maybe still have some code that should be on the initializers  
8 -  
9 -* initializers session_store.rb inflections.rb... don't exist  
10 -  
11 -* rails gems version have to be forced on Gemfile or it will use incompatible pre3vious versions (3.1.3)  
12 -  
13 -* Sweepers are now natively supported on Rails 3. Would be nice to refactor it  
14 -  
15 -* On Rails 3 it is no more possible to add allowed tags to avoid scape. The html_safe initializer is an option.  
16 -  
17 -* error when call sqlite_extensiosn  
18 -  
19 -* error related to action_tracker  
20 -  
21 -* check FIXME's in script/quick-start  
22 -  
23 -* check FIXME's in Gemfile  
24 -  
25 -* Check the FIXME in config/routes.rb  
26 -  
27 -* rewrite conditional routing. See FIXME in lib/route_if.rb and re-implement using the Rails 3 mechanism - http://guides.rubyonrails.org/routing.html#advanced-constraints  
28 -  
29 -* check FIXME's in config/environment.rb  
30 -  
31 -* xss_terminate sucks. We should replace it with the builtin mechanism in Rails 3  
32 -  
33 -* instance_eval on Ruby 1.9 yields self, so lambdas that are passed to instance_eval and do not accept exactly 1 argument will blow up. See http://www.ruby-forum.com/topic/213313 ... search for instance_eval and fix where necessary. In special, most of the blocks still need fixing.  
34 -  
35 -* all instances of <% *_form_for ... %> must be changed to <%= instead of <%  
36 -  
37 -* all ActiveRecord models have to declare explicitly which attributes must be allowed for mass assignment with attr_accessible.  
38 -  
39 -* check if we need to update config/locales/*  
40 -  
41 -* check observe_field and labelled_form_for in app/helpers/application_helper.rb  
SECURITY.md 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +# Noosfero security
  2 +
  3 +## Reporting security issues in Noosfero
  4 +
  5 +Security vulnerabilities should be reported via an email to
  6 +noosfero-security@listas.softwarelivre.org, which ia a private mailing list.
  7 +Reported problems will be published after fixes are available.
  8 +
  9 +The members of the mailing list are people who provide Noosfero (Noosfero
  10 +committers, mainly).
app/controllers/admin/admin_panel_controller.rb
@@ -87,6 +87,6 @@ class AdminPanelController &lt; AdminController @@ -87,6 +87,6 @@ class AdminPanelController &lt; AdminController
87 scope = scope.order('name ASC') 87 scope = scope.order('name ASC')
88 88
89 @q = params[:q] 89 @q = params[:q]
90 - @collection = find_by_contents(:organizations, scope, @q, {:per_page => 10, :page => params[:npage]})[:results] 90 + @collection = find_by_contents(:organizations, environment, scope, @q, {:per_page => 10, :page => params[:npage]})[:results]
91 end 91 end
92 end 92 end
app/controllers/admin/region_validators_controller.rb
@@ -33,7 +33,7 @@ class RegionValidatorsController &lt; AdminController @@ -33,7 +33,7 @@ class RegionValidatorsController &lt; AdminController
33 def load_region_and_search 33 def load_region_and_search
34 @region = environment.regions.find(params[:id]) 34 @region = environment.regions.find(params[:id])
35 if params[:search] 35 if params[:search]
36 - @search = find_by_contents(:organizations, Organization, params[:search])[:results].reject {|item| @region.validator_ids.include?(item.id) } 36 + @search = find_by_contents(:organizations, environment, Organization, params[:search])[:results].reject {|item| @region.validator_ids.include?(item.id) }
37 end 37 end
38 end 38 end
39 39
app/controllers/admin/users_controller.rb
@@ -18,7 +18,7 @@ class UsersController &lt; AdminController @@ -18,7 +18,7 @@ class UsersController &lt; AdminController
18 end 18 end
19 scope = scope.order('name ASC') 19 scope = scope.order('name ASC')
20 @q = params[:q] 20 @q = params[:q]
21 - @collection = find_by_contents(:people, scope, @q, {:per_page => per_page, :page => params[:npage]})[:results] 21 + @collection = find_by_contents(:people, environment, scope, @q, {:per_page => per_page, :page => params[:npage]})[:results]
22 end 22 end
23 23
24 def set_admin_role 24 def set_admin_role
app/controllers/application_controller.rb
@@ -28,6 +28,7 @@ class ApplicationController &lt; ActionController::Base @@ -28,6 +28,7 @@ class ApplicationController &lt; ActionController::Base
28 unless environment.access_control_allow_methods.blank? 28 unless environment.access_control_allow_methods.blank?
29 response.headers["Access-Control-Allow-Methods"] = environment.access_control_allow_methods 29 response.headers["Access-Control-Allow-Methods"] = environment.access_control_allow_methods
30 end 30 end
  31 + response.headers["Access-Control-Allow-Credentials"] = 'true'
31 elsif environment.restrict_to_access_control_origins 32 elsif environment.restrict_to_access_control_origins
32 render_access_denied _('Origin not in allowed.') 33 render_access_denied _('Origin not in allowed.')
33 end 34 end
@@ -59,15 +60,7 @@ class ApplicationController &lt; ActionController::Base @@ -59,15 +60,7 @@ class ApplicationController &lt; ActionController::Base
59 helper :document 60 helper :document
60 helper :language 61 helper :language
61 62
62 - def self.no_design_blocks  
63 - @no_design_blocks = true  
64 - end  
65 - def self.uses_design_blocks?  
66 - !@no_design_blocks  
67 - end  
68 - def uses_design_blocks?  
69 - !@no_design_blocks && self.class.uses_design_blocks?  
70 - end 63 + include DesignHelper
71 64
72 # Be sure to include AuthenticationSystem in Application Controller instead 65 # Be sure to include AuthenticationSystem in Application Controller instead
73 include AuthenticatedSystem 66 include AuthenticatedSystem
@@ -183,21 +176,19 @@ class ApplicationController &lt; ActionController::Base @@ -183,21 +176,19 @@ class ApplicationController &lt; ActionController::Base
183 end 176 end
184 end 177 end
185 178
186 - def find_by_contents(asset, scope, query, paginate_options={:page => 1}, options={})  
187 - plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options) ||  
188 - fallback_find_by_contents(asset, scope, query, paginate_options, options)  
189 - end 179 + include SearchTermHelper
190 180
191 - private 181 + def find_by_contents(asset, context, scope, query, paginate_options={:page => 1}, options={})
  182 + search = plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options)
  183 + register_search_term(query, scope.count, search[:results].count, context, asset)
  184 + search
  185 + end
192 186
193 - def fallback_find_by_contents(asset, scope, query, paginate_options, options)  
194 - scope = scope.like_search(query) unless query.blank?  
195 - scope = scope.send(options[:filter]) unless options[:filter].blank?  
196 - {:results => scope.paginate(paginate_options)} 187 + def find_suggestions(query, context, asset, options={})
  188 + plugins.dispatch_first(:find_suggestions, query, context, asset, options)
197 end 189 end
198 190
199 def private_environment? 191 def private_environment?
200 @environment.enabled?(:restrict_to_members) 192 @environment.enabled?(:restrict_to_members)
201 end 193 end
202 -  
203 end 194 end
app/controllers/my_profile/cms_controller.rb
@@ -23,6 +23,9 @@ class CmsController &lt; MyProfileController @@ -23,6 +23,9 @@ class CmsController &lt; MyProfileController
23 end 23 end
24 24
25 before_filter :login_required, :except => [:suggest_an_article] 25 before_filter :login_required, :except => [:suggest_an_article]
  26 + before_filter :load_recent_files, :only => [:new, :edit]
  27 +
  28 + helper_method :file_types
26 29
27 protect_if :only => :upload_files do |c, user, profile| 30 protect_if :only => :upload_files do |c, user, profile|
28 article_id = c.params[:parent_id] 31 article_id = c.params[:parent_id]
@@ -30,7 +33,7 @@ class CmsController &lt; MyProfileController @@ -30,7 +33,7 @@ class CmsController &lt; MyProfileController
30 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) 33 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
31 end 34 end
32 35
33 - protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :upload_files, :new] do |c, user, profile| 36 + protect_if :except => [:suggest_an_article, :set_home_page, :edit, :destroy, :publish, :publish_on_portal_community, :publish_on_communities, :search_communities_to_publish, :upload_files, :new] do |c, user, profile|
34 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)) 37 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))
35 end 38 end
36 39
@@ -40,7 +43,7 @@ class CmsController &lt; MyProfileController @@ -40,7 +43,7 @@ class CmsController &lt; MyProfileController
40 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))) 43 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
41 end 44 end
42 45
43 - protect_if :only => [:destroy, :publish] do |c, user, profile| 46 + protect_if :only => :destroy do |c, user, profile|
44 profile.articles.find(c.params[:id]).allow_post_content?(user) 47 profile.articles.find(c.params[:id]).allow_post_content?(user)
45 end 48 end
46 49
@@ -117,7 +120,7 @@ class CmsController &lt; MyProfileController @@ -117,7 +120,7 @@ class CmsController &lt; MyProfileController
117 @success_back_to = params[:success_back_to] 120 @success_back_to = params[:success_back_to]
118 # user must choose an article type first 121 # user must choose an article type first
119 122
120 - @parent = profile.articles.find(params[:parent_id]) if params && params[:parent_id] 123 + @parent = profile.articles.find(params[:parent_id]) if params && params[:parent_id].present?
121 record_coming 124 record_coming
122 @type = params[:type] 125 @type = params[:type]
123 if @type.blank? 126 if @type.blank?
@@ -163,7 +166,10 @@ class CmsController &lt; MyProfileController @@ -163,7 +166,10 @@ class CmsController &lt; MyProfileController
163 if continue 166 if continue
164 redirect_to :action => 'edit', :id => @article 167 redirect_to :action => 'edit', :id => @article
165 else 168 else
166 - success_redirect 169 + respond_to do |format|
  170 + format.html { success_redirect }
  171 + format.json { render :text => {:id => @article.id, :full_name => profile.identifier + '/' + @article.full_name}.to_json }
  172 + end
167 end 173 end
168 return 174 return
169 end 175 end
@@ -256,28 +262,53 @@ class CmsController &lt; MyProfileController @@ -256,28 +262,53 @@ class CmsController &lt; MyProfileController
256 render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' } 262 render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' }
257 end 263 end
258 264
  265 + def search_communities_to_publish
  266 + render :text => find_by_contents(:profiles, environment, user.memberships, params['q'], {:page => 1}, {:fields => ['name']})[:results].map {|community| {:id => community.id, :name => community.name} }.to_json
  267 + end
  268 +
259 def publish 269 def publish
260 @article = profile.articles.find(params[:id]) 270 @article = profile.articles.find(params[:id])
261 record_coming 271 record_coming
262 - @groups = profile.memberships - [profile]  
263 - @marked_groups = []  
264 - groups_ids = profile.memberships.map{|m|m.id.to_s}  
265 - @marked_groups = params[:marked_groups].map do |key, item|  
266 - if groups_ids.include?(item[:group_id])  
267 - item.merge :group => Profile.find(item.delete(:group_id)) 272 + @failed = {}
  273 + if request.post?
  274 + article_name = params[:name]
  275 + task = ApproveArticle.create!(:article => @article, :name => article_name, :target => user, :requestor => user)
  276 + begin
  277 + task.finish
  278 + rescue Exception => ex
  279 + @failed[ex.message] ? @failed[ex.message] << @article.name : @failed[ex.message] = [@article.name]
  280 + task.cancel
  281 + end
  282 + if @failed.blank?
  283 + session[:notice] = _("Your publish request was sent successfully")
  284 + if @back_to
  285 + redirect_to @back_to
  286 + else
  287 + redirect_to @article.view_url
  288 + end
268 end 289 end
269 - end.compact unless params[:marked_groups].nil? 290 + end
  291 + end
  292 +
  293 + def publish_on_communities
270 if request.post? 294 if request.post?
  295 + @back_to = params[:back_to]
  296 + @article = profile.articles.find(params[:id])
271 @failed = {} 297 @failed = {}
  298 + article_name = params[:name]
  299 + params_marked = (params['q'] || '').split(',').select { |marked| user.memberships.map(&:id).include? marked.to_i }
  300 + @marked_groups = Profile.find(params_marked)
272 if @marked_groups.empty? 301 if @marked_groups.empty?
  302 + redirect_to @back_to
273 return session[:notice] = _("Select some group to publish your article") 303 return session[:notice] = _("Select some group to publish your article")
274 end 304 end
275 @marked_groups.each do |item| 305 @marked_groups.each do |item|
276 - task = ApproveArticle.create!(:article => @article, :name => item[:name], :target => item[:group], :requestor => profile) 306 + task = ApproveArticle.create!(:article => @article, :name => article_name, :target => item, :requestor => user)
277 begin 307 begin
278 - task.finish unless item[:group].moderated_articles? 308 + task.finish unless item.moderated_articles?
279 rescue Exception => ex 309 rescue Exception => ex
280 - @failed[ex.message] ? @failed[ex.message] << item[:group].name : @failed[ex.message] = [item[:group].name] 310 + @failed[ex.message] ? @failed[ex.message] << item.name : @failed[ex.message] = [item.name]
  311 + task.cancel
281 end 312 end
282 end 313 end
283 if @failed.blank? 314 if @failed.blank?
@@ -287,23 +318,27 @@ class CmsController &lt; MyProfileController @@ -287,23 +318,27 @@ class CmsController &lt; MyProfileController
287 else 318 else
288 redirect_to @article.view_url 319 redirect_to @article.view_url
289 end 320 end
  321 + else
  322 + session[:notice] = _("Some of your publish requests couldn't be sent.")
  323 + render :action => 'publish'
290 end 324 end
291 end 325 end
292 end 326 end
293 327
294 def publish_on_portal_community 328 def publish_on_portal_community
295 - @article = profile.articles.find(params[:id])  
296 if request.post? 329 if request.post?
297 - if environment.portal_community 330 + @article = profile.articles.find(params[:id])
  331 + if environment.portal_enabled
298 task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user) 332 task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user)
299 begin 333 begin
300 task.finish unless environment.portal_community.moderated_articles? 334 task.finish unless environment.portal_community.moderated_articles?
301 - flash[:notice] = _("Your publish request was sent successfully") 335 + session[:notice] = _("Your publish request was sent successfully")
302 rescue 336 rescue
303 - flash[:error] = _("Your publish request couldn't be sent.") 337 + session[:notice] = _("Your publish request couldn't be sent.")
  338 + task.cancel
304 end 339 end
305 else 340 else
306 - flash[:notice] = _("There is no portal community to publish your article.") 341 + session[:notice] = _("There is no portal community to publish your article.")
307 end 342 end
308 343
309 if @back_to 344 if @back_to
@@ -331,7 +366,7 @@ class CmsController &lt; MyProfileController @@ -331,7 +366,7 @@ class CmsController &lt; MyProfileController
331 366
332 def search 367 def search
333 query = params[:q] 368 query = params[:q]
334 - results = find_by_contents(:uploaded_files, profile.files.published, query)[:results] 369 + results = find_by_contents(:uploaded_files, profile, profile.files.published, query)[:results]
335 render :text => article_list_to_json(results), :content_type => 'application/json' 370 render :text => article_list_to_json(results), :content_type => 'application/json'
336 end 371 end
337 372
@@ -342,15 +377,26 @@ class CmsController &lt; MyProfileController @@ -342,15 +377,26 @@ class CmsController &lt; MyProfileController
342 end 377 end
343 378
344 def media_upload 379 def media_upload
345 - files_uploaded = []  
346 parent = check_parent(params[:parent_id]) 380 parent = check_parent(params[:parent_id])
347 - files = [:file1,:file2, :file3].map { |f| params[f] }.compact  
348 if request.post? 381 if request.post?
349 - files.each do |file|  
350 - files_uploaded << UploadedFile.create(:uploaded_data => file, :profile => profile, :parent => parent) unless file == '' 382 + begin
  383 + @file = UploadedFile.create!(:uploaded_data => params[:file], :profile => profile, :parent => parent) unless params[:file] == ''
  384 + @file = FilePresenter.for(@file)
  385 + rescue Exception => exception
  386 + render :text => exception.to_s, :status => :bad_request
351 end 387 end
352 end 388 end
353 - render :text => article_list_to_json(files_uploaded), :content_type => 'text/plain' 389 + end
  390 +
  391 + def published_media_items
  392 + load_recent_files(params[:parent_id], params[:q])
  393 + render :partial => 'published_media_items'
  394 + end
  395 +
  396 + def view_all_media
  397 + paginate_options = {:page => params[:page].blank? ? 1 : params[:page] }
  398 + @key = params[:key].to_sym
  399 + load_recent_files(params[:parent_id], params[:q], paginate_options)
354 end 400 end
355 401
356 protected 402 protected
@@ -445,4 +491,36 @@ class CmsController &lt; MyProfileController @@ -445,4 +491,36 @@ class CmsController &lt; MyProfileController
445 end 491 end
446 end 492 end
447 493
  494 + def file_types
  495 + {:images => _('Images'), :generics => _('Files')}
  496 + end
  497 +
  498 + def load_recent_files(parent_id = nil, q = nil, paginate_options = {:page => 1, :per_page => 6})
  499 + #TODO Since we only have special support for images, I'm limiting myself to
  500 + # consider generic files as non-images. In the future, with more supported
  501 + # file types we'll need to have a smart way to fetch from the database
  502 + # scopes of each supported type as well as the non-supported types as a
  503 + # whole.
  504 + @recent_files = {}
  505 +
  506 + parent = parent_id.present? ? profile.articles.find(parent_id) : nil
  507 + if parent.present?
  508 + files = parent.children.files
  509 + else
  510 + files = profile.files
  511 + end
  512 +
  513 + files = files.reorder('created_at DESC')
  514 + images = files.images
  515 + generics = files.no_images
  516 +
  517 + if q.present?
  518 + @recent_files[:images] = find_by_contents(:images, profile, images, q, paginate_options)[:results]
  519 + @recent_files[:generics] = find_by_contents(:generics, profile, generics, q, paginate_options)[:results]
  520 + else
  521 + @recent_files[:images] = images.paginate(paginate_options)
  522 + @recent_files[:generics] = generics.paginate(paginate_options)
  523 + end
  524 + end
  525 +
448 end 526 end
app/controllers/my_profile/friends_controller.rb
@@ -3,6 +3,7 @@ class FriendsController &lt; MyProfileController @@ -3,6 +3,7 @@ class FriendsController &lt; MyProfileController
3 protect 'manage_friends', :profile 3 protect 'manage_friends', :profile
4 4
5 def index 5 def index
  6 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
6 if is_cache_expired?(profile.manage_friends_cache_key(params)) 7 if is_cache_expired?(profile.manage_friends_cache_key(params))
7 @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage]) 8 @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage])
8 end 9 end
@@ -16,6 +17,30 @@ class FriendsController &lt; MyProfileController @@ -16,6 +17,30 @@ class FriendsController &lt; MyProfileController
16 end 17 end
17 end 18 end
18 19
  20 + def suggest
  21 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
  22 + end
  23 +
  24 + def remove_suggestion
  25 + @person = profile.suggested_people.find_by_identifier(params[:id])
  26 + redirect_to :action => 'suggest' unless @person
  27 + if @person && request.post?
  28 + profile.remove_suggestion(@person)
  29 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
  30 + render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :friends_suggestions, :per_page => params[:per_page] || per_page }
  31 + end
  32 + end
  33 +
  34 + def connections
  35 + @suggestion = profile.profile_suggestions.of_person.enabled.find_by_suggestion_id(params[:id])
  36 + if @suggestion
  37 + @tags = @suggestion.tag_connections
  38 + @profiles = @suggestion.profile_connections
  39 + else
  40 + redirect_to :action => 'suggest'
  41 + end
  42 + end
  43 +
19 protected 44 protected
20 45
21 class << self 46 class << self
app/controllers/my_profile/memberships_controller.rb
@@ -20,12 +20,54 @@ class MembershipsController &lt; MyProfileController @@ -20,12 +20,54 @@ class MembershipsController &lt; MyProfileController
20 @community.environment = environment 20 @community.environment = environment
21 @back_to = params[:back_to] || url_for(:action => 'index') 21 @back_to = params[:back_to] || url_for(:action => 'index')
22 if request.post? && @community.valid? 22 if request.post? && @community.valid?
23 - @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment}))  
24 - if @community.new_record? 23 + begin
  24 + # Community was created
  25 + @community = Community.create_after_moderation(user, params[:community].merge({:environment => environment}))
  26 + @community.reload
  27 + redirect_to :action => 'welcome', :community_id => @community.id, :back_to => @back_to
  28 + rescue ActiveRecord::RecordNotFound
  29 + # Community pending approval
25 session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.') 30 session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.')
  31 + redirect_to @back_to
26 end 32 end
27 - redirect_to @back_to  
28 return 33 return
29 end 34 end
30 end 35 end
  36 +
  37 + def welcome
  38 + @community = Community.find(params[:community_id])
  39 + @back_to = params[:back_to]
  40 + end
  41 +
  42 + def suggest
  43 + @suggestions = profile.profile_suggestions.of_community.enabled.includes(:suggestion).limit(per_page)
  44 + end
  45 +
  46 + def remove_suggestion
  47 + @community = profile.suggested_communities.find_by_identifier(params[:id])
  48 + custom_per_page = params[:per_page] || per_page
  49 + redirect_to :action => 'suggest' unless @community
  50 + if @community && request.post?
  51 + profile.remove_suggestion(@community)
  52 + @suggestions = profile.profile_suggestions.of_community.enabled.includes(:suggestion).limit(custom_per_page)
  53 + render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :communities_suggestions, :per_page => custom_per_page}
  54 + end
  55 + end
  56 +
  57 + def connections
  58 + @suggestion = profile.profile_suggestions.of_community.enabled.find_by_suggestion_id(params[:id])
  59 + if @suggestion
  60 + @tags = @suggestion.tag_connections
  61 + @profiles = @suggestion.profile_connections
  62 + else
  63 + redirect_to :action => 'suggest'
  64 + end
  65 + end
  66 +
  67 + protected
  68 +
  69 + def per_page
  70 + 12
  71 + end
  72 +
31 end 73 end
app/controllers/my_profile/profile_editor_controller.rb
@@ -3,6 +3,10 @@ class ProfileEditorController &lt; MyProfileController @@ -3,6 +3,10 @@ class ProfileEditorController &lt; MyProfileController
3 protect 'edit_profile', :profile, :except => [:destroy_profile] 3 protect 'edit_profile', :profile, :except => [:destroy_profile]
4 protect 'destroy_profile', :profile, :only => [:destroy_profile] 4 protect 'destroy_profile', :profile, :only => [:destroy_profile]
5 5
  6 + before_filter :access_welcome_page, :only => [:welcome_page]
  7 + before_filter :back_to
  8 + helper_method :has_welcome_page
  9 +
6 def index 10 def index
7 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)} 11 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)}
8 end 12 end
@@ -85,6 +89,21 @@ class ProfileEditorController &lt; MyProfileController @@ -85,6 +89,21 @@ class ProfileEditorController &lt; MyProfileController
85 end 89 end
86 end 90 end
87 91
  92 + def welcome_page
  93 + @welcome_page = profile.welcome_page || TinyMceArticle.new(:name => 'Welcome Page', :profile => profile, :published => false)
  94 + if request.post?
  95 + begin
  96 + @welcome_page.update_attributes!(params[:welcome_page])
  97 + profile.welcome_page = @welcome_page
  98 + profile.save!
  99 + session[:notice] = _('Welcome page saved successfully.')
  100 + redirect_to :action => 'index'
  101 + rescue Exception => exception
  102 + session[:notice] = _('Welcome page could not be saved.')
  103 + end
  104 + end
  105 + end
  106 +
88 def deactivate_profile 107 def deactivate_profile
89 if environment.admins.include?(current_person) 108 if environment.admins.include?(current_person)
90 profile = environment.profiles.find(params[:id]) 109 profile = environment.profiles.find(params[:id])
@@ -116,9 +135,24 @@ class ProfileEditorController &lt; MyProfileController @@ -116,9 +135,24 @@ class ProfileEditorController &lt; MyProfileController
116 protected 135 protected
117 136
118 def redirect_to_previous_location 137 def redirect_to_previous_location
119 - back = request.referer  
120 - back = "/" if back.nil? 138 + redirect_to @back_to
  139 + end
121 140
122 - redirect_to back 141 + #TODO Consider using this as a general controller feature to be available on every action.
  142 + def back_to
  143 + @back_to = params[:back_to] || request.referer || "/"
123 end 144 end
  145 +
  146 + private
  147 +
  148 + def has_welcome_page
  149 + profile.is_template
  150 + end
  151 +
  152 + def access_welcome_page
  153 + unless has_welcome_page
  154 + render_access_denied
  155 + end
  156 + end
  157 +
124 end 158 end
app/controllers/my_profile/profile_members_controller.rb
@@ -20,7 +20,7 @@ class ProfileMembersController &lt; MyProfileController @@ -20,7 +20,7 @@ class ProfileMembersController &lt; MyProfileController
20 redirect_to :action => :last_admin 20 redirect_to :action => :last_admin
21 elsif @person.define_roles(@roles, profile) 21 elsif @person.define_roles(@roles, profile)
22 session[:notice] = _('Roles successfuly updated') 22 session[:notice] = _('Roles successfuly updated')
23 - redirect_to :controller => 'profile_editor' 23 + redirect_to :action => 'index'
24 else 24 else
25 session[:notice] = _('Couldn\'t change the roles') 25 session[:notice] = _('Couldn\'t change the roles')
26 redirect_to :action => 'index' 26 redirect_to :action => 'index'
app/controllers/public/account_controller.rb
@@ -82,10 +82,12 @@ class AccountController &lt; ApplicationController @@ -82,10 +82,12 @@ class AccountController &lt; ApplicationController
82 if @plugins.dispatch(:allow_user_registration).include?(false) 82 if @plugins.dispatch(:allow_user_registration).include?(false)
83 redirect_back_or_default(:controller => 'home') 83 redirect_back_or_default(:controller => 'home')
84 session[:notice] = _("This environment doesn't allow user registration.") 84 session[:notice] = _("This environment doesn't allow user registration.")
  85 + return
85 end 86 end
86 87
87 store_location(request.referer) unless params[:return_to] or session[:return_to] 88 store_location(request.referer) unless params[:return_to] or session[:return_to]
88 89
  90 + # Tranforming to boolean
89 @block_bot = !!session[:may_be_a_bot] 91 @block_bot = !!session[:may_be_a_bot]
90 @invitation_code = params[:invitation_code] 92 @invitation_code = params[:invitation_code]
91 begin 93 begin
@@ -129,8 +131,8 @@ class AccountController &lt; ApplicationController @@ -129,8 +131,8 @@ class AccountController &lt; ApplicationController
129 check_join_in_community(@user) 131 check_join_in_community(@user)
130 go_to_signup_initial_page 132 go_to_signup_initial_page
131 else 133 else
  134 + redirect_to :controller => :home, :action => :welcome, :template_id => (@user.person.template && @user.person.template.id)
132 session[:notice] = _('Thanks for registering!') 135 session[:notice] = _('Thanks for registering!')
133 - @register_pending = true  
134 end 136 end
135 end 137 end
136 end 138 end
@@ -461,6 +463,8 @@ class AccountController &lt; ApplicationController @@ -461,6 +463,8 @@ class AccountController &lt; ApplicationController
461 redirect_to user.url 463 redirect_to user.url
462 when 'user_control_panel' 464 when 'user_control_panel'
463 redirect_to user.admin_url 465 redirect_to user.admin_url
  466 + when 'welcome_page'
  467 + redirect_to :controller => :home, :action => :welcome, :template_id => (user.template && user.template.id)
464 else 468 else
465 redirect_back_or_default(default) 469 redirect_back_or_default(default)
466 end 470 end
app/controllers/public/chat_controller.rb
@@ -6,6 +6,7 @@ class ChatController &lt; PublicController @@ -6,6 +6,7 @@ class ChatController &lt; PublicController
6 def start_session 6 def start_session
7 login = user.jid 7 login = user.jid
8 password = current_user.crypted_password 8 password = current_user.crypted_password
  9 + session[:chat] ||= {:rooms => []}
9 begin 10 begin
10 jid, sid, rid = RubyBOSH.initialize_session(login, password, "http://#{environment.default_hostname}/http-bind", 11 jid, sid, rid = RubyBOSH.initialize_session(login, password, "http://#{environment.default_hostname}/http-bind",
11 :wait => 30, :hold => 1, :window => 5) 12 :wait => 30, :hold => 1, :window => 5)
@@ -16,6 +17,31 @@ class ChatController &lt; PublicController @@ -16,6 +17,31 @@ class ChatController &lt; PublicController
16 end 17 end
17 end 18 end
18 19
  20 + def toggle
  21 + session[:chat][:status] = session[:chat][:status] == 'opened' ? 'closed' : 'opened'
  22 + render :nothing => true
  23 + end
  24 +
  25 + def tab
  26 + session[:chat][:tab_id] = params[:tab_id]
  27 + render :nothing => true
  28 + end
  29 +
  30 + def join
  31 + session[:chat][:rooms] << params[:room_id]
  32 + session[:chat][:rooms].uniq!
  33 + render :nothing => true
  34 + end
  35 +
  36 + def leave
  37 + session[:chat][:rooms].delete(params[:room_id])
  38 + render :nothing => true
  39 + end
  40 +
  41 + def my_session
  42 + render :text => session[:chat].to_json, :layout => false
  43 + end
  44 +
19 def avatar 45 def avatar
20 profile = environment.profiles.find_by_identifier(params[:id]) 46 profile = environment.profiles.find_by_identifier(params[:id])
21 filename, mimetype = profile_icon(profile, :minor, true) 47 filename, mimetype = profile_icon(profile, :minor, true)
@@ -28,15 +54,6 @@ class ChatController &lt; PublicController @@ -28,15 +54,6 @@ class ChatController &lt; PublicController
28 end 54 end
29 end 55 end
30 56
31 - def index  
32 - presence = current_user.last_chat_status  
33 - if presence.blank? or presence == 'chat'  
34 - render :action => 'auto_connect_online'  
35 - else  
36 - render :action => 'auto_connect_busy'  
37 - end  
38 - end  
39 -  
40 def update_presence_status 57 def update_presence_status
41 if request.xhr? 58 if request.xhr?
42 current_user.update_attributes({:chat_status_at => DateTime.now}.merge(params[:status] || {})) 59 current_user.update_attributes({:chat_status_at => DateTime.now}.merge(params[:status] || {}))
@@ -44,6 +61,44 @@ class ChatController &lt; PublicController @@ -44,6 +61,44 @@ class ChatController &lt; PublicController
44 render :nothing => true 61 render :nothing => true
45 end 62 end
46 63
  64 + def save_message
  65 + to = environment.profiles.find_by_identifier(params[:to])
  66 + body = params[:body]
  67 +
  68 + ChatMessage.create!(:to => to, :from => user, :body => body)
  69 + render :text => 'ok'
  70 + end
  71 +
  72 + def recent_messages
  73 + other = environment.profiles.find_by_identifier(params[:identifier])
  74 + if other.kind_of?(Organization)
  75 + messages = ChatMessage.where('to_id=:other', :other => other.id)
  76 + else
  77 + messages = ChatMessage.where('(to_id=:other and from_id=:me) or (to_id=:me and from_id=:other)', {:me => user.id, :other => other.id})
  78 + end
  79 +
  80 + messages = messages.order('created_at DESC').includes(:to, :from).offset(params[:offset]).limit(20)
  81 + messages_json = messages.map do |message|
  82 + {
  83 + :body => message.body,
  84 + :to => {:id => message.to.identifier, :name => message.to.name},
  85 + :from => {:id => message.from.identifier, :name => message.from.name},
  86 + :created_at => message.created_at
  87 + }
  88 + end
  89 + render :json => messages_json.reverse
  90 + end
  91 +
  92 + def recent_conversations
  93 + conversations_order = ActiveRecord::Base.connection.execute("select profiles.identifier from profiles inner join (select distinct r.id as id, MAX(r.created_at) as created_at from (select from_id, to_id, created_at, (case when from_id=#{user.id} then to_id else from_id end) as id from chat_messages where from_id=#{user.id} or to_id=#{user.id}) as r group by id order by created_at desc, id) as t on profiles.id=t.id order by t.created_at desc").entries.map {|e| e['identifier']}
  94 + render :json => {:order => conversations_order.reverse, :domain => environment.default_hostname.gsub('.','-')}.to_json
  95 + end
  96 +
  97 + #TODO Ideally this is done through roster table on ejabberd.
  98 + def roster_groups
  99 + render :text => user.memberships.map {|m| {:jid => m.jid, :name => m.name}}.to_json
  100 + end
  101 +
47 protected 102 protected
48 103
49 def check_environment_feature 104 def check_environment_feature
app/controllers/public/contact_controller.rb
1 class ContactController < PublicController 1 class ContactController < PublicController
2 2
3 - before_filter :login_required  
4 -  
5 needs_profile 3 needs_profile
  4 + before_filter :allow_access_to_page
6 5
7 def new 6 def new
8 - @contact 7 + @contact = build_contact
9 if request.post? && params[:confirm] == 'true' 8 if request.post? && params[:confirm] == 'true'
10 - @contact = user.build_contact(profile, params[:contact])  
11 @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil 9 @contact.city = (!params[:city].blank? && City.exists?(params[:city])) ? City.find(params[:city]).name : nil
12 @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil 10 @contact.state = (!params[:state].blank? && State.exists?(params[:state])) ? State.find(params[:state]).name : nil
13 if @contact.deliver 11 if @contact.deliver
@@ -16,8 +14,17 @@ class ContactController &lt; PublicController @@ -16,8 +14,17 @@ class ContactController &lt; PublicController
16 else 14 else
17 session[:notice] = _('Contact not sent') 15 session[:notice] = _('Contact not sent')
18 end 16 end
  17 + end
  18 + end
  19 +
  20 + protected
  21 +
  22 + def build_contact
  23 + params[:contact] ||= {}
  24 + if logged_in?
  25 + user.build_contact profile, params[:contact]
19 else 26 else
20 - @contact = user.build_contact(profile) 27 + Contact.new params[:contact].merge(dest: profile)
21 end 28 end
22 end 29 end
23 30
app/controllers/public/events_controller.rb
1 class EventsController < PublicController 1 class EventsController < PublicController
2 2
3 needs_profile 3 needs_profile
  4 + before_filter :allow_access_to_page
4 5
5 def events 6 def events
6 @events = [] 7 @events = []
app/controllers/public/home_controller.rb
@@ -2,13 +2,13 @@ class HomeController &lt; PublicController @@ -2,13 +2,13 @@ class HomeController &lt; PublicController
2 2
3 def index 3 def index
4 @has_news = false 4 @has_news = false
5 - if environment.enabled?('use_portal_community') && environment.portal_community 5 + if environment.portal_enabled
6 @has_news = true 6 @has_news = true
7 @news_cache_key = environment.portal_news_cache_key(FastGettext.locale) 7 @news_cache_key = environment.portal_news_cache_key(FastGettext.locale)
8 if !read_fragment(@news_cache_key) 8 if !read_fragment(@news_cache_key)
9 portal_community = environment.portal_community 9 portal_community = environment.portal_community
10 - @highlighted_news = portal_community.news(2, true)  
11 - @portal_news = portal_community.news(7, true) - @highlighted_news 10 + @highlighted_news = portal_community.news(environment.highlighted_news_amount, true)
  11 + @portal_news = portal_community.news(environment.portal_news_amount, true).offset(environment.highlighted_news_amount)
12 @area_news = environment.portal_folders 12 @area_news = environment.portal_folders
13 end 13 end
14 end 14 end
@@ -18,4 +18,10 @@ class HomeController &lt; PublicController @@ -18,4 +18,10 @@ class HomeController &lt; PublicController
18 @no_design_blocks = true 18 @no_design_blocks = true
19 end 19 end
20 20
  21 + def welcome
  22 + @no_design_blocks = true
  23 + @display_confirmation_tips = !user.present? && !environment.enabled?(:skip_new_user_email_confirmation)
  24 + @person_template = user && user.template || params[:template_id] && Person.find(params[:template_id])
  25 + end
  26 +
21 end 27 end
app/controllers/public/invite_controller.rb
@@ -4,8 +4,15 @@ class InviteController &lt; PublicController @@ -4,8 +4,15 @@ class InviteController &lt; PublicController
4 before_filter :login_required 4 before_filter :login_required
5 before_filter :check_permissions_to_invite 5 before_filter :check_permissions_to_invite
6 6
7 - def select_address_book 7 + def invite_friends
8 @import_from = params[:import_from] || "manual" 8 @import_from = params[:import_from] || "manual"
  9 + @mail_template = params[:mail_template] || environment.invitation_mail_template(profile)
  10 +
  11 + labels = Profile::SEARCHABLE_FIELDS.except(:nickname).merge(User::SEARCHABLE_FIELDS).map { |name,info| info[:label].downcase }
  12 + last = labels.pop
  13 + label = labels.join(', ')
  14 + @search_fields = "#{label} #{_('or')} #{last}"
  15 +
9 if request.post? 16 if request.post?
10 contact_list = ContactList.create 17 contact_list = ContactList.create
11 Delayed::Job.enqueue GetEmailContactsJob.new(@import_from, params[:login], params[:password], contact_list.id) if @import_from != 'manual' 18 Delayed::Job.enqueue GetEmailContactsJob.new(@import_from, params[:login], params[:password], contact_list.id) if @import_from != 'manual'
@@ -22,7 +29,7 @@ class InviteController &lt; PublicController @@ -22,7 +29,7 @@ class InviteController &lt; PublicController
22 webmail_import_addresses = params[:webmail_import_addresses] 29 webmail_import_addresses = params[:webmail_import_addresses]
23 contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses) 30 contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses)
24 if !contacts_to_invite.empty? 31 if !contacts_to_invite.empty?
25 - Delayed::Job.enqueue InvitationJob.new(current_user.person.id, contacts_to_invite, params[:mail_template], profile.id, @contact_list.id, locale) 32 + Delayed::Job.enqueue InvitationJob.new(user.id, contacts_to_invite, params[:mail_template], profile.id, @contact_list.id, locale)
26 session[:notice] = _('Your invitations are being sent.') 33 session[:notice] = _('Your invitations are being sent.')
27 if profile.person? 34 if profile.person?
28 redirect_to :controller => 'profile', :action => 'friends' 35 redirect_to :controller => 'profile', :action => 'friends'
@@ -52,16 +59,36 @@ class InviteController &lt; PublicController @@ -52,16 +59,36 @@ class InviteController &lt; PublicController
52 def cancel_fetching_emails 59 def cancel_fetching_emails
53 contact_list = ContactList.find(params[:contact_list]) 60 contact_list = ContactList.find(params[:contact_list])
54 contact_list.destroy 61 contact_list.destroy
55 - redirect_to :action => 'select_address_book' 62 + redirect_to :action => 'invite_friends'
  63 + end
  64 +
  65 + def invite_registered_friend
  66 + contacts_to_invite = params['q'].split(',')
  67 + if !contacts_to_invite.empty? && request.post?
  68 + Delayed::Job.enqueue InvitationJob.new(user.id, contacts_to_invite, '', profile.id, nil, locale)
  69 + session[:notice] = _('Your invitations are being sent.')
  70 + if profile.person?
  71 + redirect_to :controller => 'profile', :action => 'friends'
  72 + else
  73 + redirect_to :controller => 'profile', :action => 'members'
  74 + end
  75 + else
  76 + redirect_to :action => 'invite_friends'
  77 + session[:notice] = _('Please enter a valid profile.')
  78 + end
  79 + end
  80 +
  81 + def search
  82 + scope = profile.invite_friends_only ? user.friends : environment.people
  83 + scope = scope.not_members_of(profile) if profile.organization?
  84 + scope = scope.not_friends_of(profile) if profile.person?
  85 + results = find_by_contents(:people, environment, scope, params['q'], {:page => 1}, {:joins => :user})[:results]
  86 + render :text => prepare_to_token_input(results).to_json
56 end 87 end
57 88
58 protected 89 protected
59 90
60 def check_permissions_to_invite 91 def check_permissions_to_invite
61 - if profile.person? and !current_user.person.has_permission?(:manage_friends, profile) or  
62 - profile.community? and !current_user.person.has_permission?(:invite_members, profile)  
63 - render_access_denied  
64 - end 92 + render_access_denied if !profile.allow_invitation_from?(user)
65 end 93 end
66 -  
67 end 94 end
app/controllers/public/profile_controller.rb
@@ -16,13 +16,7 @@ class ProfileController &lt; PublicController @@ -16,13 +16,7 @@ class ProfileController &lt; PublicController
16 @activities = @profile.activities.paginate(:per_page => 15, :page => params[:page]) 16 @activities = @profile.activities.paginate(:per_page => 15, :page => params[:page])
17 end 17 end
18 @tags = profile.article_tags 18 @tags = profile.article_tags
19 - unless profile.display_info_to?(user)  
20 - if profile.visible?  
21 - private_profile  
22 - else  
23 - invisible_profile  
24 - end  
25 - end 19 + allow_access_to_page
26 end 20 end
27 21
28 def tags 22 def tags
@@ -396,17 +390,6 @@ class ProfileController &lt; PublicController @@ -396,17 +390,6 @@ class ProfileController &lt; PublicController
396 end 390 end
397 end 391 end
398 392
399 - def private_profile  
400 - private_profile_partial_parameters  
401 - render :action => 'index', :status => 403  
402 - end  
403 -  
404 - def invisible_profile  
405 - unless profile.is_template?  
406 - render_access_denied(_("This profile is inaccessible. You don't have the permission to view the content here."), _("Oops ... you cannot go ahead here"))  
407 - end  
408 - end  
409 -  
410 def per_page 393 def per_page
411 Noosfero::Constants::PROFILE_PER_PAGE 394 Noosfero::Constants::PROFILE_PER_PAGE
412 end 395 end
app/controllers/public/profile_search_controller.rb
@@ -9,9 +9,12 @@ class ProfileSearchController &lt; PublicController @@ -9,9 +9,12 @@ class ProfileSearchController &lt; PublicController
9 @q = params[:q] 9 @q = params[:q]
10 unless @q.blank? 10 unless @q.blank?
11 if params[:where] == 'environment' 11 if params[:where] == 'environment'
12 - redirect_to :controller => 'search', :query => @q 12 + # user is using global search, redirects to the search controller with
  13 + # the query
  14 + search_path = url_for(:controller => 'search', :query => @q)
  15 + request.xhr? ? render(:js => "window.location.href = #{search_path.to_json}") : redirect_to(search_path)
13 else 16 else
14 - @results = find_by_contents(:articles, profile.articles.published, @q, {:per_page => 10, :page => params[:page]})[:results] 17 + @results = find_by_contents(:articles, profile, profile.articles.published, @q, {:per_page => 10, :page => params[:page]})[:results]
15 end 18 end
16 end 19 end
17 end 20 end
app/controllers/public/search_controller.rb
@@ -4,11 +4,11 @@ class SearchController &lt; PublicController @@ -4,11 +4,11 @@ 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 => :assets  
8 - before_filter :load_category  
9 - before_filter :load_search_assets  
10 - before_filter :load_query  
11 - before_filter :load_filter 7 + before_filter :redirect_asset_param, :except => [:assets, :suggestions]
  8 + before_filter :load_category, :except => :suggestions
  9 + before_filter :load_search_assets, :except => :suggestions
  10 + before_filter :load_query, :except => :suggestions
  11 + before_filter :load_order, :except => :suggestions
12 12
13 # Backwards compatibility with old URLs 13 # Backwards compatibility with old URLs
14 def redirect_asset_param 14 def redirect_asset_param
@@ -20,7 +20,7 @@ class SearchController &lt; PublicController @@ -20,7 +20,7 @@ class SearchController &lt; PublicController
20 20
21 def index 21 def index
22 @searches = {} 22 @searches = {}
23 - @order = [] 23 + @assets = []
24 @names = {} 24 @names = {}
25 @results_only = true 25 @results_only = true
26 26
@@ -28,7 +28,7 @@ class SearchController &lt; PublicController @@ -28,7 +28,7 @@ class SearchController &lt; PublicController
28 load_query 28 load_query
29 @asset = key 29 @asset = key
30 send(key) 30 send(key)
31 - @order << key 31 + @assets << key
32 @names[key] = _(description) 32 @names[key] = _(description)
33 end 33 end
34 @asset = nil 34 @asset = nil
@@ -42,7 +42,7 @@ class SearchController &lt; PublicController @@ -42,7 +42,7 @@ class SearchController &lt; PublicController
42 # view the summary of one category 42 # view the summary of one category
43 def category_index 43 def category_index
44 @searches = {} 44 @searches = {}
45 - @order = [] 45 + @assets = []
46 @names = {} 46 @names = {}
47 limit = MULTIPLE_SEARCH_LIMIT 47 limit = MULTIPLE_SEARCH_LIMIT
48 [ 48 [
@@ -53,7 +53,7 @@ class SearchController &lt; PublicController @@ -53,7 +53,7 @@ class SearchController &lt; PublicController
53 [ :communities, _('Communities'), :recent_communities ], 53 [ :communities, _('Communities'), :recent_communities ],
54 [ :articles, _('Contents'), :recent_articles ] 54 [ :articles, _('Contents'), :recent_articles ]
55 ].each do |asset, name, filter| 55 ].each do |asset, name, filter|
56 - @order << asset 56 + @assets << asset
57 @searches[asset]= {:results => @category.send(filter, limit)} 57 @searches[asset]= {:results => @category.send(filter, limit)}
58 raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries) 58 raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries)
59 @names[asset] = name 59 @names[asset] = name
@@ -147,12 +147,16 @@ class SearchController &lt; PublicController @@ -147,12 +147,16 @@ class SearchController &lt; PublicController
147 render :partial => 'events/events' 147 render :partial => 'events/events'
148 end 148 end
149 149
  150 + def suggestions
  151 + render :text => find_suggestions(normalize_term(params[:term]), environment, params[:asset]).to_json
  152 + end
  153 +
150 ####################################################### 154 #######################################################
151 protected 155 protected
152 156
153 def load_query 157 def load_query
154 @asset = (params[:asset] || params[:action]).to_sym 158 @asset = (params[:asset] || params[:action]).to_sym
155 - @order ||= [@asset] 159 + @assets ||= [@asset]
156 @searches ||= {} 160 @searches ||= {}
157 161
158 @query = params[:query] || '' 162 @query = params[:query] || ''
@@ -173,13 +177,22 @@ class SearchController &lt; PublicController @@ -173,13 +177,22 @@ class SearchController &lt; PublicController
173 end 177 end
174 end 178 end
175 179
  180 + AVAILABLE_SEARCHES = ActiveSupport::OrderedHash[
  181 + :articles, _('Contents'),
  182 + :people, _('People'),
  183 + :communities, _('Communities'),
  184 + :enterprises, _('Enterprises'),
  185 + :products, _('Products and Services'),
  186 + :events, _('Events'),
  187 + ]
  188 +
176 def load_search_assets 189 def load_search_assets
177 - if SEARCHES.keys.include?(params[:action].to_sym) && environment.enabled?("disable_asset_#{params[:action]}") 190 + if AVAILABLE_SEARCHES.keys.include?(params[:action].to_sym) && environment.enabled?("disable_asset_#{params[:action]}")
178 render_not_found 191 render_not_found
179 return 192 return
180 end 193 end
181 194
182 - @enabled_searches = SEARCHES.select {|key, name| environment.disabled?("disable_asset_#{key}") } 195 + @enabled_searches = AVAILABLE_SEARCHES.select {|key, name| environment.disabled?("disable_asset_#{key}") }
183 @searching = {} 196 @searching = {}
184 @titles = {} 197 @titles = {}
185 @enabled_searches.each do |key, name| 198 @enabled_searches.each do |key, name|
@@ -189,11 +202,11 @@ class SearchController &lt; PublicController @@ -189,11 +202,11 @@ class SearchController &lt; PublicController
189 @names = @titles if @names.nil? 202 @names = @titles if @names.nil?
190 end 203 end
191 204
192 - def load_filter  
193 - @filter = 'more_recent'  
194 - if SEARCHES.keys.include?(@asset.to_sym)  
195 - available_filters = asset_class(@asset)::SEARCH_FILTERS  
196 - @filter = params[:filter] if available_filters.include?(params[:filter]) 205 + def load_order
  206 + @order = 'more_recent'
  207 + if AVAILABLE_SEARCHES.keys.include?(@asset.to_sym)
  208 + available_orders = asset_class(@asset)::SEARCH_FILTERS[:order]
  209 + @order = params[:order] if available_orders.include?(params[:order])
197 end 210 end
198 end 211 end
199 212
@@ -217,7 +230,7 @@ class SearchController &lt; PublicController @@ -217,7 +230,7 @@ class SearchController &lt; PublicController
217 end 230 end
218 231
219 def full_text_search 232 def full_text_search
220 - @searches[@asset] = find_by_contents(@asset, @scope, @query, paginate_options, {:category => @category, :filter => @filter}) 233 + @searches[@asset] = find_by_contents(@asset, environment, @scope, @query, paginate_options, {:category => @category, :filter => @order})
221 end 234 end
222 235
223 private 236 private
@@ -232,4 +245,14 @@ class SearchController &lt; PublicController @@ -232,4 +245,14 @@ class SearchController &lt; PublicController
232 20 245 20
233 end 246 end
234 247
  248 + def available_assets
  249 + assets = ActiveSupport::OrderedHash[
  250 + :articles, _('Contents'),
  251 + :enterprises, _('Enterprises'),
  252 + :people, _('People'),
  253 + :communities, _('Communities'),
  254 + :products, _('Products and Services'),
  255 + ]
  256 + end
  257 +
235 end 258 end
app/controllers/public_controller.rb
1 class PublicController < ApplicationController 1 class PublicController < ApplicationController
  2 + protected
  3 +
  4 + def allow_access_to_page
  5 + unless profile.display_info_to?(user)
  6 + if profile.visible?
  7 + private_profile
  8 + else
  9 + invisible_profile
  10 + end
  11 + end
  12 + end
  13 +
  14 + def private_profile
  15 + private_profile_partial_parameters
  16 + render :template => 'profile/_private_profile', :status => 403, :formats => [:html]
  17 + end
  18 +
  19 + def invisible_profile
  20 + unless profile.is_template?
  21 + render_access_denied(_("This profile is inaccessible. You don't have the permission to view the content here."), _("Oops ... you cannot go ahead here"))
  22 + end
  23 + end
2 end 24 end
app/helpers/application_helper.rb
@@ -8,11 +8,7 @@ module ApplicationHelper @@ -8,11 +8,7 @@ module ApplicationHelper
8 8
9 include PermissionNameHelper 9 include PermissionNameHelper
10 10
11 - include LightboxHelper  
12 -  
13 - include ThickboxHelper  
14 -  
15 - include ColorboxHelper 11 + include ModalHelper
16 12
17 include BoxesHelper 13 include BoxesHelper
18 14
@@ -46,6 +42,8 @@ module ApplicationHelper @@ -46,6 +42,8 @@ module ApplicationHelper
46 42
47 include CatalogHelper 43 include CatalogHelper
48 44
  45 + include PluginsHelper
  46 +
49 def locale 47 def locale
50 (@page && !@page.language.blank?) ? @page.language : FastGettext.locale 48 (@page && !@page.language.blank?) ? @page.language : FastGettext.locale
51 end 49 end
@@ -594,7 +592,7 @@ module ApplicationHelper @@ -594,7 +592,7 @@ module ApplicationHelper
594 extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' ) 592 extra_info = extra_info.nil? ? '' : content_tag( 'span', extra_info, :class => 'extra_info' )
595 links = links_for_balloon(profile) 593 links = links_for_balloon(profile)
596 content_tag('div', content_tag(tag, 594 content_tag('div', content_tag(tag,
597 - (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ? link_to( content_tag( 'span', _('Profile links')), '#', :onclick => "toggleSubmenu(this, '#{profile.short_name}', #{CGI::escapeHTML(links.to_json)}); return false", :class => "menu-submenu-trigger #{trigger_class}", :url => url) : "") + 595 + (environment.enabled?(:show_balloon_with_profile_links_when_clicked) ? popover_menu(_('Profile links'),profile.short_name,links,{:class => trigger_class, :url => url}) : "") +
598 link_to( 596 link_to(
599 content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) + 597 content_tag( 'span', profile_image( profile, size ), :class => 'profile-image' ) +
600 content_tag( 'span', h(name), :class => ( profile.class == Person ? 'fn' : 'org' ) ) + 598 content_tag( 'span', h(name), :class => ( profile.class == Person ? 'fn' : 'org' ) ) +
@@ -606,6 +604,14 @@ module ApplicationHelper @@ -606,6 +604,14 @@ module ApplicationHelper
606 :class => 'vcard'), :class => 'common-profile-list-block') 604 :class => 'vcard'), :class => 'common-profile-list-block')
607 end 605 end
608 606
  607 + def popover_menu(title,menu_title,links,html_options={})
  608 + html_options[:class] = "" unless html_options[:class]
  609 + html_options[:class] << " menu-submenu-trigger"
  610 + html_options[:onclick] = "toggleSubmenu(this, '#{menu_title}', #{CGI::escapeHTML(links.to_json)}); return false"
  611 +
  612 + link_to(content_tag(:span, title), '#', html_options)
  613 + end
  614 +
609 def gravatar_default 615 def gravatar_default
610 (respond_to?(:theme_option) && theme_option.present? && theme_option['gravatar']) || NOOSFERO_CONF['gravatar'] || 'mm' 616 (respond_to?(:theme_option) && theme_option.present? && theme_option['gravatar']) || NOOSFERO_CONF['gravatar'] || 'mm'
611 end 617 end
@@ -649,8 +655,8 @@ module ApplicationHelper @@ -649,8 +655,8 @@ module ApplicationHelper
649 ' onfocus="if(this.value==\''+s+'\'){this.value=\'\'} this.form.className=\'focus-in\'"'+ 655 ' onfocus="if(this.value==\''+s+'\'){this.value=\'\'} this.form.className=\'focus-in\'"'+
650 ' onblur="if(/^\s*$/.test(this.value)){this.value=\''+s+'\'} this.form.className=\'focus-out\'">'+ 656 ' onblur="if(/^\s*$/.test(this.value)){this.value=\''+s+'\'} this.form.className=\'focus-out\'">'+
651 '</form>' 657 '</form>'
652 - else #opt == 'lightbox_link' is default  
653 - lightbox_link_to '<span class="icon-menu-search"></span>'+ _('Search'), { 658 + else
  659 + modal_link_to '<span class="icon-menu-search"></span>'+ _('Search'), {
654 :controller => 'search', 660 :controller => 'search',
655 :action => 'popup', 661 :action => 'popup',
656 :category_path => (@category ? @category.explode_path : nil)}, 662 :category_path => (@category ? @category.explode_path : nil)},
@@ -720,7 +726,7 @@ module ApplicationHelper @@ -720,7 +726,7 @@ module ApplicationHelper
720 class NoosferoFormBuilder < ActionView::Helpers::FormBuilder 726 class NoosferoFormBuilder < ActionView::Helpers::FormBuilder
721 extend ActionView::Helpers::TagHelper 727 extend ActionView::Helpers::TagHelper
722 728
723 - def self.output_field(text, field_html, field_id = nil) 729 + def self.output_field(text, field_html, field_id = nil, options = {})
724 # try to guess an id if none given 730 # try to guess an id if none given
725 if field_id.nil? 731 if field_id.nil?
726 field_html =~ /id=['"]([^'"]*)['"]/ 732 field_html =~ /id=['"]([^'"]*)['"]/
@@ -862,8 +868,9 @@ module ApplicationHelper @@ -862,8 +868,9 @@ module ApplicationHelper
862 end 868 end
863 869
864 def base_url 870 def base_url
865 - environment.top_url 871 + environment.top_url(request.scheme)
866 end 872 end
  873 + alias :top_url :base_url
867 874
868 def helper_for_article(article) 875 def helper_for_article(article)
869 article_helper = ActionView::Base.new 876 article_helper = ActionView::Base.new
@@ -1047,11 +1054,11 @@ module ApplicationHelper @@ -1047,11 +1054,11 @@ module ApplicationHelper
1047 {s_('contents|Most commented') => {:href => url_for({:controller => 'search', :action => 'contents', :filter => 'more_comments'})}} 1054 {s_('contents|Most commented') => {:href => url_for({:controller => 'search', :action => 'contents', :filter => 'more_comments'})}}
1048 ] 1055 ]
1049 if logged_in? 1056 if logged_in?
1050 - links.push(_('New content') => colorbox_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_user.login, :cms => true})})) 1057 + links.push(_('New content') => modal_options({:href => url_for({:controller => 'cms', :action => 'new', :profile => current_user.login, :cms => true})}))
1051 end 1058 end
1052 1059
1053 link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => nil}, :id => 'submenu-contents') + 1060 link_to(content_tag(:span, _('Contents'), :class => 'icon-menu-articles'), {:controller => "search", :action => 'contents', :category_path => nil}, :id => 'submenu-contents') +
1054 - link_to(content_tag(:span, _('Contents menu')), '#', :onclick => "toggleSubmenu(this,'',#{CGI::escapeHTML(links.to_json)}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-contents-trigger') 1061 + popover_menu(_('Contents menu'),'',links,:class => 'up', :id => 'submenu-contents-trigger')
1055 end 1062 end
1056 alias :browse_contents_menu :search_contents_menu 1063 alias :browse_contents_menu :search_contents_menu
1057 1064
@@ -1067,7 +1074,7 @@ module ApplicationHelper @@ -1067,7 +1074,7 @@ module ApplicationHelper
1067 end 1074 end
1068 1075
1069 link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "search", :action => 'people', :category_path => ''}, :id => 'submenu-people') + 1076 link_to(content_tag(:span, _('People'), :class => 'icon-menu-people'), {:controller => "search", :action => 'people', :category_path => ''}, :id => 'submenu-people') +
1070 - link_to(content_tag(:span, _('People menu')), '#', :onclick => "toggleSubmenu(this,'',#{CGI::escapeHTML(links.to_json)}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-people-trigger') 1077 + popover_menu(_('People menu'),'',links,:class => 'up', :id => 'submenu-people-trigger')
1071 end 1078 end
1072 alias :browse_people_menu :search_people_menu 1079 alias :browse_people_menu :search_people_menu
1073 1080
@@ -1083,7 +1090,7 @@ module ApplicationHelper @@ -1083,7 +1090,7 @@ module ApplicationHelper
1083 end 1090 end
1084 1091
1085 link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "search", :action => 'communities'}, :id => 'submenu-communities') + 1092 link_to(content_tag(:span, _('Communities'), :class => 'icon-menu-community'), {:controller => "search", :action => 'communities'}, :id => 'submenu-communities') +
1086 - link_to(content_tag(:span, _('Communities menu')), '#', :onclick => "toggleSubmenu(this,'',#{CGI::escapeHTML(links.to_json)}); return false", :class => 'menu-submenu-trigger up', :id => 'submenu-communities-trigger') 1093 + popover_menu(_('Communities menu'),'',links,:class => 'up', :id => 'submenu-communities-trigger')
1087 end 1094 end
1088 alias :browse_communities_menu :search_communities_menu 1095 alias :browse_communities_menu :search_communities_menu
1089 1096
@@ -1306,8 +1313,19 @@ module ApplicationHelper @@ -1306,8 +1313,19 @@ module ApplicationHelper
1306 end 1313 end
1307 end 1314 end
1308 1315
1309 - def remove_content_button(action)  
1310 - @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true) 1316 + def content_remove_spread(content)
  1317 + !content.public? || content.folder? || (profile == user && user.communities.blank? && !environment.portal_enabled)
  1318 + end
  1319 +
  1320 + def remove_content_button(action, content)
  1321 + method_name = "content_remove_#{action.to_s}"
  1322 + plugin_condition = @plugins.dispatch(method_name, content).include?(true)
  1323 + begin
  1324 + core_condition = self.send(method_name, content)
  1325 + rescue NoMethodError
  1326 + core_condition = false
  1327 + end
  1328 + core_condition || plugin_condition
1311 end 1329 end
1312 1330
1313 def template_options(kind, field_name) 1331 def template_options(kind, field_name)
@@ -1384,16 +1402,16 @@ module ApplicationHelper @@ -1384,16 +1402,16 @@ module ApplicationHelper
1384 end 1402 end
1385 1403
1386 def convert_macro(html, source) 1404 def convert_macro(html, source)
1387 - doc = Hpricot(html) 1405 + doc = Nokogiri::HTML.fragment html
1388 #TODO This way is more efficient but do not support macro inside of 1406 #TODO This way is more efficient but do not support macro inside of
1389 # macro. You must parse them from the inside-out in order to enable 1407 # macro. You must parse them from the inside-out in order to enable
1390 # that. 1408 # that.
1391 - doc.search('.macro').each do |macro| 1409 + doc.css('.macro').each do |macro|
1392 macro_name = macro['data-macro'] 1410 macro_name = macro['data-macro']
1393 result = @plugins.parse_macro(macro_name, macro, source) 1411 result = @plugins.parse_macro(macro_name, macro, source)
1394 macro.inner_html = result.kind_of?(Proc) ? self.instance_exec(&result) : result 1412 macro.inner_html = result.kind_of?(Proc) ? self.instance_exec(&result) : result
1395 end 1413 end
1396 - doc.html 1414 + doc.to_html
1397 end 1415 end
1398 1416
1399 def default_folder_for_image_upload(profile) 1417 def default_folder_for_image_upload(profile)
@@ -1410,6 +1428,43 @@ module ApplicationHelper @@ -1410,6 +1428,43 @@ module ApplicationHelper
1410 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))}) 1428 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))})
1411 end 1429 end
1412 1430
  1431 + def search_input_with_suggestions(name, asset, default, options = {})
  1432 + text_field_tag name, default, options.merge({:class => 'search-input-with-suggestions', 'data-asset' => asset})
  1433 + end
  1434 +
  1435 + def profile_suggestion_profile_connections(suggestion)
  1436 + profiles = suggestion.profile_connections.first(4).map do |profile|
  1437 + link_to(profile_image(profile, :icon, :title => profile.name), profile.url, :class => 'profile-suggestion-connection-icon')
  1438 + end
  1439 +
  1440 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1441 + profiles << link_to("<big> +#{suggestion.profile_connections.count - 4}</big>", :controller => controller_target, :action => :connections, :id => suggestion.suggestion_id) if suggestion.profile_connections.count > 4
  1442 +
  1443 + if profiles.present?
  1444 + content_tag(:div, profiles.join , :class => 'profile-connections')
  1445 + else
  1446 + ''
  1447 + end
  1448 + end
  1449 +
  1450 + def profile_suggestion_tag_connections(suggestion)
  1451 + tags = suggestion.tag_connections.first(4).map do |tag|
  1452 + tag.name + ', '
  1453 + end
  1454 + last_tag = tags.pop
  1455 + tags << last_tag.strip.chop if last_tag.present?
  1456 + title = tags.join
  1457 +
  1458 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1459 + tags << ' ' + link_to('...', {:controller => controller_target, :action => :connections, :id => suggestion.suggestion_id}, :class => 'more-tag-connections', :title => _('See all connections')) if suggestion.tag_connections.count > 4
  1460 +
  1461 + if tags.present?
  1462 + content_tag(:div, tags.join, :class => 'tag-connections', :title => title)
  1463 + else
  1464 + ''
  1465 + end
  1466 + end
  1467 +
1413 def labelled_colorpicker_field(human_name, object_name, method, options = {}) 1468 def labelled_colorpicker_field(human_name, object_name, method, options = {})
1414 options[:id] ||= 'text-field-' + FormsHelper.next_id_number 1469 options[:id] ||= 'text-field-' + FormsHelper.next_id_number
1415 content_tag('label', human_name, :for => options[:id], :class => 'formlabel') + 1470 content_tag('label', human_name, :for => options[:id], :class => 'formlabel') +
app/helpers/article_helper.rb
1 module ArticleHelper 1 module ArticleHelper
2 2
3 - include PrototypeHelper  
4 include TokenHelper 3 include TokenHelper
5 4
6 def article_reported_version(article) 5 def article_reported_version(article)
@@ -35,7 +34,7 @@ module ArticleHelper @@ -35,7 +34,7 @@ module ArticleHelper
35 'div', 34 'div',
36 check_box(:article, :notify_comments) + 35 check_box(:article, :notify_comments) +
37 content_tag('label', _('I want to receive a notification of each comment written by e-mail'), :for => 'article_notify_comments') + 36 content_tag('label', _('I want to receive a notification of each comment written by e-mail'), :for => 'article_notify_comments') +
38 - observe_field(:article_accept_comments, :function => "$('article_notify_comments').disabled = ! $('article_accept_comments').checked;$('article_moderate_comments').disabled = ! $('article_accept_comments').checked") 37 + observe_field(:article_accept_comments, :function => "jQuery('#article_notify_comments')[0].disabled = ! jQuery('#article_accept_comments')[0].checked;jQuery('#article_moderate_comments')[0].disabled = ! jQuery('#article_accept_comments')[0].checked")
39 ) + 38 ) +
40 39
41 content_tag( 40 content_tag(
app/helpers/block_helper.rb
@@ -19,7 +19,7 @@ module BlockHelper @@ -19,7 +19,7 @@ module BlockHelper
19 content_tag('span', _('Title')) + 19 content_tag('span', _('Title')) +
20 text_field_tag('block[images][][title]', image[:title], :class => 'highlight-title', :size => 45) 20 text_field_tag('block[images][][title]', image[:title], :class => 'highlight-title', :size => 45)
21 }</label></td> 21 }</label></td>
22 - <td>#{link_to '', '#', :class=>'button icon-button icon-delete delete-highlight', :confirm=>_('Are you sure you want to remove this highlight')}</td> 22 + <td>#{button_without_text(:delete, _('Remove'), '#', class: 'delete-highlight', :confirm=>_('Are you sure you want to remove this highlight'))}</td>
23 </tr> 23 </tr>
24 " 24 "
25 end 25 end
app/helpers/boxes_helper.rb
@@ -38,8 +38,12 @@ module BoxesHelper @@ -38,8 +38,12 @@ module BoxesHelper
38 end 38 end
39 end 39 end
40 40
  41 + def boxes_limit holder
  42 + controller.send(:custom_design)[:boxes_limit] || holder.boxes_limit(controller.send(:custom_design)[:layout_template])
  43 + end
  44 +
41 def display_boxes(holder, main_content) 45 def display_boxes(holder, main_content)
42 - boxes = holder.boxes.with_position.first(holder.boxes_limit) 46 + boxes = holder.boxes.with_position.first(boxes_limit(holder))
43 content = boxes.reverse.map { |item| display_box(item, main_content) }.join("\n") 47 content = boxes.reverse.map { |item| display_box(item, main_content) }.join("\n")
44 content = main_content if (content.blank?) 48 content = main_content if (content.blank?)
45 49
@@ -65,11 +69,13 @@ module BoxesHelper @@ -65,11 +69,13 @@ module BoxesHelper
65 end 69 end
66 70
67 def display_box_content(box, main_content) 71 def display_box_content(box, main_content)
68 - context = { :article => @page, :request_path => request.path, :locale => locale, :params => request.params, :user => user }  
69 - box_decorator.select_blocks(box.blocks.includes(:box), context).map { |item| display_block(item, main_content) }.join("\n") + box_decorator.block_target(box) 72 + context = { :article => @page, :request_path => request.path, :locale => locale, :params => request.params, :user => user, :controller => controller }
  73 + box_decorator.select_blocks(box, box.blocks.includes(:box), context).map do |item|
  74 + display_block item, main_content
  75 + end.join("\n") + box_decorator.block_target(box)
70 end 76 end
71 77
72 - def select_blocks(arr, context) 78 + def select_blocks box, arr, context
73 arr 79 arr
74 end 80 end
75 81
@@ -150,8 +156,22 @@ module BoxesHelper @@ -150,8 +156,22 @@ module BoxesHelper
150 def self.block_edit_buttons(block) 156 def self.block_edit_buttons(block)
151 '' 157 ''
152 end 158 end
153 - def self.select_blocks(arr, context)  
154 - arr.select { |block| block.visible?(context) } 159 + def self.select_blocks box, arr, context
  160 + arr = arr.select{ |block| block.visible? context }
  161 +
  162 + custom_design = context[:controller].send(:custom_design)
  163 + inserts = [custom_design[:insert]].flatten.compact
  164 + inserts.each do |insert_opts|
  165 + next unless box.position == insert_opts[:box]
  166 + position, block = insert_opts[:position], insert_opts[:block]
  167 + block = block.new box: box if block.is_a? Class
  168 +
  169 + if not insert_opts[:uniq] or not box.blocks.map(&:class).include? block.klass
  170 + arr = arr.insert position, block
  171 + end
  172 + end
  173 +
  174 + arr
155 end 175 end
156 end 176 end
157 177
@@ -211,7 +231,7 @@ module BoxesHelper @@ -211,7 +231,7 @@ module BoxesHelper
211 end 231 end
212 232
213 if block.editable? 233 if block.editable?
214 - buttons << colorbox_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id }) 234 + buttons << modal_icon_button(:edit, _('Edit'), { :action => 'edit', :id => block.id })
215 end 235 end
216 236
217 if !block.main? 237 if !block.main?
@@ -221,7 +241,7 @@ module BoxesHelper @@ -221,7 +241,7 @@ module BoxesHelper
221 end 241 end
222 242
223 if block.respond_to?(:help) 243 if block.respond_to?(:help)
224 - buttons << thickbox_inline_popup_icon(:help, _('Help on this block'), {}, "help-on-box-#{block.id}") << content_tag('div', content_tag('h2', _('Help')) + content_tag('div', block.help, :style => 'margin-bottom: 1em;') + thickbox_close_button(_('Close')), :style => 'display: none;', :id => "help-on-box-#{block.id}") 244 + buttons << modal_inline_icon(:help, _('Help on this block'), {}, "#help-on-box-#{block.id}") << content_tag('div', content_tag('h2', _('Help')) + content_tag('div', block.help, :style => 'margin-bottom: 1em;') + modal_close_button(_('Close')), :style => 'display: none;', :id => "help-on-box-#{block.id}")
225 end 245 end
226 246
227 if block.embedable? 247 if block.embedable?
app/helpers/categories_helper.rb
@@ -25,10 +25,13 @@ module CategoriesHelper @@ -25,10 +25,13 @@ module CategoriesHelper
25 ) 25 )
26 end 26 end
27 27
28 - def update_categories_link(body, category_id=nil, html_options={}) 28 + #TODO: remove this function and, in views, use existing basic buttons
  29 + def update_categories_link(type, body, category_id=nil, html_options={})
  30 + html_class = 'select-subcategory-link'
  31 + html_class = " icon-#{type} btn btn-primary btn-xs" if type.present?
29 link_to body, 32 link_to body,
30 { :action => "update_categories", :category_id => category_id, :id => @object }, 33 { :action => "update_categories", :category_id => category_id, :id => @object },
31 - {:id => category_id ? "select-category-#{category_id}-link" : nil, :remote => true, :class => 'select-subcategory-link'}.merge(html_options) 34 + {:id => category_id ? "select-category-#{category_id}-link" : nil, :remote => true, :class => html_class}.merge(html_options)
32 end 35 end
33 36
34 end 37 end
app/helpers/chat_helper.rb
@@ -6,8 +6,9 @@ module ChatHelper @@ -6,8 +6,9 @@ module ChatHelper
6 ['icon-menu-busy', _('Busy'), 'chat-busy'], 6 ['icon-menu-busy', _('Busy'), 'chat-busy'],
7 ['icon-menu-offline', _('Sign out of chat'), 'chat-disconnect'], 7 ['icon-menu-offline', _('Sign out of chat'), 'chat-disconnect'],
8 ] 8 ]
  9 + avatar = profile_image(user, :portrait, :class => 'avatar')
9 content_tag('span', 10 content_tag('span',
10 - link_to(content_tag('span', status) + ui_icon('ui-icon-triangle-1-s'), 11 + link_to(avatar + content_tag('span', user.name) + ui_icon('ui-icon-triangle-1-s'),
11 '#', 12 '#',
12 :onclick => 'toggleMenu(this); return false', 13 :onclick => 'toggleMenu(this); return false',
13 :class => icon_class + ' simplemenu-trigger' 14 :class => icon_class + ' simplemenu-trigger'
app/helpers/cms_helper.rb
@@ -40,12 +40,8 @@ module CmsHelper @@ -40,12 +40,8 @@ module CmsHelper
40 end 40 end
41 end 41 end
42 42
43 - def display_spread_button(profile, article)  
44 - if profile.person?  
45 - expirable_button article, :spread, _('Spread this'), :action => 'publish', :id => article.id  
46 - elsif profile.community? && environment.portal_community  
47 - expirable_button article, :spread, _('Spread this'), :action => 'publish_on_portal_community', :id => article.id  
48 - end 43 + def display_spread_button(article)
  44 + expirable_button article, :spread, _('Spread this'), {:action => 'publish', :id => article.id}, {:class => 'colorbox'}
49 end 45 end
50 46
51 def display_delete_button(article) 47 def display_delete_button(article)
app/helpers/colorbox_helper.rb
@@ -1,25 +0,0 @@ @@ -1,25 +0,0 @@
1 -module ColorboxHelper  
2 -  
3 - def colorbox_close_button(text, options = {})  
4 - button(:close, text, '#', colorbox_options(options, :close))  
5 - end  
6 -  
7 - def colorbox_button(type, label, url, options = {})  
8 - button(type, label, url, colorbox_options(options))  
9 - end  
10 -  
11 - def colorbox_icon_button(type, label, url, options = {})  
12 - icon_button(type, label, url, colorbox_options(options))  
13 - end  
14 -  
15 - # options must be an HTML options hash as passed to link_to etc.  
16 - #  
17 - # returns a new hash with colorbox class added. Keeps existing classes.  
18 - def colorbox_options(options, type=nil)  
19 - the_class = 'colorbox'  
20 - the_class += "-#{type.to_s}" unless type.nil?  
21 - the_class << " #{options[:class]}" if options.has_key?(:class)  
22 - options.merge(:class => the_class)  
23 - end  
24 -  
25 -end  
app/helpers/comment_helper.rb
@@ -65,7 +65,7 @@ module CommentHelper @@ -65,7 +65,7 @@ module CommentHelper
65 65
66 def link_for_edit(comment) 66 def link_for_edit(comment)
67 if comment.can_be_updated_by?(user) 67 if comment.can_be_updated_by?(user)
68 - {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'colorbox')} 68 + {:link => expirable_comment_link(comment, :edit, _('Edit'), url_for(:profile => profile.identifier, :controller => :comment, :action => :edit, :id => comment.id),:class => 'modal')}
69 end 69 end
70 end 70 end
71 71
app/helpers/dates_helper.rb
@@ -2,24 +2,14 @@ require &#39;noosfero/i18n&#39; @@ -2,24 +2,14 @@ require &#39;noosfero/i18n&#39;
2 2
3 module DatesHelper 3 module DatesHelper
4 4
5 - # FIXME Date#strftime should translate this for us !!!!  
6 - MONTHS = [  
7 - N_('January'),  
8 - N_('February'),  
9 - N_('March'),  
10 - N_('April'),  
11 - N_('May'),  
12 - N_('June'),  
13 - N_('July'),  
14 - N_('August'),  
15 - N_('September'),  
16 - N_('October'),  
17 - N_('November'),  
18 - N_('December')  
19 - ]  
20 -  
21 - def month_name(n)  
22 - _(MONTHS[n-1]) 5 + MONTHS = I18n.t('date.month_names')
  6 +
  7 + def month_name(n, abbreviated = false)
  8 + if abbreviated
  9 + I18n.t('date.abbr_month_names')[n]
  10 + else
  11 + MONTHS[n]
  12 + end
23 end 13 end
24 14
25 # formats a date for displaying. 15 # formats a date for displaying.
@@ -91,15 +81,7 @@ module DatesHelper @@ -91,15 +81,7 @@ module DatesHelper
91 _(date.strftime("%a")) 81 _(date.strftime("%a"))
92 else 82 else
93 # FIXME Date#strftime should translate this for us !!!! 83 # FIXME Date#strftime should translate this for us !!!!
94 - _([  
95 - N_('Sunday'),  
96 - N_('Monday'),  
97 - N_('Tuesday'),  
98 - N_('Wednesday'),  
99 - N_('Thursday'),  
100 - N_('Friday'),  
101 - N_('Saturday'),  
102 - ][date.wday]) 84 + I18n.t('date.day_names')[date.wday]
103 end 85 end
104 end 86 end
105 87
@@ -111,7 +93,7 @@ module DatesHelper @@ -111,7 +93,7 @@ module DatesHelper
111 date = date << 1 93 date = date << 1
112 end 94 end
113 if opts[:only_month] 95 if opts[:only_month]
114 - _('%{month}') % {:month => month_name(date.month.to_i) } 96 + _('%{month}') % { :month => month_name(date.month.to_i) }
115 else 97 else
116 _('%{month} %{year}') % { :year => date.year, :month => month_name(date.month.to_i) } 98 _('%{month} %{year}') % { :year => date.year, :month => month_name(date.month.to_i) }
117 end 99 end
@@ -156,7 +138,7 @@ module DatesHelper @@ -156,7 +138,7 @@ module DatesHelper
156 else 138 else
157 order = [:day, :month, :year] 139 order = [:day, :month, :year]
158 end 140 end
159 - date_select(object, method, html_options.merge(options.merge(:include_blank => true, :order => order, :use_month_names => MONTHS.map {|item| gettext(item)}))) 141 + date_select(object, method, html_options.merge(options.merge(:include_blank => true, :order => order, :use_month_names => MONTHS)))
160 end 142 end
161 143
162 end 144 end
app/helpers/design_helper.rb 0 → 100644
@@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
  1 +module DesignHelper
  2 +
  3 + extend ActiveSupport::Concern
  4 +
  5 + included do
  6 + extend ClassMethods
  7 + include InstanceMethods
  8 + before_filter :load_custom_design if self.respond_to? :before_filter
  9 + end
  10 +
  11 + module ClassMethods
  12 +
  13 + def no_design_blocks
  14 + @no_design_blocks = true
  15 + end
  16 +
  17 + def use_custom_design options = {}
  18 + @custom_design = options
  19 + end
  20 +
  21 + def custom_design
  22 + @custom_design ||= {}
  23 + end
  24 +
  25 + def uses_design_blocks?
  26 + !@no_design_blocks
  27 + end
  28 +
  29 + end
  30 +
  31 + module InstanceMethods
  32 +
  33 + protected
  34 +
  35 + def uses_design_blocks?
  36 + !@no_design_blocks && self.class.uses_design_blocks?
  37 + end
  38 +
  39 + def load_custom_design
  40 + # see also: LayoutHelper#body_classes
  41 + @layout_template = self.class.custom_design[:layout_template]
  42 + end
  43 +
  44 + def custom_design
  45 + @custom_design || self.class.custom_design
  46 + end
  47 +
  48 + end
  49 +
  50 +end
app/helpers/forms_helper.rb
@@ -265,7 +265,7 @@ module FormsHelper @@ -265,7 +265,7 @@ module FormsHelper
265 ) 265 )
266 end 266 end
267 267
268 - def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {}, find_options = {}) 268 + def select_profile_folder(label_text, field_id, profile, default_value='', html_options = {}, js_options = {}, find_options = {}, extra_options = {})
269 if find_options.empty? 269 if find_options.empty?
270 folders = profile.folders 270 folders = profile.folders
271 else 271 else
@@ -276,7 +276,7 @@ module FormsHelper @@ -276,7 +276,7 @@ module FormsHelper
276 select_tag( 276 select_tag(
277 field_id, 277 field_id,
278 options_for_select( 278 options_for_select(
279 - [[profile.identifier, '']] + 279 + [[(extra_options[:root_label] || profile.identifier), '']] +
280 folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] }, 280 folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] },
281 default_value.to_s 281 default_value.to_s
282 ), 282 ),
app/helpers/language_helper.rb
1 module LanguageHelper 1 module LanguageHelper
2 def language 2 def language
3 - locale 3 + locale.to_s
4 end 4 end
5 5
6 def tinymce_language 6 def tinymce_language
@@ -20,7 +20,7 @@ module LanguageHelper @@ -20,7 +20,7 @@ module LanguageHelper
20 separator = options[:separator] || ' &mdash; ' 20 separator = options[:separator] || ' &mdash; '
21 21
22 if options[:element] == 'dropdown' 22 if options[:element] == 'dropdown'
23 - select_tag('lang', 23 + select_tag('lang',
24 options_for_select(locales.map{|code,name| [name, code]}, current), 24 options_for_select(locales.map{|code,name| [name, code]}, current),
25 :onchange => "document.location.href= #{url_for(params.merge(:lang => 'LANGUAGE'))}.replace(/LANGUAGE/, this.value) ;", 25 :onchange => "document.location.href= #{url_for(params.merge(:lang => 'LANGUAGE'))}.replace(/LANGUAGE/, this.value) ;",
26 :help => _('The language you choose here is the language used for options, buttons, etc. It does not affect the language of the content created by other users.') 26 :help => _('The language you choose here is the language used for options, buttons, etc. It does not affect the language of the content created by other users.')
app/helpers/layout_helper.rb
@@ -31,12 +31,12 @@ module LayoutHelper @@ -31,12 +31,12 @@ module LayoutHelper
31 plugins_javascripts = @plugins.map { |plugin| [plugin.js_files].flatten.map { |js| plugin.class.public_path(js) } }.flatten 31 plugins_javascripts = @plugins.map { |plugin| [plugin.js_files].flatten.map { |js| plugin.class.public_path(js) } }.flatten
32 32
33 output = '' 33 output = ''
34 - output += render :file => 'layouts/_javascript'  
35 - output += javascript_tag 'render_all_jquery_ui_widgets()' 34 + output += render 'layouts/javascript'
36 unless plugins_javascripts.empty? 35 unless plugins_javascripts.empty?
37 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}" 36 output += javascript_include_tag plugins_javascripts, :cache => "cache/plugins-#{Digest::MD5.hexdigest plugins_javascripts.to_s}"
38 end 37 end
39 output += theme_javascript_ng.to_s 38 output += theme_javascript_ng.to_s
  39 + output += javascript_tag 'render_all_jquery_ui_widgets()'
40 40
41 output 41 output
42 end 42 end
@@ -45,10 +45,10 @@ module LayoutHelper @@ -45,10 +45,10 @@ module LayoutHelper
45 standard_stylesheets = [ 45 standard_stylesheets = [
46 'application', 46 'application',
47 'search', 47 'search',
48 - 'thickbox',  
49 - 'lightbox',  
50 'colorbox', 48 'colorbox',
  49 + 'selectordie',
51 'inputosaurus', 50 'inputosaurus',
  51 + 'chat',
52 pngfix_stylesheet_path, 52 pngfix_stylesheet_path,
53 ] + tokeninput_stylesheets 53 ] + tokeninput_stylesheets
54 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') } 54 plugins_stylesheets = @plugins.select(&:stylesheet?).map { |plugin| plugin.class.public_path('style.css') }
@@ -85,6 +85,7 @@ module LayoutHelper @@ -85,6 +85,7 @@ module LayoutHelper
85 end 85 end
86 end 86 end
87 87
  88 +
88 def icon_theme_stylesheet_path 89 def icon_theme_stylesheet_path
89 icon_themes = [] 90 icon_themes = []
90 theme_icon_themes = theme_option(:icon_theme) || [] 91 theme_icon_themes = theme_option(:icon_theme) || []
@@ -115,8 +116,5 @@ module LayoutHelper @@ -115,8 +116,5 @@ module LayoutHelper
115 end 116 end
116 end 117 end
117 118
118 - def meta_description_tag(article=nil)  
119 - article ? CGI.escapeHTML(truncate(strip_tags(article.body.to_s), :length => 200)) : environment.name  
120 - end  
121 end 119 end
122 120
app/helpers/lightbox_helper.rb
@@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
1 -module LightboxHelper  
2 -  
3 - def lightbox_link_to(text, url, options = {})  
4 - link_to(text, url, lightbox_options(options))  
5 - end  
6 -  
7 - def lightbox_close_button(text, options = {})  
8 - button(:close, text, '#', lightbox_options(options, 'lbAction').merge(:rel => 'deactivate'))  
9 - end  
10 -  
11 - def lightbox_button(type, label, url, options = {})  
12 - button(type, label, url, lightbox_options(options))  
13 - end  
14 -  
15 - def lightbox_icon_button(type, label, url, options = {})  
16 - icon_button(type, label, url, lightbox_options(options))  
17 - end  
18 -  
19 - # options must be an HTML options hash as passed to link_to etc.  
20 - #  
21 - # returns a new hash with lightbox class added. Keeps existing classes.  
22 - def lightbox_options(options, lightbox_type = 'lbOn')  
23 - the_class = lightbox_type  
24 - the_class << " #{options[:class]}" if options.has_key?(:class)  
25 - options.merge(:class => the_class)  
26 - end  
27 -  
28 - def lightbox?  
29 - request.xhr?  
30 - end  
31 -  
32 - def lightbox_remote_button(type, label, url, options = {})  
33 - button(type, label, url, lightbox_options(options, 'remote-lbOn'))  
34 - end  
35 -  
36 -end  
app/helpers/manage_products_helper.rb
@@ -137,7 +137,7 @@ module ManageProductsHelper @@ -137,7 +137,7 @@ module ManageProductsHelper
137 ui_button_to_remote(label, 137 ui_button_to_remote(label,
138 {:update => "product-#{field}", 138 {:update => "product-#{field}",
139 :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field }, 139 :url => { :controller => 'manage_products', :action => "edit", :id => product.id, :field => field },
140 - :complete => "$('edit-product-button-ui-#{field}').hide()", 140 + :complete => "jQuery('#edit-product-button-ui-#{field}').hide()",
141 :method => :get, 141 :method => :get,
142 :loading => "loading_for_button('##{id}')"}, 142 :loading => "loading_for_button('##{id}')"},
143 options) 143 options)
app/helpers/modal_helper.rb 0 → 100644
@@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
  1 +module ModalHelper
  2 +
  3 + def modal_inline_link_to title, url, selector, options = {}
  4 + link_to title, url, modal_options(options.merge(:inline => selector))
  5 + end
  6 +
  7 + def modal_inline_icon type, title, url, selector, options = {}
  8 + icon_button type, title, url, modal_options(options.merge(:inline => selector))
  9 + end
  10 +
  11 + def modal_link_to title, url, options = {}
  12 + link_to title, url, modal_options(options)
  13 + end
  14 +
  15 + def modal_close_link text, options = {}
  16 + link_to text, '#', modal_options(options, :close)
  17 + end
  18 +
  19 + def modal_close_button(text, options = {})
  20 + button :close, text, '#', modal_options(options, :close).merge(:rel => 'deactivate')
  21 + end
  22 +
  23 + def modal_button(type, label, url, options = {})
  24 + button type, label, url, modal_options(options)
  25 + end
  26 +
  27 + def modal_icon_button(type, label, url, options = {})
  28 + icon_button type, label, url, modal_options(options)
  29 + end
  30 +
  31 + # options must be an HTML options hash as passed to link_to etc.
  32 + #
  33 + # returns a new hash with modal class added. Keeps existing classes.
  34 + def modal_options(options, type=nil)
  35 + inline_selector = options.delete :inline
  36 + options[:onclick] = "return noosfero.modal.inline('#{inline_selector}')" if inline_selector
  37 +
  38 + classes = if inline_selector then '' else 'modal-toggle' end
  39 + classes += " modal-#{type.to_s}" if type.present?
  40 + classes << " #{options[:class]}" if options.has_key? :class
  41 + options.merge!(:class => classes)
  42 +
  43 + options
  44 + end
  45 +
  46 +end
app/helpers/person_notifier_helper.rb
@@ -1,15 +0,0 @@ @@ -1,15 +0,0 @@
1 -module PersonNotifierHelper  
2 -  
3 - include ApplicationHelper  
4 -  
5 - private  
6 -  
7 - def path_to_image(source)  
8 - top_url + source  
9 - end  
10 -  
11 - def top_url  
12 - top_url = @profile.environment ? @profile.environment.top_url : ''  
13 - end  
14 -  
15 -end  
app/helpers/plugins_helper.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +module PluginsHelper
  2 +
  3 + def plugins_product_tabs
  4 + @plugins.dispatch(:product_tabs, @product).map do |tab|
  5 + {:title => tab[:title], :id => tab[:id], :content => instance_eval(&tab[:content])}
  6 + end
  7 + end
  8 +
  9 +end
app/helpers/search_helper.rb
@@ -5,20 +5,23 @@ module SearchHelper @@ -5,20 +5,23 @@ module SearchHelper
5 BLOCKS_SEARCH_LIMIT = 24 5 BLOCKS_SEARCH_LIMIT = 24
6 MULTIPLE_SEARCH_LIMIT = 8 6 MULTIPLE_SEARCH_LIMIT = 8
7 7
8 - SEARCHES = ActiveSupport::OrderedHash[  
9 - :articles, _('Contents'),  
10 - :enterprises, _('Enterprises'),  
11 - :people, _('People'),  
12 - :communities, _('Communities'),  
13 - :products, _('Products and Services'),  
14 - :events, _('Events'),  
15 - ] 8 + FILTERS_TRANSLATIONS = {
  9 + :order => _('Order'),
  10 + :display => _('Display')
  11 + }
16 12
17 - FILTER_TRANSLATION = {  
18 - 'more_popular' => _('More popular'),  
19 - 'more_active' => _('More active'),  
20 - 'more_recent' => _('More recent'),  
21 - 'more_comments' => _('More comments') 13 + FILTERS_OPTIONS_TRANSLATION = {
  14 + :order => {
  15 + 'more_popular' => _('More popular'),
  16 + 'more_active' => _('More active'),
  17 + 'more_recent' => _('More recent'),
  18 + 'more_comments' => _('More comments')
  19 + },
  20 + :display => {
  21 + 'map' => _('Map'),
  22 + 'full' => _('Full'),
  23 + 'compact' => _('Compact')
  24 + }
22 } 25 }
23 26
24 COMMON_PROFILE_LIST_BLOCK = [ 27 COMMON_PROFILE_LIST_BLOCK = [
@@ -56,7 +59,7 @@ module SearchHelper @@ -56,7 +59,7 @@ module SearchHelper
56 end 59 end
57 60
58 def display?(asset, mode) 61 def display?(asset, mode)
59 - defined?(asset_class(asset)::SEARCH_DISPLAYS) && asset_class(asset)::SEARCH_DISPLAYS.include?(mode.to_s) 62 + defined?(asset_class(asset)::SEARCH_FILTERS[:display]) && asset_class(asset)::SEARCH_FILTERS[:display].include?(mode.to_s)
60 end 63 end
61 64
62 def display_results(searches=nil, asset=nil) 65 def display_results(searches=nil, asset=nil)
@@ -93,6 +96,16 @@ module SearchHelper @@ -93,6 +96,16 @@ module SearchHelper
93 end 96 end
94 end 97 end
95 98
  99 + def select_filter(name, options, default = nil)
  100 + if options.size <= 1
  101 + return
  102 + else
  103 + options = options.map {|option| [FILTERS_OPTIONS_TRANSLATION[name][option], option]}
  104 + options = options_for_select(options, :selected => (params[name] || default))
  105 + select_tag(name, options)
  106 + end
  107 + end
  108 +
96 def display_selector(asset, display, float = 'right') 109 def display_selector(asset, display, float = 'right')
97 display = nil if display.blank? 110 display = nil if display.blank?
98 display ||= asset_class(asset).default_search_display 111 display ||= asset_class(asset).default_search_display
@@ -107,36 +120,32 @@ module SearchHelper @@ -107,36 +120,32 @@ module SearchHelper
107 end 120 end
108 end 121 end
109 122
110 - def filter_selector(asset, filter, float = 'right') 123 + def filters(asset)
  124 + return if !asset
111 klass = asset_class(asset) 125 klass = asset_class(asset)
112 - if klass::SEARCH_FILTERS.count > 1  
113 - options = options_for_select(klass::SEARCH_FILTERS.map {|f| [FILTER_TRANSLATION[f], f]}, filter)  
114 - url_params = url_for(params.merge(:filter => 'FILTER'))  
115 - onchange = "document.location.href = '#{url_params}'.replace('FILTER', this.value)"  
116 - select_field = select_tag(:filter, options, :onchange => onchange)  
117 - content_tag('div',  
118 - content_tag('strong', _('Filter')) + ': ' + select_field,  
119 - :class => "search-customize-options"  
120 - )  
121 - end 126 + content_tag('div', klass::SEARCH_FILTERS.map do |name, options|
  127 + default = klass.respond_to?("default_search_#{name}") ? klass.send("default_search_#{name}".to_s) : nil
  128 + select_filter(name, options, default)
  129 + end.join("\n"), :id => 'search-filters')
  130 + end
  131 +
  132 + def assets_menu(selected)
  133 + assets = @enabled_searches.keys
  134 + # Events is a search asset but do not have a good interface for
  135 + #TODO searching. When this is solved we may add it back again to the assets
  136 + # menu.
  137 + assets.delete(:events)
  138 + content_tag('ul',
  139 + assets.map do |asset|
  140 + options = {}
  141 + options.merge!(:class => 'selected') if selected.to_s == asset.to_s
  142 + content_tag('li', asset_link(asset), options)
  143 + end.join("\n"),
  144 + :id => 'assets-menu')
122 end 145 end
123 146
124 - def filter_title(asset, filter)  
125 - {  
126 - 'articles_more_recent' => _('More recent contents from network'),  
127 - 'articles_more_popular' => _('More viewed contents from network'),  
128 - 'articles_more_comments' => _('Most commented contents from network'),  
129 - 'people_more_recent' => _('More recent people from network'),  
130 - 'people_more_active' => _('More active people from network'),  
131 - 'people_more_popular' => _('More popular people from network'),  
132 - 'communities_more_recent' => _('More recent communities from network'),  
133 - 'communities_more_active' => _('More active communities from network'),  
134 - 'communities_more_popular' => _('More popular communities from network'),  
135 - 'enterprises_more_recent' => _('More recent enterprises from network'),  
136 - 'enterprises_more_active' => _('More active enterprises from network'),  
137 - 'enterprises_more_popular' => _('More popular enterprises from network'),  
138 - 'products_more_recent' => _('Highlights'),  
139 - }[asset.to_s + '_' + filter].to_s 147 + def asset_link(asset)
  148 + link_to(@enabled_searches[asset], "/search/#{asset}")
140 end 149 end
141 150
142 end 151 end
app/helpers/search_term_helper.rb 0 → 100644
@@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
  1 +module SearchTermHelper
  2 + def register_search_term(term, total, indexed, context, asset='all')
  3 + normalized_term = normalize_term(term)
  4 + if normalized_term.present?
  5 + search_term = SearchTerm.find_or_create(normalized_term, context, asset)
  6 + SearchTermOccurrence.create!(:search_term => search_term, :total => total, :indexed => indexed)
  7 + end
  8 + end
  9 +
  10 + #FIXME For some reason the job is created but nothing is ran.
  11 + #handle_asynchronously :register_search_term
  12 +
  13 + #TODO Think smarter criteria to normalize search terms properly
  14 + def normalize_term(search_term)
  15 + search_term ||= ''
  16 + search_term.downcase
  17 + end
  18 +end
app/helpers/thickbox_helper.rb
@@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
1 -module ThickboxHelper  
2 - def thickbox_inline_popup_link(title, url, id, options = {})  
3 - link_to(title, url_for(url) + "#TB_inline?height=300&width=500&inlineId=#{id}&modal=true", {:class => 'thickbox'}.merge(options))  
4 - end  
5 - def thickbox_inline_popup_icon(type, title, url, id, options = {})  
6 - icon_button(type, title, url_for(url) + "#TB_inline?height=300&width=500&inlineId=#{id}&modal=true", {:class => "thickbox"}.merge(options))  
7 - end  
8 - def thickbox_close_button(title)  
9 - button_to_function(:close, title, 'tb_remove();')  
10 - end  
11 -end  
app/helpers/tinymce_helper.rb
@@ -11,7 +11,7 @@ module TinymceHelper @@ -11,7 +11,7 @@ module TinymceHelper
11 end 11 end
12 12
13 def tinymce_init_js options = {} 13 def tinymce_init_js options = {}
14 - options.merge! :document_base_url => environment.top_url, 14 + options.merge! :document_base_url => top_url,
15 :content_css => "/stylesheets/tinymce.css,#{macro_css_files}", 15 :content_css => "/stylesheets/tinymce.css,#{macro_css_files}",
16 :plugins => %w[compat3x advlist autolink lists link image charmap print preview hr anchor pagebreak 16 :plugins => %w[compat3x advlist autolink lists link image charmap print preview hr anchor pagebreak
17 searchreplace wordcount visualblocks visualchars code fullscreen 17 searchreplace wordcount visualblocks visualchars code fullscreen
app/helpers/token_helper.rb
@@ -12,6 +12,7 @@ module TokenHelper @@ -12,6 +12,7 @@ module TokenHelper
12 options[:search_delay] ||= 1000 12 options[:search_delay] ||= 1000
13 options[:prevent_duplicates] ||= true 13 options[:prevent_duplicates] ||= true
14 options[:backspace_delete_item] ||= false 14 options[:backspace_delete_item] ||= false
  15 + options[:zindex] ||= 999
15 options[:focus] ||= false 16 options[:focus] ||= false
16 options[:avoid_enter] ||= true 17 options[:avoid_enter] ||= true
17 options[:on_result] ||= 'null' 18 options[:on_result] ||= 'null'
@@ -31,6 +32,7 @@ module TokenHelper @@ -31,6 +32,7 @@ module TokenHelper
31 searchDelay: #{options[:search_delay].to_json}, 32 searchDelay: #{options[:search_delay].to_json},
32 preventDuplicates: #{options[:prevent_duplicates].to_json}, 33 preventDuplicates: #{options[:prevent_duplicates].to_json},
33 backspaceDeleteItem: #{options[:backspace_delete_item].to_json}, 34 backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
  35 + zindex: #{options[:zindex].to_json},
34 queryParam: #{options[:query_param].to_json}, 36 queryParam: #{options[:query_param].to_json},
35 tokenLimit: #{options[:token_limit].to_json}, 37 tokenLimit: #{options[:token_limit].to_json},
36 onResult: #{options[:on_result]}, 38 onResult: #{options[:on_result]},
app/mailers/comment_notifier.rb
1 -class Comment::Notifier < ActionMailer::Base 1 +class CommentNotifier < ActionMailer::Base
2 def notification(comment) 2 def notification(comment)
3 profile = comment.article.profile 3 profile = comment.article.profile
4 @recipient = profile.nickname || profile.name 4 @recipient = profile.nickname || profile.name
app/mailers/scrap_notifier.rb
1 -class Scrap::Notifier < ActionMailer::Base 1 +class ScrapNotifier < ActionMailer::Base
2 def notification(scrap) 2 def notification(scrap)
3 sender, receiver = scrap.sender, scrap.receiver 3 sender, receiver = scrap.sender, scrap.receiver
4 @recipient = receiver.name 4 @recipient = receiver.name
app/mailers/user_mailer.rb
@@ -41,6 +41,23 @@ class UserMailer &lt; ActionMailer::Base @@ -41,6 +41,23 @@ class UserMailer &lt; ActionMailer::Base
41 ) 41 )
42 end 42 end
43 43
  44 + def profiles_suggestions_email(user)
  45 + @recipient = user.name
  46 + @environment = user.environment.name
  47 + @url = user.environment.top_url
  48 + @people_suggestions_url = user.people_suggestions_url
  49 + @people_suggestions = user.suggested_people.sample(3)
  50 + @communities_suggestions_url = user.communities_suggestions_url
  51 + @communities_suggestions = user.suggested_communities.sample(3)
  52 +
  53 + mail(
  54 + content_type: 'text/html',
  55 + to: user.email,
  56 + from: "#{user.environment.name} <#{user.environment.contact_email}>",
  57 + subject: _("[%s] What about grow up your network?") % user.environment.name
  58 + )
  59 + end
  60 +
44 class Job < Struct.new(:user, :method) 61 class Job < Struct.new(:user, :method)
45 def perform 62 def perform
46 UserMailer.send(method, user).deliver 63 UserMailer.send(method, user).deliver
app/models/add_friend.rb
@@ -14,6 +14,11 @@ class AddFriend &lt; Task @@ -14,6 +14,11 @@ class AddFriend &lt; Task
14 alias :friend :target 14 alias :friend :target
15 alias :friend= :target= 15 alias :friend= :target=
16 16
  17 + after_create do |task|
  18 + TaskMailer.invitation_notification(task).deliver unless task.friend
  19 + remove_from_suggestion_list(task)
  20 + end
  21 +
17 def perform 22 def perform
18 target.add_friend(requestor, group_for_friend) 23 target.add_friend(requestor, group_for_friend)
19 requestor.add_friend(target, group_for_person) 24 requestor.add_friend(target, group_for_person)
@@ -48,4 +53,8 @@ class AddFriend &lt; Task @@ -48,4 +53,8 @@ class AddFriend &lt; Task
48 {:type => :profile_image, :profile => requestor, :url => requestor.url} 53 {:type => :profile_image, :profile => requestor, :url => requestor.url}
49 end 54 end
50 55
  56 + def remove_from_suggestion_list(task)
  57 + suggestion = task.requestor.profile_suggestions.find_by_suggestion_id task.target.id
  58 + suggestion.disable if suggestion
  59 + end
51 end 60 end
app/models/add_member.rb
@@ -10,6 +10,10 @@ class AddMember &lt; Task @@ -10,6 +10,10 @@ class AddMember &lt; Task
10 10
11 settings_items :roles 11 settings_items :roles
12 12
  13 + after_create do |task|
  14 + remove_from_suggestion_list(task)
  15 + end
  16 +
13 def perform 17 def perform
14 if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?) 18 if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?)
15 self.roles = [Profile::Roles.member(organization.environment.id).id] 19 self.roles = [Profile::Roles.member(organization.environment.id).id]
@@ -46,4 +50,9 @@ class AddMember &lt; Task @@ -46,4 +50,9 @@ class AddMember &lt; Task
46 _('You will need login to %{system} in order to accept or reject %{requestor} as a member of %{organization}.') % { :system => target.environment.name, :requestor => requestor.name, :organization => organization.name } 50 _('You will need login to %{system} in order to accept or reject %{requestor} as a member of %{organization}.') % { :system => target.environment.name, :requestor => requestor.name, :organization => organization.name }
47 end 51 end
48 52
  53 + def remove_from_suggestion_list(task)
  54 + suggestion = task.requestor.profile_suggestions.find_by_suggestion_id task.target.id
  55 + suggestion.disable if suggestion
  56 + end
  57 +
49 end 58 end
app/models/approve_article.rb
@@ -22,6 +22,7 @@ class ApproveArticle &lt; Task @@ -22,6 +22,7 @@ class ApproveArticle &lt; Task
22 end 22 end
23 23
24 settings_items :closing_statment, :article_parent_id, :highlighted 24 settings_items :closing_statment, :article_parent_id, :highlighted
  25 + settings_items :create_link, :type => :boolean, :default => false
25 26
26 def article_parent 27 def article_parent
27 Article.find_by_id article_parent_id.to_i 28 Article.find_by_id article_parent_id.to_i
@@ -48,7 +49,11 @@ class ApproveArticle &lt; Task @@ -48,7 +49,11 @@ class ApproveArticle &lt; Task
48 end 49 end
49 50
50 def perform 51 def perform
51 - article.copy!(:name => name, :abstract => abstract, :body => body, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted, :source => article.source, :last_changed_by_id => article.last_changed_by_id, :created_by_id => article.created_by_id) 52 + if create_link
  53 + LinkArticle.create!(:reference_article => article, :profile => target, :parent => article_parent, :highlighted => highlighted)
  54 + else
  55 + article.copy!(:name => name, :abstract => abstract, :body => body, :profile => target, :reference_article => article, :parent => article_parent, :highlighted => highlighted, :source => article.source, :last_changed_by_id => article.last_changed_by_id, :created_by_id => article.created_by_id)
  56 + end
52 end 57 end
53 58
54 def title 59 def title
app/models/article.rb
1 -require 'hpricot'  
2 1
3 class Article < ActiveRecord::Base 2 class Article < ActiveRecord::Base
4 3
@@ -14,20 +13,17 @@ class Article &lt; ActiveRecord::Base @@ -14,20 +13,17 @@ class Article &lt; ActiveRecord::Base
14 acts_as_having_image 13 acts_as_having_image
15 14
16 SEARCHABLE_FIELDS = { 15 SEARCHABLE_FIELDS = {
17 - :name => 10,  
18 - :abstract => 3,  
19 - :body => 2,  
20 - :slug => 1,  
21 - :filename => 1, 16 + :name => {:label => _('Name'), :weight => 10},
  17 + :abstract => {:label => _('Abstract'), :weight => 3},
  18 + :body => {:label => _('Content'), :weight => 2},
  19 + :slug => {:label => _('Slug'), :weight => 1},
  20 + :filename => {:label => _('Filename'), :weight => 1},
22 } 21 }
23 22
24 - SEARCH_FILTERS = %w[  
25 - more_recent  
26 - more_popular  
27 - more_comments  
28 - ]  
29 -  
30 - SEARCH_DISPLAYS = %w[full] 23 + SEARCH_FILTERS = {
  24 + :order => %w[more_recent more_popular more_comments],
  25 + :display => %w[full]
  26 + }
31 27
32 def self.default_search_display 28 def self.default_search_display
33 'full' 29 'full'
@@ -110,6 +106,11 @@ class Article &lt; ActiveRecord::Base @@ -110,6 +106,11 @@ class Article &lt; ActiveRecord::Base
110 self.activity.destroy if self.activity 106 self.activity.destroy if self.activity
111 end 107 end
112 108
  109 + after_destroy :destroy_link_article
  110 + def destroy_link_article
  111 + Article.where(:reference_article_id => self.id, :type => LinkArticle).destroy_all
  112 + end
  113 +
113 xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list' 114 xss_terminate :only => [ :name ], :on => 'validation', :with => 'white_list'
114 115
115 scope :in_category, lambda { |category| 116 scope :in_category, lambda { |category|
@@ -388,6 +389,10 @@ class Article &lt; ActiveRecord::Base @@ -388,6 +389,10 @@ class Article &lt; ActiveRecord::Base
388 {} 389 {}
389 end 390 end
390 391
  392 + def alternate_languages
  393 + self.translations.map(&:language)
  394 + end
  395 +
391 scope :native_translations, :conditions => { :translation_of_id => nil } 396 scope :native_translations, :conditions => { :translation_of_id => nil }
392 397
393 def translatable? 398 def translatable?
@@ -472,7 +477,9 @@ class Article &lt; ActiveRecord::Base @@ -472,7 +477,9 @@ class Article &lt; ActiveRecord::Base
472 scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}} 477 scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}}
473 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ] 478 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ]
474 scope :images, :conditions => { :is_image => true } 479 scope :images, :conditions => { :is_image => true }
  480 + scope :no_images, :conditions => { :is_image => false }
475 scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ] 481 scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ]
  482 + scope :files, :conditions => { :type => 'UploadedFile' }
476 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } } 483 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } }
477 484
478 scope :more_popular, :order => 'hits DESC' 485 scope :more_popular, :order => 'hits DESC'
@@ -523,7 +530,10 @@ class Article &lt; ActiveRecord::Base @@ -523,7 +530,10 @@ class Article &lt; ActiveRecord::Base
523 end 530 end
524 531
525 alias :allow_delete? :allow_post_content? 532 alias :allow_delete? :allow_post_content?
526 - alias :allow_spread? :allow_post_content? 533 +
  534 + def allow_spread?(user = nil)
  535 + user && public?
  536 + end
527 537
528 def allow_create?(user) 538 def allow_create?(user)
529 allow_post_content?(user) || allow_publish_content?(user) 539 allow_post_content?(user) || allow_publish_content?(user)
@@ -696,7 +706,7 @@ class Article &lt; ActiveRecord::Base @@ -696,7 +706,7 @@ class Article &lt; ActiveRecord::Base
696 end 706 end
697 707
698 def first_paragraph 708 def first_paragraph
699 - paragraphs = Hpricot(to_html).search('p') 709 + paragraphs = Nokogiri::HTML.fragment(to_html).css('p')
700 paragraphs.empty? ? '' : paragraphs.first.to_html 710 paragraphs.empty? ? '' : paragraphs.first.to_html
701 end 711 end
702 712
@@ -718,8 +728,8 @@ class Article &lt; ActiveRecord::Base @@ -718,8 +728,8 @@ class Article &lt; ActiveRecord::Base
718 728
719 def body_images_paths 729 def body_images_paths
720 require 'uri' 730 require 'uri'
721 - Hpricot(self.body.to_s).search('img[@src]').collect do |i|  
722 - (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, URI.escape(i.attributes['src'])).to_s : i.attributes['src'] 731 + Nokogiri::HTML.fragment(self.body.to_s).css('img[src]').collect do |i|
  732 + (self.profile && self.profile.environment) ? URI.join(self.profile.environment.top_url, URI.escape(i['src'])).to_s : i['src']
723 end 733 end
724 end 734 end
725 735
@@ -756,11 +766,11 @@ class Article &lt; ActiveRecord::Base @@ -756,11 +766,11 @@ class Article &lt; ActiveRecord::Base
756 end 766 end
757 767
758 def first_image 768 def first_image
759 - img = Hpricot(self.lead.to_s).search('img[@src]').first || Hpricot(self.body.to_s).search('img').first  
760 - img.nil? ? '' : img.attributes['src'] 769 + img = Nokogiri::HTML.fragment(self.lead.to_s).css('img[src]').first || Nokogiri::HTML.fragment(self.body.to_s).search('img').first
  770 + img.nil? ? '' : img['src']
761 end 771 end
762 772
763 - delegate :region, :region_id, :environment, :environment_id, :to => :profile, :allow_nil => true 773 + delegate :lat, :lng, :region, :region_id, :environment, :environment_id, :to => :profile, :allow_nil => true
764 774
765 def has_macro? 775 def has_macro?
766 true 776 true
app/models/blog_archives_block.rb
@@ -36,8 +36,7 @@ class BlogArchivesBlock &lt; Block @@ -36,8 +36,7 @@ class BlogArchivesBlock &lt; Block
36 results << content_tag('li', content_tag('strong', "#{year} (#{count})")) 36 results << content_tag('li', content_tag('strong', "#{year} (#{count})"))
37 results << "<ul class='#{year}-archive'>" 37 results << "<ul class='#{year}-archive'>"
38 posts.except(:order).count(:all, :conditions => ['EXTRACT(YEAR FROM published_at)=?', year], :group => 'EXTRACT(MONTH FROM published_at)').sort_by {|month, count| -month.to_i}.each do |month, count| 38 posts.except(:order).count(:all, :conditions => ['EXTRACT(YEAR FROM published_at)=?', year], :group => 'EXTRACT(MONTH FROM published_at)').sort_by {|month, count| -month.to_i}.each do |month, count|
39 - month_name = gettext(MONTHS[month.to_i - 1])  
40 - results << content_tag('li', link_to("#{month_name} (#{count})", owner_blog.url.merge(:year => year, :month => month))) 39 + results << content_tag('li', link_to("#{month_name(month.to_i)} (#{count})", owner_blog.url.merge(:year => year, :month => month)))
41 end 40 end
42 results << "</ul>" 41 results << "</ul>"
43 end 42 end
app/models/box.rb
@@ -14,8 +14,8 @@ class Box &lt; ActiveRecord::Base @@ -14,8 +14,8 @@ class Box &lt; ActiveRecord::Base
14 end 14 end
15 15
16 def acceptable_blocks 16 def acceptable_blocks
17 - blocks_classes = central? ? Box.acceptable_center_blocks + plugins.dispatch(:extra_blocks, :type => owner.class, :position => 1) : Box.acceptable_side_blocks + plugins.dispatch(:extra_blocks, :type => owner.class, :position => [2, 3])  
18 - to_css_class_name(blocks_classes) 17 + blocks_classes = if central? then Box.acceptable_center_blocks + plugins.dispatch(:extra_blocks, :type => owner.class, :position => 1) else Box.acceptable_side_blocks + plugins.dispatch(:extra_blocks, :type => owner.class, :position => [2, 3]) end
  18 + to_css_selector blocks_classes
19 end 19 end
20 20
21 def central? 21 def central?
@@ -74,8 +74,8 @@ class Box &lt; ActiveRecord::Base @@ -74,8 +74,8 @@ class Box &lt; ActiveRecord::Base
74 74
75 private 75 private
76 76
77 - def to_css_class_name(blocks_classes)  
78 - blocks_classes.map{ |block_class| block_class.name.to_css_class } 77 + def to_css_selector(blocks_classes)
  78 + blocks_classes.map{ |block_class| ".#{block_class.name.to_css_class}" }.join(',')
79 end 79 end
80 80
81 end 81 end
app/models/category.rb
@@ -3,10 +3,10 @@ class Category &lt; ActiveRecord::Base @@ -3,10 +3,10 @@ class Category &lt; ActiveRecord::Base
3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent 3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :acronym => 5,  
8 - :abbreviation => 5,  
9 - :slug => 1, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :acronym => {:label => _('Acronym'), :weight => 5},
  8 + :abbreviation => {:label => _('Abbreviation'), :weight => 5},
  9 + :slug => {:label => _('Slug'), :weight => 1},
10 } 10 }
11 11
12 validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n 12 validates_exclusion_of :slug, :in => [ 'index' ], :message => N_('{fn} cannot be like that.').fix_i18n
app/models/certifier.rb
@@ -3,9 +3,9 @@ class Certifier &lt; ActiveRecord::Base @@ -3,9 +3,9 @@ class Certifier &lt; ActiveRecord::Base
3 attr_accessible :name, :environment 3 attr_accessible :name, :environment
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :description => 3,  
8 - :link => 1, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :description => {:label => _('Description'), :weight => 3},
  8 + :link => {:label => _('Link'), :weight => 1},
9 } 9 }
10 10
11 belongs_to :environment 11 belongs_to :environment
app/models/chat_message.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +class ChatMessage < ActiveRecord::Base
  2 + attr_accessible :body, :from, :to
  3 +
  4 + belongs_to :to, :class_name => 'Profile'
  5 + belongs_to :from, :class_name => 'Profile'
  6 +
  7 +end
app/models/comment.rb
1 class Comment < ActiveRecord::Base 1 class Comment < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :title => 10,  
5 - :name => 4,  
6 - :body => 2, 4 + :title => {:label => _('Title'), :weight => 10},
  5 + :name => {:label => _('Name'), :weight => 4},
  6 + :body => {:label => _('Content'), :weight => 2},
7 } 7 }
8 8
9 attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source 9 attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source
@@ -134,11 +134,11 @@ class Comment &lt; ActiveRecord::Base @@ -134,11 +134,11 @@ class Comment &lt; ActiveRecord::Base
134 def notify_by_mail 134 def notify_by_mail
135 if source.kind_of?(Article) && article.notify_comments? 135 if source.kind_of?(Article) && article.notify_comments?
136 if !notification_emails.empty? 136 if !notification_emails.empty?
137 - Comment::Notifier.notification(self).deliver 137 + CommentNotifier.notification(self).deliver
138 end 138 end
139 emails = article.followers - [author_email] 139 emails = article.followers - [author_email]
140 if !emails.empty? 140 if !emails.empty?
141 - Comment::Notifier.mail_to_followers(self, emails).deliver 141 + CommentNotifier.mail_to_followers(self, emails).deliver
142 end 142 end
143 end 143 end
144 end 144 end
app/models/communities_block.rb
@@ -14,19 +14,17 @@ class CommunitiesBlock &lt; ProfileListBlock @@ -14,19 +14,17 @@ class CommunitiesBlock &lt; ProfileListBlock
14 _('This block displays the communities in which the user is a member.') 14 _('This block displays the communities in which the user is a member.')
15 end 15 end
16 16
  17 + def suggestions
  18 + return nil unless owner.kind_of?(Profile)
  19 + owner.profile_suggestions.of_community.enabled.limit(3).includes(:suggestion)
  20 + end
  21 +
17 def footer 22 def footer
18 owner = self.owner 23 owner = self.owner
19 - case owner  
20 - when Profile  
21 - lambda do |context|  
22 - link_to s_('communities|View all'), :profile => owner.identifier, :controller => 'profile', :action => 'communities'  
23 - end  
24 - when Environment  
25 - lambda do |context|  
26 - link_to s_('communities|View all'), :controller => 'search', :action => 'communities'  
27 - end  
28 - else  
29 - '' 24 + suggestions = self.suggestions
  25 + return '' unless owner.kind_of?(Profile) || owner.kind_of?(Environment)
  26 + proc do
  27 + render :file => 'blocks/communities', :locals => { :owner => owner, :suggestions => suggestions }
30 end 28 end
31 end 29 end
32 30
app/models/create_enterprise.rb
@@ -73,7 +73,13 @@ class CreateEnterprise &lt; Task @@ -73,7 +73,13 @@ class CreateEnterprise &lt; Task
73 73
74 # sets the associated region for the enterprise creation 74 # sets the associated region for the enterprise creation
75 def region=(value) 75 def region=(value)
76 - raise ArgumentError.new("Region expected, but got #{value.class}") unless value.kind_of?(Region) 76 + unless value.kind_of?(Region)
  77 + begin
  78 + value = Region.find(value)
  79 + rescue
  80 + raise ArgumentError.new("Could not find any region with the id #{value}")
  81 + end
  82 + end
77 83
78 @region = value 84 @region = value
79 self.region_id = value.id 85 self.region_id = value.id
app/models/enterprise.rb
@@ -4,7 +4,10 @@ class Enterprise &lt; Organization @@ -4,7 +4,10 @@ class Enterprise &lt; Organization
4 4
5 attr_accessible :business_name, :address_reference, :district, :tag_list, :organization_website, :historic_and_current_context, :activities_short_description, :products_per_catalog_page 5 attr_accessible :business_name, :address_reference, :district, :tag_list, :organization_website, :historic_and_current_context, :activities_short_description, :products_per_catalog_page
6 6
7 - SEARCH_DISPLAYS += %w[map full] 7 + SEARCH_FILTERS = {
  8 + :order => %w[more_recent more_popular more_active],
  9 + :display => %w[compact full map]
  10 + }
8 11
9 def self.type_name 12 def self.type_name
10 _('Enterprise') 13 _('Enterprise')
@@ -16,7 +19,8 @@ class Enterprise &lt; Organization @@ -16,7 +19,8 @@ class Enterprise &lt; Organization
16 has_many :inputs, :through => :products 19 has_many :inputs, :through => :products
17 has_many :production_costs, :as => :owner 20 has_many :production_costs, :as => :owner
18 21
19 - has_and_belongs_to_many :fans, :class_name => 'Person', :join_table => 'favorite_enteprises_people' 22 + has_many :favorite_enterprise_people
  23 + has_many :fans, through: :favorite_enterprise_people, source: :person
20 24
21 def product_categories 25 def product_categories
22 ProductCategory.by_enterprise(self) 26 ProductCategory.by_enterprise(self)
app/models/environment.rb
@@ -3,13 +3,14 @@ @@ -3,13 +3,14 @@
3 # domains. 3 # domains.
4 class Environment < ActiveRecord::Base 4 class Environment < ActiveRecord::Base
5 5
6 - attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body, :members_whitelist_enabled, :members_whitelist 6 + attr_accessible :name, :is_default, :signup_welcome_text_subject, :signup_welcome_text_body, :terms_of_use, :message_for_disabled_enterprise, :news_amount_by_folder, :default_language, :languages, :description, :organization_approval_method, :enabled_plugins, :enabled_features, :redirection_after_login, :redirection_after_signup, :contact_email, :theme, :reports_lower_bound, :noreply_email, :signup_welcome_screen_body, :members_whitelist_enabled, :members_whitelist, :highlighted_news_amount, :portal_news_amount
7 7
8 has_many :users 8 has_many :users
9 9
10 self.partial_updates = false 10 self.partial_updates = false
11 11
12 has_many :tasks, :dependent => :destroy, :as => 'target' 12 has_many :tasks, :dependent => :destroy, :as => 'target'
  13 + has_many :search_terms, :as => :context
13 14
14 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/ 15 IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/
15 16
@@ -85,7 +86,9 @@ class Environment &lt; ActiveRecord::Base @@ -85,7 +86,9 @@ class Environment &lt; ActiveRecord::Base
85 end 86 end
86 87
87 def admins 88 def admins
88 - Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', Environment::Roles.admin(self).id]) 89 + admin_role = Environment::Roles.admin(self)
  90 + return [] if admin_role.blank?
  91 + Person.members_of(self).all(:conditions => ['role_assignments.role_id = ?', admin_role.id])
89 end 92 end
90 93
91 # returns the available features for a Environment, in the form of a 94 # returns the available features for a Environment, in the form of a
@@ -155,7 +158,8 @@ class Environment &lt; ActiveRecord::Base @@ -155,7 +158,8 @@ class Environment &lt; ActiveRecord::Base
155 'site_homepage' => _('Redirects the user to the environment homepage.'), 158 'site_homepage' => _('Redirects the user to the environment homepage.'),
156 'user_profile_page' => _('Redirects the user to his profile page.'), 159 'user_profile_page' => _('Redirects the user to his profile page.'),
157 'user_homepage' => _('Redirects the user to his homepage.'), 160 'user_homepage' => _('Redirects the user to his homepage.'),
158 - 'user_control_panel' => _('Redirects the user to his control panel.') 161 + 'user_control_panel' => _('Redirects the user to his control panel.'),
  162 + 'welcome_page' => _('Redirects the user to the environment welcome page.')
159 } 163 }
160 end 164 end
161 validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true 165 validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true
@@ -264,9 +268,12 @@ class Environment &lt; ActiveRecord::Base @@ -264,9 +268,12 @@ class Environment &lt; ActiveRecord::Base
264 settings_items :description, :type => String, :default => '<div style="text-align: center"><a href="http://noosfero.org/"><img src="/images/noosfero-network.png" alt="Noosfero"/></a></div>' 268 settings_items :description, :type => String, :default => '<div style="text-align: center"><a href="http://noosfero.org/"><img src="/images/noosfero-network.png" alt="Noosfero"/></a></div>'
265 settings_items :local_docs, :type => Array, :default => [] 269 settings_items :local_docs, :type => Array, :default => []
266 settings_items :news_amount_by_folder, :type => Integer, :default => 4 270 settings_items :news_amount_by_folder, :type => Integer, :default => 4
  271 + settings_items :highlighted_news_amount, :type => Integer, :default => 2
  272 + settings_items :portal_news_amount, :type => Integer, :default => 5
267 settings_items :help_message_to_add_enterprise, :type => String, :default => '' 273 settings_items :help_message_to_add_enterprise, :type => String, :default => ''
268 settings_items :tip_message_enterprise_activation_question, :type => String, :default => '' 274 settings_items :tip_message_enterprise_activation_question, :type => String, :default => ''
269 275
  276 + settings_items :currency_iso_unit, :type => String, :default => 'USD'
270 settings_items :currency_unit, :type => String, :default => '$' 277 settings_items :currency_unit, :type => String, :default => '$'
271 settings_items :currency_separator, :type => String, :default => '.' 278 settings_items :currency_separator, :type => String, :default => '.'
272 settings_items :currency_delimiter, :type => String, :default => ',' 279 settings_items :currency_delimiter, :type => String, :default => ','
@@ -657,8 +664,8 @@ class Environment &lt; ActiveRecord::Base @@ -657,8 +664,8 @@ class Environment &lt; ActiveRecord::Base
657 { :controller => 'admin_panel', :action => 'index' } 664 { :controller => 'admin_panel', :action => 'index' }
658 end 665 end
659 666
660 - def top_url  
661 - url = 'http://' 667 + def top_url(scheme = 'http')
  668 + url = scheme + '://'
662 url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname) 669 url << (Noosfero.url_options.key?(:host) ? Noosfero.url_options[:host] : default_hostname)
663 url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port) 670 url << ':' << Noosfero.url_options[:port].to_s if Noosfero.url_options.key?(:port)
664 url << Noosfero.root('') 671 url << Noosfero.root('')
@@ -824,6 +831,10 @@ class Environment &lt; ActiveRecord::Base @@ -824,6 +831,10 @@ class Environment &lt; ActiveRecord::Base
824 "home-page-news/#{cache_key}-#{language}" 831 "home-page-news/#{cache_key}-#{language}"
825 end 832 end
826 833
  834 + def portal_enabled
  835 + portal_community && enabled?('use_portal_community')
  836 + end
  837 +
827 def notification_emails 838 def notification_emails
828 [contact_email].select(&:present?) + admins.map(&:email) 839 [contact_email].select(&:present?) + admins.map(&:email)
829 end 840 end
@@ -937,6 +948,10 @@ class Environment &lt; ActiveRecord::Base @@ -937,6 +948,10 @@ class Environment &lt; ActiveRecord::Base
937 locales_list 948 locales_list
938 end 949 end
939 950
  951 + def has_license?
  952 + self.licenses.any?
  953 + end
  954 +
940 private 955 private
941 956
942 def default_language_available 957 def default_language_available
app/models/event.rb
@@ -19,7 +19,7 @@ class Event &lt; Article @@ -19,7 +19,7 @@ class Event &lt; Article
19 maybe_add_http(self.setting[:link]) 19 maybe_add_http(self.setting[:link])
20 end 20 end
21 21
22 - xss_terminate :only => [ :body, :link, :address ], :with => 'white_list', :on => 'validation' 22 + xss_terminate :only => [ :name, :body, :link, :address ], :with => 'white_list', :on => 'validation'
23 23
24 def initialize(*args) 24 def initialize(*args)
25 super(*args) 25 super(*args)
@@ -141,6 +141,10 @@ class Event &lt; Article @@ -141,6 +141,10 @@ class Event &lt; Article
141 result 141 result
142 end 142 end
143 143
  144 + def duration
  145 + ((self.end_date || self.start_date) - self.start_date).to_i
  146 + end
  147 +
144 def lead 148 def lead
145 content_tag('div', 149 content_tag('div',
146 show_period(start_date, end_date), 150 show_period(start_date, end_date),
app/models/external_feed.rb
@@ -14,9 +14,9 @@ class ExternalFeed &lt; ActiveRecord::Base @@ -14,9 +14,9 @@ class ExternalFeed &lt; ActiveRecord::Base
14 14
15 def add_item(title, link, date, content) 15 def add_item(title, link, date, content)
16 return if content.blank? 16 return if content.blank?
17 - doc = Hpricot(content)  
18 - doc.search('*').each do |p|  
19 - if p.instance_of? Hpricot::Elem 17 + doc = Nokogiri::HTML.fragment content
  18 + doc.css('*').each do |p|
  19 + if p.instance_of? Nokogiri::XML::Element
20 p.remove_attribute 'style' 20 p.remove_attribute 'style'
21 p.remove_attribute 'class' 21 p.remove_attribute 'class'
22 end 22 end
@@ -26,10 +26,10 @@ class ExternalFeed &lt; ActiveRecord::Base @@ -26,10 +26,10 @@ class ExternalFeed &lt; ActiveRecord::Base
26 article = TinyMceArticle.new 26 article = TinyMceArticle.new
27 article.name = title 27 article.name = title
28 article.profile = blog.profile 28 article.profile = blog.profile
29 - article.body = content  
30 - article.published_at = date  
31 - article.source = link  
32 - article.profile = blog.profile 29 + article.body = content
  30 + article.published_at = date
  31 + article.source = link
  32 + article.profile = blog.profile
33 article.parent = blog 33 article.parent = blog
34 article.author_name = self.feed_title 34 article.author_name = self.feed_title
35 unless blog.children.exists?(:slug => article.slug) 35 unless blog.children.exists?(:slug => article.slug)
app/models/favorite_enterprise_person.rb 0 → 100644
@@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
  1 +class FavoriteEnterprisePerson < ActiveRecord::Base
  2 +
  3 + self.table_name = :favorite_enteprises_people
  4 +
  5 + belongs_to :enterprise
  6 + belongs_to :person
  7 +
  8 +end
app/models/folder.rb
@@ -12,7 +12,7 @@ class Folder &lt; Article @@ -12,7 +12,7 @@ class Folder &lt; Article
12 12
13 acts_as_having_settings :field => :setting 13 acts_as_having_settings :field => :setting
14 14
15 - xss_terminate :only => [ :body ], :with => 'white_list', :on => 'validation' 15 + xss_terminate :only => [ :name, :body ], :with => 'white_list', :on => 'validation'
16 16
17 include WhiteListFilter 17 include WhiteListFilter
18 filter_iframes :body 18 filter_iframes :body
app/models/forum.rb
@@ -54,7 +54,7 @@ class Forum &lt; Folder @@ -54,7 +54,7 @@ class Forum &lt; Folder
54 54
55 def first_paragraph 55 def first_paragraph
56 return '' if body.blank? 56 return '' if body.blank?
57 - paragraphs = Hpricot(body).search('p') 57 + paragraphs = Nokogiri::HTML.fragment(body).css('p')
58 paragraphs.empty? ? '' : paragraphs.first.to_html 58 paragraphs.empty? ? '' : paragraphs.first.to_html
59 end 59 end
60 60
app/models/input.rb
1 class Input < ActiveRecord::Base 1 class Input < ActiveRecord::Base
2 2
3 - attr_accessible :product, :product_category, :product_category_id, :amount_used, :unit_id, :price_per_unit, :relevant_to_price 3 + attr_accessible :product, :product_id, :product_category, :product_category_id,
  4 + :amount_used, :unit_id, :price_per_unit, :relevant_to_price, :is_from_solidarity_economy
4 5
5 belongs_to :product 6 belongs_to :product
6 belongs_to :product_category 7 belongs_to :product_category
app/models/invitation.rb
@@ -51,7 +51,10 @@ class Invitation &lt; Task @@ -51,7 +51,10 @@ class Invitation &lt; Task
51 next if contact_to_invite == _("Firstname Lastname <friend@email.com>") 51 next if contact_to_invite == _("Firstname Lastname <friend@email.com>")
52 52
53 contact_to_invite.strip! 53 contact_to_invite.strip!
54 - if match = contact_to_invite.match(/(.*)<(.*)>/) and match[2].match(Noosfero::Constants::EMAIL_FORMAT) 54 + find_by_profile_id = false
  55 + if contact_to_invite.match(/^\d*$/)
  56 + find_by_profile_id = true
  57 + elsif match = contact_to_invite.match(/(.*)<(.*)>/) and match[2].match(Noosfero::Constants::EMAIL_FORMAT)
55 friend_name = match[1].strip 58 friend_name = match[1].strip
56 friend_email = match[2] 59 friend_email = match[2]
57 elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT) 60 elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT)
@@ -61,11 +64,15 @@ class Invitation &lt; Task @@ -61,11 +64,15 @@ class Invitation &lt; Task
61 next 64 next
62 end 65 end
63 66
64 - user = User.find_by_email(friend_email) 67 + begin
  68 + user = find_by_profile_id ? Person.find_by_id(contact_to_invite).user : User.find_by_email(friend_email)
  69 + rescue
  70 + user = nil
  71 + end
65 72
66 - task_args = if user.nil? 73 + task_args = if user.nil? && !find_by_profile_id
67 {:person => person, :friend_name => friend_name, :friend_email => friend_email, :message => message} 74 {:person => person, :friend_name => friend_name, :friend_email => friend_email, :message => message}
68 - else 75 + elsif user.present? && !(user.person.is_a_friend?(person) && profile.person?)
69 {:person => person, :target => user.person} 76 {:person => person, :target => user.person}
70 end 77 end
71 78
app/models/invite_friend.rb
1 class InviteFriend < Invitation 1 class InviteFriend < Invitation
2 2
3 settings_items :group_for_person, :group_for_friend 3 settings_items :group_for_person, :group_for_friend
  4 + before_create :check_for_invitation_existence
4 5
5 def perform 6 def perform
6 person.add_friend(friend, group_for_person) 7 person.add_friend(friend, group_for_person)
@@ -41,4 +42,11 @@ class InviteFriend &lt; Invitation @@ -41,4 +42,11 @@ class InviteFriend &lt; Invitation
41 ].join("\n\n") 42 ].join("\n\n")
42 end 43 end
43 44
  45 + private
  46 + def check_for_invitation_existence
  47 + if friend
  48 + friend.tasks.pending.of("InviteFriend").find(:all, :conditions => {:requestor_id => person.id, :target_id => friend.id}).blank?
  49 + end
  50 + end
  51 +
44 end 52 end
app/models/invite_member.rb
@@ -2,6 +2,7 @@ class InviteMember &lt; Invitation @@ -2,6 +2,7 @@ class InviteMember &lt; Invitation
2 2
3 settings_items :community_id, :type => :integer 3 settings_items :community_id, :type => :integer
4 validates_presence_of :community_id 4 validates_presence_of :community_id
  5 + before_create :check_for_invitation_existence
5 6
6 def community 7 def community
7 Community.find(community_id) 8 Community.find(community_id)
@@ -39,6 +40,14 @@ class InviteMember &lt; Invitation @@ -39,6 +40,14 @@ class InviteMember &lt; Invitation
39 _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name} 40 _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name}
40 end 41 end
41 42
  43 + def target_notification_message
  44 + if friend
  45 + _('%{requestor} is inviting you to join "%{community}" on %{system}.') % { :system => target.environment.name, :requestor => requestor.name, :community => community.name }
  46 + else
  47 + super
  48 + end
  49 + end
  50 +
42 def expanded_message 51 def expanded_message
43 super.gsub /<community>/, community.name 52 super.gsub /<community>/, community.name
44 end 53 end
@@ -53,4 +62,11 @@ class InviteMember &lt; Invitation @@ -53,4 +62,11 @@ class InviteMember &lt; Invitation
53 ].join("\n\n") 62 ].join("\n\n")
54 end 63 end
55 64
  65 + private
  66 + def check_for_invitation_existence
  67 + if friend
  68 + friend.tasks.pending.of("InviteMember").find(:all, :conditions => {:requestor_id => person.id}).select { |t| t.data[:community_id] == community_id }.blank?
  69 + end
  70 + end
  71 +
56 end 72 end
app/models/license.rb
@@ -3,8 +3,8 @@ class License &lt; ActiveRecord::Base @@ -3,8 +3,8 @@ class License &lt; ActiveRecord::Base
3 attr_accessible :name, :url 3 attr_accessible :name, :url
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 10,  
7 - :url => 5, 6 + :name => {:label => _('Name'), :weight => 10},
  7 + :url => {:label => _('URL'), :weight => 5},
8 } 8 }
9 9
10 belongs_to :environment 10 belongs_to :environment
app/models/link_article.rb 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +class LinkArticle < Article
  2 +
  3 + attr_accessible :reference_article
  4 +
  5 + def self.short_description
  6 + "Article link"
  7 + end
  8 +
  9 + delegate :name, :to => :reference_article
  10 + delegate :body, :to => :reference_article
  11 + delegate :abstract, :to => :reference_article
  12 + delegate :url, :to => :reference_article
  13 +
  14 +end
app/models/national_region.rb
1 class NationalRegion < ActiveRecord::Base 1 class NationalRegion < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :name => 1,  
5 - :national_region_code => 1, 4 + :name => {:label => _('Name'), :weight => 1},
  5 + :national_region_code => {:label => _('Region Code'), :weight => 1},
6 } 6 }
7 7
8 def self.search_city(city_name, like = false, state = nil) 8 def self.search_city(city_name, like = false, state = nil)
app/models/organization.rb
@@ -3,10 +3,11 @@ class Organization &lt; Profile @@ -3,10 +3,11 @@ class Organization &lt; Profile
3 3
4 attr_accessible :moderated_articles, :foundation_year, :contact_person, :acronym, :legal_form, :economic_activity, :management_information, :cnpj, :display_name, :enable_contact_us 4 attr_accessible :moderated_articles, :foundation_year, :contact_person, :acronym, :legal_form, :economic_activity, :management_information, :cnpj, :display_name, :enable_contact_us
5 5
6 - SEARCH_FILTERS += %w[  
7 - more_popular  
8 - more_active  
9 - ] 6 + SEARCH_FILTERS = {
  7 + :order => %w[more_recent more_popular more_active],
  8 + :display => %w[compact]
  9 + }
  10 +
10 11
11 settings_items :closed, :type => :boolean, :default => false 12 settings_items :closed, :type => :boolean, :default => false
12 def closed? 13 def closed?
@@ -176,4 +177,8 @@ class Organization &lt; Profile @@ -176,4 +177,8 @@ class Organization &lt; Profile
176 self.visible = false 177 self.visible = false
177 save! 178 save!
178 end 179 end
  180 +
  181 + def allow_invitation_from?(person)
  182 + (followed_by?(person) && self.allow_members_to_invite) || person.has_permission?('invite-members', self)
  183 + end
179 end 184 end
app/models/person.rb
@@ -3,10 +3,11 @@ class Person &lt; Profile @@ -3,10 +3,11 @@ class Person &lt; Profile
3 3
4 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website 4 attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website
5 5
6 - SEARCH_FILTERS += %w[  
7 - more_popular  
8 - more_active  
9 - ] 6 + SEARCH_FILTERS = {
  7 + :order => %w[more_recent more_popular more_active],
  8 + :display => %w[compact]
  9 + }
  10 +
10 11
11 def self.type_name 12 def self.type_name
12 _('Person') 13 _('Person')
@@ -21,16 +22,34 @@ class Person &lt; Profile @@ -21,16 +22,34 @@ class Person &lt; Profile
21 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } 22 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] }
22 } 23 }
23 24
  25 + scope :not_members_of, lambda { |resources|
  26 + resources = [resources] if !resources.kind_of?(Array)
  27 + conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
  28 + { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] }
  29 + }
  30 +
24 scope :by_role, lambda { |roles| 31 scope :by_role, lambda { |roles|
25 roles = [roles] unless roles.kind_of?(Array) 32 roles = [roles] unless roles.kind_of?(Array)
26 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)', 33 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
27 roles] } 34 roles] }
28 } 35 }
29 36
30 - def has_permission_with_plugins?(permission, profile)  
31 - permissions = [has_permission_without_plugins?(permission, profile)] 37 + scope :not_friends_of, lambda { |resources|
  38 + resources = Array(resources)
  39 + { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] }
  40 + }
  41 +
  42 + def has_permission_with_admin?(permission, resource)
  43 + return true if resource.blank? || resource.admins.include?(self)
  44 + return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self)
  45 + has_permission_without_admin?(permission, resource)
  46 + end
  47 + alias_method_chain :has_permission?, :admin
  48 +
  49 + def has_permission_with_plugins?(permission, resource)
  50 + permissions = [has_permission_without_plugins?(permission, resource)]
32 permissions += plugins.map do |plugin| 51 permissions += plugins.map do |plugin|
33 - plugin.has_permission?(self, permission, profile) 52 + plugin.has_permission?(self, permission, resource)
34 end 53 end
35 permissions.include?(true) 54 permissions.include?(true)
36 end 55 end
@@ -45,9 +64,9 @@ roles] } @@ -45,9 +64,9 @@ roles] }
45 ScopeTool.union *scopes 64 ScopeTool.union *scopes
46 end 65 end
47 66
48 - def memberships_by_role(role)  
49 - memberships.where('role_assignments.role_id = ?', role.id)  
50 - end 67 + def memberships_by_role(role)
  68 + memberships.where('role_assignments.role_id = ?', role.id)
  69 + end
51 70
52 has_many :friendships, :dependent => :destroy 71 has_many :friendships, :dependent => :destroy
53 has_many :friends, :class_name => 'Person', :through => :friendships 72 has_many :friends, :class_name => 'Person', :through => :friendships
@@ -65,6 +84,10 @@ roles] } @@ -65,6 +84,10 @@ roles] }
65 has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people' 84 has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people'
66 has_and_belongs_to_many :articles_with_access, :class_name => 'Article', :join_table => 'article_privacy_exceptions' 85 has_and_belongs_to_many :articles_with_access, :class_name => 'Article', :join_table => 'article_privacy_exceptions'
67 86
  87 + has_many :profile_suggestions, :foreign_key => :person_id, :order => 'score DESC', :dependent => :destroy
  88 + has_many :suggested_people, :through => :profile_suggestions, :source => :suggestion, :conditions => ['profile_suggestions.suggestion_type = ? AND profile_suggestions.enabled = ?', 'Person', true]
  89 + has_many :suggested_communities, :through => :profile_suggestions, :source => :suggestion, :conditions => ['profile_suggestions.suggestion_type = ? AND profile_suggestions.enabled = ?', 'Community', true]
  90 +
68 scope :more_popular, :order => 'friends_count DESC' 91 scope :more_popular, :order => 'friends_count DESC'
69 92
70 scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*' 93 scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*'
@@ -118,12 +141,12 @@ roles] } @@ -118,12 +141,12 @@ roles] }
118 end 141 end
119 142
120 def add_friend(friend, group = nil) 143 def add_friend(friend, group = nil)
121 - unless self.is_a_friend?(friend) 144 + unless self.is_a_friend?(friend)
122 friendship = self.friendships.build 145 friendship = self.friendships.build
123 friendship.friend = friend 146 friendship.friend = friend
124 friendship.group = group 147 friendship.group = group
125 friendship.save 148 friendship.save
126 - end 149 + end
127 end 150 end
128 151
129 def already_request_friendship?(person) 152 def already_request_friendship?(person)
@@ -497,6 +520,15 @@ roles] } @@ -497,6 +520,15 @@ roles] }
497 person.notifier.reschedule_next_notification_mail 520 person.notifier.reschedule_next_notification_mail
498 end 521 end
499 522
  523 + def remove_suggestion(profile)
  524 + suggestion = profile_suggestions.find_by_suggestion_id profile.id
  525 + suggestion.disable if suggestion
  526 + end
  527 +
  528 + def allow_invitation_from?(person)
  529 + person.has_permission?(:manage_friends, self)
  530 + end
  531 +
500 protected 532 protected
501 533
502 def followed_by?(profile) 534 def followed_by?(profile)
app/models/person_notifier.rb
@@ -22,12 +22,17 @@ class PersonNotifier @@ -22,12 +22,17 @@ class PersonNotifier
22 schedule_next_notification_mail 22 schedule_next_notification_mail
23 end 23 end
24 24
  25 + def notify_from
  26 + @person.last_notification || DateTime.now - @person.notification_time.hours
  27 + end
  28 +
25 def notify 29 def notify
26 if @person.notification_time && @person.notification_time > 0 30 if @person.notification_time && @person.notification_time > 0
27 - from = @person.last_notification || DateTime.now - @person.notification_time.hours  
28 - notifications = @person.tracked_notifications.find(:all, :conditions => ["created_at > ?", from]) 31 + notifications = @person.tracked_notifications.find(:all, :conditions => ["created_at > ?", notify_from])
  32 + tasks = Task.to(@person).without_spam.pending.where("created_at > ?", notify_from).order_by('created_at', 'asc')
  33 +
29 Noosfero.with_locale @person.environment.default_language do 34 Noosfero.with_locale @person.environment.default_language do
30 - Mailer::content_summary(@person, notifications).deliver unless notifications.empty? 35 + Mailer::content_summary(@person, notifications, tasks).deliver unless notifications.empty? && tasks.empty?
31 end 36 end
32 @person.settings[:last_notification] = DateTime.now 37 @person.settings[:last_notification] = DateTime.now
33 @person.save! 38 @person.save!
@@ -59,32 +64,42 @@ class PersonNotifier @@ -59,32 +64,42 @@ class PersonNotifier
59 end 64 end
60 65
61 def failure(job) 66 def failure(job)
62 - person = Person.find(person_id)  
63 - person.notifier.dispatch_notification_mail 67 + begin
  68 + person = Person.find(person_id)
  69 + person.notifier.dispatch_notification_mail
  70 + rescue
  71 + Rails.logger.error "PersonNotifier::NotifyJob: Cannot recover from failure"
  72 + end
64 end 73 end
65 74
66 end 75 end
67 76
68 class Mailer < ActionMailer::Base 77 class Mailer < ActionMailer::Base
69 78
70 - add_template_helper(PersonNotifierHelper) 79 + add_template_helper(ApplicationHelper)
71 80
72 def session 81 def session
73 {:theme => nil} 82 {:theme => nil}
74 end 83 end
75 84
76 - def content_summary(person, notifications) 85 + def content_summary(person, notifications, tasks)
  86 + if person.environment
  87 + ActionMailer::Base.asset_host = person.environment.top_url
  88 + ActionMailer::Base.default_url_options[:host] = person.environment.default_hostname
  89 + end
  90 +
77 @current_theme = 'default' 91 @current_theme = 'default'
78 @profile = person 92 @profile = person
79 @recipient = @profile.nickname || @profile.name 93 @recipient = @profile.nickname || @profile.name
80 @notifications = notifications 94 @notifications = notifications
  95 + @tasks = tasks
81 @environment = @profile.environment.name 96 @environment = @profile.environment.name
82 @url = @profile.environment.top_url 97 @url = @profile.environment.top_url
83 mail( 98 mail(
84 content_type: "text/html", 99 content_type: "text/html",
85 from: "#{@profile.environment.name} <#{@profile.environment.noreply_email}>", 100 from: "#{@profile.environment.name} <#{@profile.environment.noreply_email}>",
86 to: @profile.email, 101 to: @profile.email,
87 - subject: _("[%s] Network Activity") % [@profile.environment.name] 102 + subject: _("[%s] Notifications") % [@profile.environment.name]
88 ) 103 )
89 end 104 end
90 end 105 end
app/models/product.rb
1 class Product < ActiveRecord::Base 1 class Product < ActiveRecord::Base
2 2
3 SEARCHABLE_FIELDS = { 3 SEARCHABLE_FIELDS = {
4 - :name => 10,  
5 - :description => 1, 4 + :name => {:label => _('Name'), :weight => 10},
  5 + :description => {:label => _('Description'), :weight => 1},
6 } 6 }
7 7
8 - SEARCH_FILTERS = %w[  
9 - more_recent  
10 - ]  
11 -  
12 - SEARCH_DISPLAYS = %w[map full] 8 + SEARCH_FILTERS = {
  9 + :order => %w[more_recent],
  10 + :display => %w[full map]
  11 + }
13 12
14 - attr_accessible :name, :product_category, :highlighted, :price, :enterprise, :image_builder, :description, :available, :qualifiers, :unit_id, :discount, :inputs, :qualifiers_list 13 + attr_accessible :name, :product_category, :profile, :profile_id, :enterprise,
  14 + :highlighted, :price, :image_builder, :description, :available, :qualifiers, :unit_id, :discount, :inputs, :qualifiers_list
15 15
16 def self.default_search_display 16 def self.default_search_display
17 'full' 17 'full'
@@ -237,7 +237,7 @@ class Product &lt; ActiveRecord::Base @@ -237,7 +237,7 @@ class Product &lt; ActiveRecord::Base
237 237
238 def percentage_from_solidarity_economy 238 def percentage_from_solidarity_economy
239 se_i = t_i = 0 239 se_i = t_i = 0
240 - self.inputs(true).each{ |i| t_i += 1; se_i += 1 if i.is_from_solidarity_economy } 240 + self.inputs.each{ |i| t_i += 1; se_i += 1 if i.is_from_solidarity_economy }
241 t_i = 1 if t_i == 0 # avoid division by 0 241 t_i = 1 if t_i == 0 # avoid division by 0
242 p = case (se_i.to_f/t_i)*100 242 p = case (se_i.to_f/t_i)*100
243 when 0 then [0, ''] 243 when 0 then [0, '']
app/models/profile.rb
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # which by default is the one returned by Environment:default. 3 # which by default is the one returned by Environment:default.
4 class Profile < ActiveRecord::Base 4 class Profile < ActiveRecord::Base
5 5
6 - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login 6 + attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only
7 7
8 # use for internationalizable human type names in search facets 8 # use for internationalizable human type names in search facets
9 # reimplement on subclasses 9 # reimplement on subclasses
@@ -12,16 +12,15 @@ class Profile &lt; ActiveRecord::Base @@ -12,16 +12,15 @@ class Profile &lt; ActiveRecord::Base
12 end 12 end
13 13
14 SEARCHABLE_FIELDS = { 14 SEARCHABLE_FIELDS = {
15 - :name => 10,  
16 - :identifier => 5,  
17 - :nickname => 2, 15 + :name => {:label => _('Name'), :weight => 10},
  16 + :identifier => {:label => _('Username'), :weight => 5},
  17 + :nickname => {:label => _('Nickname'), :weight => 2},
18 } 18 }
19 19
20 - SEARCH_FILTERS = %w[  
21 - more_recent  
22 - ]  
23 -  
24 - SEARCH_DISPLAYS = %w[compact] 20 + SEARCH_FILTERS = {
  21 + :order => %w[more_recent],
  22 + :display => %w[compact]
  23 + }
25 24
26 def self.default_search_display 25 def self.default_search_display
27 'compact' 26 'compact'
@@ -123,6 +122,7 @@ class Profile &lt; ActiveRecord::Base @@ -123,6 +122,7 @@ class Profile &lt; ActiveRecord::Base
123 scope :visible, :conditions => { :visible => true } 122 scope :visible, :conditions => { :visible => true }
124 scope :disabled, :conditions => { :visible => false } 123 scope :disabled, :conditions => { :visible => false }
125 scope :public, :conditions => { :visible => true, :public_profile => true } 124 scope :public, :conditions => { :visible => true, :public_profile => true }
  125 + scope :enabled, :conditions => { :enabled => true }
126 126
127 # Subclasses must override this method 127 # Subclasses must override this method
128 scope :more_popular 128 scope :more_popular
@@ -139,6 +139,17 @@ class Profile &lt; ActiveRecord::Base @@ -139,6 +139,17 @@ class Profile &lt; ActiveRecord::Base
139 139
140 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments 140 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
141 141
  142 + # Although this should be a has_one relation, there are no non-silly names for
  143 + # a foreign key on article to reference the template to which it is
  144 + # welcome_page... =P
  145 + belongs_to :welcome_page, :class_name => 'Article', :dependent => :destroy
  146 +
  147 + def welcome_page_content
  148 + welcome_page && welcome_page.published ? welcome_page.body : nil
  149 + end
  150 +
  151 + has_many :search_terms, :as => :context
  152 +
142 def scraps(scrap=nil) 153 def scraps(scrap=nil)
143 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap 154 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap
144 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap) 155 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap)
@@ -154,6 +165,7 @@ class Profile &lt; ActiveRecord::Base @@ -154,6 +165,7 @@ class Profile &lt; ActiveRecord::Base
154 settings_items :public_content, :type => :boolean, :default => true 165 settings_items :public_content, :type => :boolean, :default => true
155 settings_items :description 166 settings_items :description
156 settings_items :fields_privacy, :type => :hash, :default => {} 167 settings_items :fields_privacy, :type => :hash, :default => {}
  168 + settings_items :email_suggestions, :type => :boolean, :default => false
157 169
158 validates_length_of :description, :maximum => 550, :allow_nil => true 170 validates_length_of :description, :maximum => 550, :allow_nil => true
159 171
@@ -218,6 +230,8 @@ class Profile &lt; ActiveRecord::Base @@ -218,6 +230,8 @@ class Profile &lt; ActiveRecord::Base
218 230
219 has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy 231 has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy
220 232
  233 + has_many :profile_suggestions, :foreign_key => :suggestion_id, :dependent => :destroy
  234 +
221 def top_level_categorization 235 def top_level_categorization
222 ret = {} 236 ret = {}
223 self.profile_categorizations.each do |c| 237 self.profile_categorizations.each do |c|
@@ -392,7 +406,7 @@ class Profile &lt; ActiveRecord::Base @@ -392,7 +406,7 @@ class Profile &lt; ActiveRecord::Base
392 end 406 end
393 407
394 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation' 408 xss_terminate :only => [ :name, :nickname, :address, :contact_phone, :description ], :on => 'validation'
395 - xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list', :on => 'validation' 409 + xss_terminate :only => [ :custom_footer, :custom_header ], :with => 'white_list'
396 410
397 include WhiteListFilter 411 include WhiteListFilter
398 filter_iframes :custom_header, :custom_footer 412 filter_iframes :custom_header, :custom_footer
@@ -513,6 +527,14 @@ class Profile &lt; ActiveRecord::Base @@ -513,6 +527,14 @@ class Profile &lt; ActiveRecord::Base
513 generate_url(:profile => identifier, :controller => 'profile', :action => 'index') 527 generate_url(:profile => identifier, :controller => 'profile', :action => 'index')
514 end 528 end
515 529
  530 + def people_suggestions_url
  531 + generate_url(:profile => identifier, :controller => 'friends', :action => 'suggest')
  532 + end
  533 +
  534 + def communities_suggestions_url
  535 + generate_url(:profile => identifier, :controller => 'memberships', :action => 'suggest')
  536 + end
  537 +
516 def generate_url(options) 538 def generate_url(options)
517 url_options.merge(options) 539 url_options.merge(options)
518 end 540 end
@@ -602,7 +624,7 @@ private :generate_url, :url_options @@ -602,7 +624,7 @@ private :generate_url, :url_options
602 end 624 end
603 625
604 def copy_article_tree(article, parent=nil) 626 def copy_article_tree(article, parent=nil)
605 - return if article.is_a?(RssFeed) 627 + return if !copy_article?(article)
606 original_article = self.articles.find_by_name(article.name) 628 original_article = self.articles.find_by_name(article.name)
607 if original_article 629 if original_article
608 num = 2 630 num = 2
@@ -622,6 +644,11 @@ private :generate_url, :url_options @@ -622,6 +644,11 @@ private :generate_url, :url_options
622 end 644 end
623 end 645 end
624 646
  647 + def copy_article?(article)
  648 + !article.is_a?(RssFeed) &&
  649 + !(is_template && article.slug=='welcome-page')
  650 + end
  651 +
625 # Adds a person as member of this Profile. 652 # Adds a person as member of this Profile.
626 def add_member(person) 653 def add_member(person)
627 if self.has_members? 654 if self.has_members?
@@ -631,6 +658,8 @@ private :generate_url, :url_options @@ -631,6 +658,8 @@ private :generate_url, :url_options
631 self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0 658 self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0
632 self.affiliate(person, Profile::Roles.member(environment.id)) 659 self.affiliate(person, Profile::Roles.member(environment.id))
633 end 660 end
  661 + person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
  662 + remove_from_suggestion_list person
634 else 663 else
635 raise _("%s can't have members") % self.class.name 664 raise _("%s can't have members") % self.class.name
636 end 665 end
@@ -768,7 +797,10 @@ private :generate_url, :url_options @@ -768,7 +797,10 @@ private :generate_url, :url_options
768 end 797 end
769 798
770 def admins 799 def admins
771 - self.members_by_role(Profile::Roles.admin(environment.id)) 800 + return [] if environment.blank?
  801 + admin_role = Profile::Roles.admin(environment.id)
  802 + return [] if admin_role.blank?
  803 + self.members_by_role(admin_role)
772 end 804 end
773 805
774 def enable_contact? 806 def enable_contact?
@@ -966,4 +998,14 @@ private :generate_url, :url_options @@ -966,4 +998,14 @@ private :generate_url, :url_options
966 def preferred_login_redirection 998 def preferred_login_redirection
967 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login 999 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
968 end 1000 end
  1001 +
  1002 + def remove_from_suggestion_list(person)
  1003 + suggestion = person.profile_suggestions.find_by_suggestion_id self.id
  1004 + suggestion.disable if suggestion
  1005 + end
  1006 +
  1007 + def allow_invitation_from(person)
  1008 + false
  1009 + end
  1010 +
969 end 1011 end
app/models/profile_suggestion.rb 0 → 100644
@@ -0,0 +1,290 @@ @@ -0,0 +1,290 @@
  1 +class ProfileSuggestion < ActiveRecord::Base
  2 + belongs_to :person
  3 + belongs_to :suggestion, :class_name => 'Profile', :foreign_key => :suggestion_id
  4 +
  5 + attr_accessible :person, :suggestion, :suggestion_type, :categories, :enabled
  6 +
  7 + has_many :suggestion_connections, :foreign_key => 'suggestion_id'
  8 + has_many :profile_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'Profile'
  9 + has_many :tag_connections, :through => :suggestion_connections, :source => :connection, :source_type => 'ActsAsTaggableOn::Tag'
  10 +
  11 + before_create do |profile_suggestion|
  12 + profile_suggestion.suggestion_type = self.suggestion.class.to_s
  13 + end
  14 +
  15 + after_destroy do |profile_suggestion|
  16 + self.class.generate_profile_suggestions(profile_suggestion.person)
  17 + end
  18 +
  19 + acts_as_having_settings :field => :categories
  20 +
  21 + validate :must_be_a_valid_category, :on => :create
  22 + def must_be_a_valid_category
  23 + if categories.keys.map { |cat| self.respond_to?(cat)}.include?(false)
  24 + errors.add(:categories, 'Category must be valid')
  25 + end
  26 + end
  27 +
  28 + validates_uniqueness_of :suggestion_id, :scope => [ :person_id ]
  29 + scope :of_person, :conditions => { :suggestion_type => 'Person' }
  30 + scope :of_community, :conditions => { :suggestion_type => 'Community' }
  31 + scope :enabled, :conditions => { :enabled => true }
  32 +
  33 + # {:category_type => ['category-icon', 'category-label']}
  34 + CATEGORIES = {
  35 + :people_with_common_friends => ['menu-people', _('Friends in common')],
  36 + :people_with_common_communities => ['menu-community',_('Communities in common')],
  37 + :people_with_common_tags => ['edit', _('Tags in common')],
  38 + :communities_with_common_friends => ['menu-people', _('Friends in common')],
  39 + :communities_with_common_tags => ['edit', _('Tags in common')]
  40 + }
  41 +
  42 + def category_icon(category)
  43 + 'icon-' + ProfileSuggestion::CATEGORIES[category][0]
  44 + end
  45 +
  46 + def category_label(category)
  47 + ProfileSuggestion::CATEGORIES[category][1]
  48 + end
  49 +
  50 + RULES = {
  51 + :people_with_common_communities => {
  52 + :threshold => 2, :weight => 1, :connection => 'Profile'
  53 + },
  54 + :people_with_common_friends => {
  55 + :threshold => 2, :weight => 1, :connection => 'Profile'
  56 + },
  57 + :people_with_common_tags => {
  58 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag'
  59 + },
  60 + :communities_with_common_friends => {
  61 + :threshold => 2, :weight => 1, :connection => 'Profile'
  62 + },
  63 + :communities_with_common_tags => {
  64 + :threshold => 2, :weight => 1, :connection => 'ActsAsTaggableOn::Tag'
  65 + }
  66 + }
  67 +
  68 + RULES.keys.each do |rule|
  69 + settings_items rule
  70 + attr_accessible rule
  71 + end
  72 +
  73 + # Number of suggestions by rule
  74 + N_SUGGESTIONS = 30
  75 +
  76 + # Minimum number of suggestions
  77 + MIN_LIMIT = 10
  78 +
  79 + def self.profile_id(rule)
  80 + "#{rule}_profile_id"
  81 + end
  82 +
  83 + def self.connections(rule)
  84 + "#{rule}_connections"
  85 + end
  86 +
  87 + def self.counter(rule)
  88 + "#{rule}_count"
  89 + end
  90 +
  91 + # If you are about to rewrite the following sql queries, think twice. After
  92 + # that make sure that whatever you are writing to replace it should be faster
  93 + # than how it is now. Yes, sqls are ugly but are fast! And fast is what we
  94 + # need here.
  95 + #
  96 + # The logic behind this code is to produce a table somewhat like this:
  97 + # profile_id | rule1_count | rule1_connections | rule2_count | rule2_connections | ... | score |
  98 + # 12 | 2 | {32,54} | 3 | {8,22,27} | ... | 13 |
  99 + # 13 | 4 | {3,12,32,54} | 2 | {11,24} | ... | 15 |
  100 + # 14 | | | 2 | {44,56} | ... | 17 |
  101 + # ...
  102 + # ...
  103 + #
  104 + # This table has the suggested profile id and the count and connections of
  105 + # each rule that made this profile be suggested. Each suggestion has a score
  106 + # associated based on the rules' counts and rules' weights.
  107 + #
  108 + # From this table, we can sort suggestions by the score and save a small
  109 + # amount of them in the database. At this moment we also register the
  110 + # connections of each suggestion.
  111 +
  112 + def self.calculate_suggestions(person)
  113 + suggested_profiles = all_suggestions(person)
  114 + return if suggested_profiles.nil?
  115 +
  116 + already_suggested_profiles = person.profile_suggestions.map(&:suggestion_id).join(',')
  117 + suggested_profiles = suggested_profiles.where("profiles.id NOT IN (#{already_suggested_profiles})") if already_suggested_profiles.present?
  118 + #TODO suggested_profiles = suggested_profiles.order('score DESC')
  119 + suggested_profiles = suggested_profiles.limit(N_SUGGESTIONS)
  120 + return if suggested_profiles.blank?
  121 +
  122 + suggested_profiles.each do |suggested_profile|
  123 + suggestion = person.profile_suggestions.find_or_initialize_by_suggestion_id(suggested_profile.id)
  124 + RULES.each do |rule, options|
  125 + begin
  126 + value = suggested_profile.send("#{rule}_count").to_i
  127 + rescue NoMethodError
  128 + next
  129 + end
  130 + connections = suggested_profile.send("#{rule}_connections")
  131 + if connections.present?
  132 + connections = connections[1..-2].split(',')
  133 + else
  134 + connections = []
  135 + end
  136 + suggestion.send("#{rule}=", value)
  137 + connections.each do |connection_id|
  138 + next if SuggestionConnection.where(:suggestion_id => suggestion.id, :connection_id => connection_id, :connection_type => options[:connection]).present?
  139 + SuggestionConnection.create!(:suggestion => suggestion, :connection_id => connection_id, :connection_type => options[:connection])
  140 + end
  141 + suggestion.score += value * options[:weight]
  142 + end
  143 + suggestion.save!
  144 + end
  145 + end
  146 +
  147 + def self.people_with_common_friends(person)
  148 + person_friends = person.friends.map(&:id)
  149 + rule = "people_with_common_friends"
  150 + return if person_friends.blank?
  151 + "SELECT person_id as #{profile_id(rule)},
  152 + array_agg(friend_id) as #{connections(rule)},
  153 + count(person_id) as #{counter(rule)}
  154 + FROM friendships WHERE friend_id IN (#{person_friends.join(',')})
  155 + AND person_id NOT IN (#{(person_friends << person.id).join(',')})
  156 + GROUP BY person_id"
  157 + end
  158 +
  159 + def self.people_with_common_communities(person)
  160 + person_communities = person.communities.map(&:id)
  161 + rule = "people_with_common_communities"
  162 + return if person_communities.blank?
  163 + "SELECT common_members.accessor_id as #{profile_id(rule)},
  164 + array_agg(common_members.resource_id) as #{connections(rule)},
  165 + count(common_members.accessor_id) as #{counter(rule)}
  166 + FROM
  167 + (SELECT DISTINCT accessor_id, resource_id FROM
  168 + role_assignments WHERE role_assignments.resource_id IN (#{person_communities.join(',')}) AND
  169 + role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
  170 + role_assignments.accessor_type = 'Profile') AS common_members
  171 + GROUP BY common_members.accessor_id"
  172 + end
  173 +
  174 + def self.people_with_common_tags(person)
  175 + profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
  176 + rule = "people_with_common_tags"
  177 + return if profile_tags.blank?
  178 + "SELECT results.profiles_id as #{profile_id(rule)},
  179 + array_agg(results.tags_id) as #{connections(rule)},
  180 + count(results.profiles_id) as #{counter(rule)}
  181 + FROM (
  182 + SELECT DISTINCT tags.id as tags_id, profiles.id as profiles_id FROM profiles
  183 + INNER JOIN articles ON articles.profile_id = profiles.id
  184 + INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
  185 + INNER JOIN tags ON tags.id = taggings.tag_id
  186 + WHERE (tags.id in (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
  187 + GROUP BY results.profiles_id"
  188 + end
  189 +
  190 + def self.communities_with_common_friends(person)
  191 + person_friends = person.friends.map(&:id)
  192 + rule = "communities_with_common_friends"
  193 + return if person_friends.blank?
  194 + "SELECT common_communities.resource_id as #{profile_id(rule)},
  195 + array_agg(common_communities.accessor_id) as #{connections(rule)},
  196 + count(common_communities.resource_id) as #{counter(rule)}
  197 + FROM
  198 + (SELECT DISTINCT accessor_id, resource_id FROM
  199 + role_assignments WHERE role_assignments.accessor_id IN (#{person_friends.join(',')}) AND
  200 + role_assignments.accessor_id != #{person.id} AND role_assignments.resource_type = 'Profile' AND
  201 + role_assignments.accessor_type = 'Profile') AS common_communities
  202 + GROUP BY common_communities.resource_id"
  203 + end
  204 +
  205 + def self.communities_with_common_tags(person)
  206 + profile_tags = person.articles.select('tags.id').joins(:tags).map(&:id)
  207 + rule = "communities_with_common_tags"
  208 + return if profile_tags.blank?
  209 + "SELECT results.profiles_id as #{profile_id(rule)},
  210 + array_agg(results.tags_id) as #{connections(rule)},
  211 + count(results.profiles_id) as #{counter(rule)}
  212 + FROM
  213 + (SELECT DISTINCT tags.id as tags_id, profiles.id AS profiles_id FROM profiles
  214 + INNER JOIN articles ON articles.profile_id = profiles.id
  215 + INNER JOIN taggings ON taggings.taggable_id = articles.id AND taggings.context = ('tags') AND taggings.taggable_type = 'Article'
  216 + INNER JOIN tags ON tags.id = taggings.tag_id
  217 + WHERE (tags.id IN (#{profile_tags.join(',')}) AND profiles.id != #{person.id})) AS results
  218 + GROUP BY results.profiles_id"
  219 + end
  220 +
  221 + def self.all_suggestions(person)
  222 + select_string = ["profiles.*"]
  223 + suggestions_join = []
  224 + where_string = []
  225 + valid_rules = []
  226 + previous_rule = nil
  227 + join_column = nil
  228 + RULES.each do |rule, options|
  229 + rule_select = self.send(rule, person)
  230 + next if !rule_select.present?
  231 +
  232 + valid_rules << rule
  233 + select_string << "suggestions.#{counter(rule)} as #{counter(rule)}, suggestions.#{connections(rule)} as #{connections(rule)}"
  234 + where_string << "#{counter(rule)} >= #{options[:threshold]}"
  235 + rule_select = "
  236 + (SELECT profiles.id as #{profile_id(rule)},
  237 + #{rule}_sub.#{counter(rule)} as #{counter(rule)},
  238 + #{rule}_sub.#{connections(rule)} as #{connections(rule)}
  239 + FROM profiles
  240 + LEFT OUTER JOIN (#{rule_select}) as #{rule}_sub
  241 + ON profiles.id = #{rule}_sub.#{profile_id(rule)}) AS #{rule}"
  242 +
  243 + if previous_rule.nil?
  244 + result = rule_select
  245 + else
  246 + result = "INNER JOIN #{rule_select}
  247 + ON #{previous_rule}.#{profile_id(previous_rule)} = #{rule}.#{profile_id(rule)}"
  248 + end
  249 + previous_rule = rule
  250 + suggestions_join << result
  251 + end
  252 +
  253 + return if valid_rules.blank?
  254 +
  255 + select_string = select_string.compact.join(',')
  256 + join_string = "INNER JOIN (SELECT * FROM #{suggestions_join.compact.join(' ')}) AS suggestions ON profiles.id = suggestions.#{profile_id(valid_rules.first)}"
  257 + where_string = where_string.compact.join(' OR ')
  258 +
  259 + person.environment.profiles.
  260 + select(select_string).
  261 + joins(join_string).
  262 + where(where_string)
  263 + end
  264 +
  265 + def disable
  266 + self.enabled = false
  267 + self.save!
  268 + self.class.generate_profile_suggestions(self.person)
  269 + end
  270 +
  271 + def self.generate_all_profile_suggestions
  272 + Delayed::Job.enqueue(ProfileSuggestion::GenerateAllJob.new) unless ProfileSuggestion::GenerateAllJob.exists?
  273 + end
  274 +
  275 + def self.generate_profile_suggestions(person, force = false)
  276 + return if person.profile_suggestions.enabled.count >= MIN_LIMIT && !force
  277 + Delayed::Job.enqueue ProfileSuggestionsJob.new(person.id) unless ProfileSuggestionsJob.exists?(person.id)
  278 + end
  279 +
  280 + class GenerateAllJob
  281 + def self.exists?
  282 + Delayed::Job.by_handler("--- !ruby/object:ProfileSuggestion::GenerateAllJob {}\n").count > 0
  283 + end
  284 +
  285 + def perform
  286 + Person.find_each {|person| ProfileSuggestion.generate_profile_suggestions(person) }
  287 + end
  288 + end
  289 +
  290 +end
app/models/qualifier.rb
@@ -3,7 +3,7 @@ class Qualifier &lt; ActiveRecord::Base @@ -3,7 +3,7 @@ class Qualifier &lt; ActiveRecord::Base
3 attr_accessible :name, :environment 3 attr_accessible :name, :environment
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :name => 1, 6 + :name => {:label => _('Name'), :weight => 1},
7 } 7 }
8 8
9 belongs_to :environment 9 belongs_to :environment
app/models/scrap.rb
@@ -3,7 +3,7 @@ class Scrap &lt; ActiveRecord::Base @@ -3,7 +3,7 @@ class Scrap &lt; ActiveRecord::Base
3 attr_accessible :content, :sender_id, :receiver_id, :scrap_id 3 attr_accessible :content, :sender_id, :receiver_id, :scrap_id
4 4
5 SEARCHABLE_FIELDS = { 5 SEARCHABLE_FIELDS = {
6 - :content => 1, 6 + :content => {:label => _('Content'), :weight => 1},
7 } 7 }
8 validates_presence_of :content 8 validates_presence_of :content
9 validates_presence_of :sender_id, :receiver_id 9 validates_presence_of :sender_id, :receiver_id
@@ -25,7 +25,7 @@ class Scrap &lt; ActiveRecord::Base @@ -25,7 +25,7 @@ class Scrap &lt; ActiveRecord::Base
25 25
26 after_create do |scrap| 26 after_create do |scrap|
27 scrap.root.update_attribute('updated_at', DateTime.now) unless scrap.root.nil? 27 scrap.root.update_attribute('updated_at', DateTime.now) unless scrap.root.nil?
28 - Scrap::Notifier.notification(scrap).deliver if scrap.send_notification? 28 + ScrapNotifier.notification(scrap).deliver if scrap.send_notification?
29 end 29 end
30 30
31 before_validation :strip_all_html_tags 31 before_validation :strip_all_html_tags
app/models/search_term.rb 0 → 100644
@@ -0,0 +1,63 @@ @@ -0,0 +1,63 @@
  1 +class SearchTerm < ActiveRecord::Base
  2 + validates_presence_of :term, :context
  3 + validates_uniqueness_of :term, :scope => [:context_id, :context_type, :asset]
  4 +
  5 + belongs_to :context, :polymorphic => true
  6 + has_many :occurrences, :class_name => 'SearchTermOccurrence'
  7 +
  8 + attr_accessible :term, :context, :asset
  9 +
  10 + def self.calculate_scores
  11 + os = occurrences_scores
  12 + find_each { |search_term| search_term.calculate_score(os) }
  13 + end
  14 +
  15 + def self.find_or_create(term, context, asset='all')
  16 + context.search_terms.where(:term => term, :asset => asset).first || context.search_terms.create!(:term => term, :asset=> asset)
  17 + end
  18 +
  19 + # Fast way of getting the occurrences score for each search_term. Ugly but fast!
  20 + #
  21 + # Each occurrence of a search_term has a score that is smaller the older the
  22 + # occurrence happened. We subtract the amount of time between now and the
  23 + # moment it happened from the total time any occurrence is valid to happen. E.g.:
  24 + # The expiration time is 100 days and an occurrence happened 3 days ago.
  25 + # Therefore the score is 97. Them we sum every score to get the total score
  26 + # for a search term.
  27 + def self.occurrences_scores
  28 + ActiveSupport::OrderedHash[*ActiveRecord::Base.connection.execute(
  29 + joins(:occurrences).
  30 + select("search_terms.id, sum(#{SearchTermOccurrence::EXPIRATION_TIME.to_i} - extract(epoch from (now() - search_term_occurrences.created_at))) as value").
  31 + where("search_term_occurrences.created_at > ?", DateTime.now - SearchTermOccurrence::EXPIRATION_TIME).
  32 + group("search_terms.id").
  33 + order('value DESC').
  34 + to_sql
  35 + ).map {|result| [result['id'].to_i, result['value'].to_i]}.flatten]
  36 + end
  37 +
  38 + def calculate_occurrence(occurrences_scores)
  39 + max_score = occurrences_scores.first[1]
  40 + (occurrences_scores[id]/max_score.to_f)*100
  41 + end
  42 +
  43 + def calculate_relevance(valid_occurrences)
  44 + indexed = valid_occurrences.last.indexed.to_f
  45 + return 0 if indexed == 0
  46 + total = valid_occurrences.last.total.to_f
  47 + (1 - indexed/total)*100
  48 + end
  49 +
  50 + def calculate_score(occurrences_scores)
  51 + valid_occurrences = occurrences.valid
  52 + if valid_occurrences.present?
  53 + # These scores vary from 1~100
  54 + self.occurrence_score = calculate_occurrence(occurrences_scores)
  55 + self.relevance_score = calculate_relevance(valid_occurrences)
  56 + else
  57 + self.occurrence_score = 0
  58 + self.relevance_score = 0
  59 + end
  60 + self.score = (occurrence_score * relevance_score)/100.0
  61 + self.save!
  62 + end
  63 +end
app/models/search_term_occurrence.rb 0 → 100644
@@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
  1 +class SearchTermOccurrence < ActiveRecord::Base
  2 + belongs_to :search_term
  3 + validates_presence_of :search_term
  4 + attr_accessible :search_term, :created_at, :total, :indexed
  5 +
  6 + EXPIRATION_TIME = 1.year
  7 +
  8 + scope :valid, :conditions => ["search_term_occurrences.created_at > ?", DateTime.now - EXPIRATION_TIME]
  9 +end
app/models/suggestion_connection.rb 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +class SuggestionConnection < ActiveRecord::Base
  2 + attr_accessible :suggestion, :connection_type, :connection_id
  3 +
  4 + belongs_to :suggestion, :class_name => 'ProfileSuggestion', :foreign_key => 'suggestion_id'
  5 + belongs_to :connection, :polymorphic => true
  6 +end
app/models/text_article.rb
1 require 'noosfero/translatable_content' 1 require 'noosfero/translatable_content'
2 2
3 -# a base class for all text article types. 3 +# a base class for all text article types.
4 class TextArticle < Article 4 class TextArticle < Article
5 5
6 xss_terminate :only => [ :name ], :on => 'validation' 6 xss_terminate :only => [ :name ], :on => 'validation'
@@ -26,10 +26,10 @@ class TextArticle &lt; Article @@ -26,10 +26,10 @@ class TextArticle &lt; Article
26 before_save :set_relative_path 26 before_save :set_relative_path
27 27
28 def set_relative_path 28 def set_relative_path
29 - parsed = Hpricot(self.body.to_s)  
30 - parsed.search('img[@src]').map { |i| change_element_path(i, 'src') }  
31 - parsed.search('a[@href]').map { |i| change_element_path(i, 'href') }  
32 - self.body = parsed.to_s 29 + parsed = Nokogiri::HTML.fragment(self.body.to_s)
  30 + parsed.css('img[src]').each { |i| change_element_path(i, 'src') }
  31 + parsed.css('a[href]').each { |i| change_element_path(i, 'href') }
  32 + self.body = parsed.to_html
33 end 33 end
34 34
35 def change_element_path(el, attribute) 35 def change_element_path(el, attribute)
app/models/user.rb
@@ -11,6 +11,10 @@ class User &lt; ActiveRecord::Base @@ -11,6 +11,10 @@ class User &lt; ActiveRecord::Base
11 N_('Password confirmation') 11 N_('Password confirmation')
12 N_('Terms accepted') 12 N_('Terms accepted')
13 13
  14 + SEARCHABLE_FIELDS = {
  15 + :email => {:label => _('Email'), :weight => 5},
  16 + }
  17 +
14 def self.[](login) 18 def self.[](login)
15 self.find_by_login(login) 19 self.find_by_login(login)
16 end 20 end
@@ -50,7 +54,7 @@ class User &lt; ActiveRecord::Base @@ -50,7 +54,7 @@ class User &lt; ActiveRecord::Base
50 54
51 user.person = p 55 user.person = p
52 end 56 end
53 - if user.environment.enabled?('skip_new_user_email_confirmation') 57 + if user.environment.enabled?('skip_new_user_email_confirmation')
54 if user.environment.enabled?('admin_must_approve_new_users') 58 if user.environment.enabled?('admin_must_approve_new_users')
55 create_moderate_task 59 create_moderate_task
56 else 60 else
@@ -98,6 +102,7 @@ class User &lt; ActiveRecord::Base @@ -98,6 +102,7 @@ class User &lt; ActiveRecord::Base
98 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?}) 102 validates_length_of :email, :within => 3..100, :if => (lambda {|user| !user.email.blank?})
99 validates_uniqueness_of :login, :email, :case_sensitive => false, :scope => :environment_id 103 validates_uniqueness_of :login, :email, :case_sensitive => false, :scope => :environment_id
100 before_save :encrypt_password 104 before_save :encrypt_password
  105 + before_save :normalize_email, if: proc{ |u| u.email.present? }
101 validates_format_of :email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?}) 106 validates_format_of :email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|user| !user.email.blank?})
102 107
103 validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n 108 validates_inclusion_of :terms_accepted, :in => [ '1' ], :if => lambda { |u| ! u.terms_of_use.blank? }, :message => N_('{fn} must be checked in order to signup.').fix_i18n
@@ -110,6 +115,10 @@ class User &lt; ActiveRecord::Base @@ -110,6 +115,10 @@ class User &lt; ActiveRecord::Base
110 u && u.authenticated?(password) ? u : nil 115 u && u.authenticated?(password) ? u : nil
111 end 116 end
112 117
  118 + def register_login
  119 + self.update_attribute :last_login_at, Time.now
  120 + end
  121 +
113 # Activates the user in the database. 122 # Activates the user in the database.
114 def activate 123 def activate
115 return false unless self.person 124 return false unless self.person
@@ -328,6 +337,11 @@ class User &lt; ActiveRecord::Base @@ -328,6 +337,11 @@ class User &lt; ActiveRecord::Base
328 end 337 end
329 338
330 protected 339 protected
  340 +
  341 + def normalize_email
  342 + self.email = self.email.squish.downcase
  343 + end
  344 +
331 # before filter 345 # before filter
332 def encrypt_password 346 def encrypt_password
333 return if password.blank? 347 return if password.blank?
@@ -355,6 +369,6 @@ class User &lt; ActiveRecord::Base @@ -355,6 +369,6 @@ class User &lt; ActiveRecord::Base
355 369
356 def delay_activation_check 370 def delay_activation_check
357 return if person.is_template? 371 return if person.is_template?
358 - Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => 72.hours.from_now}) 372 + Delayed::Job.enqueue(UserActivationJob.new(self.id), {:priority => 0, :run_at => (NOOSFERO_CONF['hours_until_user_activation_check'] || 72).hours.from_now})
359 end 373 end
360 end 374 end
app/views/account/_login_form.html.erb
1 <%= labelled_form_for :user, 1 <%= labelled_form_for :user,
2 :url => { :controller => 'account', :action => (params[:enterprise_code] ? 'activate_enterprise' : 'login') } do |f| %> 2 :url => { :controller => 'account', :action => (params[:enterprise_code] ? 'activate_enterprise' : 'login') } do |f| %>
3 3
4 -<%= f.text_field :login,  
5 - :id => ( lightbox? ? 'lightbox_' : '' ) + 'user_login',  
6 - :onchange => 'this.value = convToValidLogin( this.value )' %> 4 +<%= f.text_field :login, :id => 'user_login', :onchange => 'this.value = convToValidLogin( this.value )' %>
7 5
8 -<%= f.password_field :password,  
9 - :id => ( lightbox? ? 'lightbox_' : '' ) + 'user_password' %> 6 +<%= f.password_field :password, :id => 'user_password' %>
10 7
11 <% if params[:enterprise_code] %> 8 <% if params[:enterprise_code] %>
12 <%= hidden_field_tag :enterprise_code, params[:enterprise_code] %> 9 <%= hidden_field_tag :enterprise_code, params[:enterprise_code] %>
@@ -16,7 +13,7 @@ @@ -16,7 +13,7 @@
16 13
17 <% button_bar do %> 14 <% button_bar do %>
18 <%= submit_button( 'login', _('Log in') )%> 15 <%= submit_button( 'login', _('Log in') )%>
19 - <%= lightbox_close_button(_('Cancel')) if lightbox? %> 16 + <%= modal_close_button _('Cancel') if request.xhr? %>
20 <% end %> 17 <% end %>
21 18
22 <% end %> 19 <% end %>
app/views/account/_signup_form.html.erb
@@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
16 <input type="hidden" id="signup_time_key" name="signup_time_key" /> 16 <input type="hidden" id="signup_time_key" name="signup_time_key" />
17 <script type="text/javascript"> 17 <script type="text/javascript">
18 jQuery.ajax({ 18 jQuery.ajax({
19 - type: "POST", 19 + type: "GET",
20 url: "<%= url_for :controller=>'account', :action=>'signup_time' %>", 20 url: "<%= url_for :controller=>'account', :action=>'signup_time' %>",
21 dataType: 'json', 21 dataType: 'json',
22 success: function(data) { 22 success: function(data) {
app/views/account/activate_enterprise.html.erb
@@ -7,8 +7,8 @@ @@ -7,8 +7,8 @@
7 <p><%= _('Do you have a personal user account in the system?') %></p> 7 <p><%= _('Do you have a personal user account in the system?') %></p>
8 8
9 <div id="enterprise-activation-create-user-or-login-button"> 9 <div id="enterprise-activation-create-user-or-login-button">
10 - <%= button_to_function 'login', _('Yes'), "$('enterprise-activation-create-user-form').hide(); $('enterprise-activation-login-form').show()" %>  
11 - <%= button_to_function 'add', _('No'), "$('enterprise-activation-login-form').hide(); $('enterprise-activation-create-user-form').show()" %> 10 + <%= button_to_function 'login', _('Yes'), "jQuery('#enterprise-activation-create-user-form').hide(); jQuery('#enterprise-activation-login-form').show()" %>
  11 + <%= button_to_function 'add', _('No'), "jQuery('#enterprise-activation-login-form').hide(); jQuery('#enterprise-activation-create-user-form').show()" %>
12 </div> 12 </div>
13 13
14 <div id="enterprise-activation-create-user-form" style="display: none"> 14 <div id="enterprise-activation-create-user-form" style="display: none">
app/views/account/index_anonymous.html.erb
1 <h1><%= _('Identify yourself') %></h1> 1 <h1><%= _('Identify yourself') %></h1>
2 2
3 <p> 3 <p>
4 -<%= lightbox_link_to _('Login.'), { :controller => 'account', :action => 'login_popup' } %> 4 +<%= modal_link_to _('Login.'), { :controller => 'account', :action => 'login_popup' } %>
5 5
6 <%= _('You need to login to be able to use all the features in this environment.') %> 6 <%= _('You need to login to be able to use all the features in this environment.') %>
7 </p> 7 </p>
app/views/account/login.html.erb
@@ -3,11 +3,11 @@ @@ -3,11 +3,11 @@
3 <h2><%= _('Login') %></h2> 3 <h2><%= _('Login') %></h2>
4 4
5 <% @user ||= User.new %> 5 <% @user ||= User.new %>
6 -<% is_thickbox ||= false %> 6 +<% is_popin ||= false %>
7 7
8 <%= @message %> 8 <%= @message %>
9 9
10 -<%= labelled_form_for :user, :url => login_url do |f| %> 10 +<%= labelled_form_for :user, :url => login_url, :horizontal => true do |f| %>
11 11
12 <%= f.text_field :login, :id => 'main_user_login', :onchange => 'this.value = convToValidLogin( this.value )', :value => params[:userlogin] %> 12 <%= f.text_field :login, :id => 'main_user_login', :onchange => 'this.value = convToValidLogin( this.value )', :value => params[:userlogin] %>
13 13
@@ -17,8 +17,8 @@ @@ -17,8 +17,8 @@
17 17
18 <% button_bar do %> 18 <% button_bar do %>
19 <%= submit_button( 'login', _('Log in') )%> 19 <%= submit_button( 'login', _('Log in') )%>
20 - <% if is_thickbox %>  
21 - <%= thickbox_close_button(_('Cancel')) %> 20 + <% if is_popin %>
  21 + <%= modal_close_button(_('Cancel')) %>
22 <% end %> 22 <% end %>
23 <% end %> 23 <% end %>
24 24
app/views/account/login_block.html.erb
@@ -20,9 +20,7 @@ @@ -20,9 +20,7 @@
20 <% button_bar do %> 20 <% button_bar do %>
21 <%= submit_button( 'login', _('Log in') )%> 21 <%= submit_button( 'login', _('Log in') )%>
22 <% unless @plugins.dispatch(:allow_user_registration).include?(false) %> 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' %> 23 + <%= button(:add, _('New user'), { :controller => 'account', :action => 'signup' }) %>
26 <% end %> 24 <% end %>
27 <% end %> 25 <% end %>
28 26
app/views/account/logout_popup.html.erb
@@ -2,6 +2,6 @@ @@ -2,6 +2,6 @@
2 <p> 2 <p>
3 <% button_bar do %> 3 <% button_bar do %>
4 <%= button :ok, _('Yes'), { :controller => 'account', :action => 'logout' } %> 4 <%= button :ok, _('Yes'), { :controller => 'account', :action => 'logout' } %>
5 - <%= lightbox_close_button _('No, I want to stay.') %> 5 + <%= modal_close_button _('No, I want to stay.') %>
6 <% end %> 6 <% end %>
7 </p> 7 </p>
app/views/account/signup.html.erb
1 -<% if @register_pending %>  
2 - <div id='thanks-for-signing'>  
3 - <% if environment.has_custom_welcome_screen? %>  
4 - <%= environment.settings[:signup_welcome_screen_body].html_safe %>  
5 - <% elsif environment.enabled?('admin_must_approve_new_users')%>  
6 - <h1><%= _("Welcome to %s!") % environment.name %></h1>  
7 - <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>  
8 - <p><%= _("Firstly, some tips for getting started:") %></p>  
9 - <% unless environment.enabled?('skip_new_user_email_confirmation') %>  
10 - <h4><%= _("Confirm your account and wait for admin approvement!") %></h4>  
11 - <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>  
12 - <p><%= _("You won't appear as %s until your account is confirmed and approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
13 - <% else %>  
14 - <h4><%= _("Wait for admin approvement!") %></h4>  
15 - <p><%= _("The administrators will evaluate your signup request for approvement.") %></p>  
16 - <p><%= _("You won't appear as %s until your account is approved.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
17 - <% end %>  
18 - <h4><%= _("What to do next?") %></h4>  
19 - <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>  
20 - <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>  
21 - <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>  
22 - <p><%= _("Start exploring and have fun!") %></p>  
23 - <% else %>  
24 - <h1><%= _("Welcome to %s!") % environment.name %></h1>  
25 - <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>  
26 - <p><%= _("Firstly, some tips for getting started:") %></p>  
27 - <h4><%= _("Confirm your account!") %></h4>  
28 - <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>  
29 - <p><%= _("You won't appear as %s until your account is confirmed.") % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>  
30 - <h4><%= _("What to do next?") %></h4>  
31 - <p><%= _("%s. Upload an avatar and let your friends find you easily :)") % link_to(_('Customize your profile'), {:controller => 'doc', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank') %></p>  
32 - <p><%= _("Learn the guidelines. Read the %s for more details on how to use this social network!") % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>  
33 - <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>  
34 - <p><%= _("Start exploring and have fun!") %></p>  
35 - <% end %>  
36 - </div>  
37 -<% else %>  
38 - <h1><%= _('Sign up for %s!') % environment.name %></h1>  
39 - <%= render :partial => 'signup_form' %>  
40 -<% end %> 1 +<h1><%= _('Sign up for %s!') % environment.name %></h1>
  2 +<%= render :partial => 'signup_form' %>
app/views/admin_panel/_signup_welcome_screen.html.erb
1 <div class='description'> 1 <div class='description'>
2 - <%= _('This text will be showed as a welcome message to users after signup') %><br/><br/> 2 + <%= _('If you enable this feature on the "Features" section of the Administration Panel, this text will be shown as a welcome message to users after signup.') %>
3 </div> 3 </div>
4 -  
5 <%= labelled_form_field(_('Body'), text_area(:environment, :signup_welcome_screen_body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor')) %> 4 <%= labelled_form_field(_('Body'), text_area(:environment, :signup_welcome_screen_body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor')) %>
  5 +
  6 +<div class='description'>
  7 + <%= _('If this content is left blank, the following page will be displayed to the user:') %>
  8 +</div>
  9 +
  10 +<%= render :file => 'home/welcome', :locals => {:default_message => true} %>
app/views/admin_panel/set_portal_community.html.erb
@@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
18 <%= button 'ok', _('Enable'), {:action => 'manage_portal_community', :activate => 1} %> 18 <%= button 'ok', _('Enable'), {:action => 'manage_portal_community', :activate => 1} %>
19 <% end %> 19 <% end %>
20 <%= button 'folder', _('Select Portal Folders'), {:action => 'set_portal_folders'} %> 20 <%= button 'folder', _('Select Portal Folders'), {:action => 'set_portal_folders'} %>
21 - <%= button 'edit', _('Define Amount by Folder'), {:action => 'set_portal_news_amount'} %> 21 + <%= button 'edit', _('Define news amount on portal'), {:action => 'set_portal_news_amount'} %>
22 <%= button 'delete', _('Remove'), { :action => 'unset_portal_community'} %> 22 <%= button 'delete', _('Remove'), { :action => 'unset_portal_community'} %>
23 <% end %> 23 <% end %>
24 <% end %> 24 <% end %>