Commit 34d3a283087086d38b318c8e5021ba60e301a1e2

Authored by Rodrigo Souto
2 parents ef453b43 d2029dc6

Merge branch 'stoa' into 'master'

Stoa Network Features

This merge-request groups 6 features developed majorly by me, @danielafeitosa and @larissa funded by Stoa Network. These features were finished during the freezing time and only now we were able to update it to the current master. I'm sorry that we'll merge them all together in a single request, but we had to group them on a single branch do improve the efficiency of our work. Here are the features grouped here:
  - Media gallery complete refactoring
  - Welcome page improvements
  - Better interaction with other networks
  - Friends and communities suggestions
  - Invitation improvements
  - Search improvements

All 6 features were reviewed by at least one of us and since we are committers, I'm merging this right away. This merge-request was made more for information sake.

See merge request !451
Showing 354 changed files with 15890 additions and 880 deletions   Show diff stats

Too many changes.

To preserve performance only 100 of 354 files displayed.

Gemfile
... ... @@ -21,6 +21,8 @@ gem 'exception_notification', '~> 4.0.1'
21 21 gem 'gettext', '~> 2.2.1', :require => false, :group => :development
22 22 gem 'locale', '~> 2.0.5'
23 23  
  24 +gem 'whenever', :require => false
  25 +
24 26 # FIXME list here all actual dependencies (i.e. the ones in debian/control),
25 27 # with their GEM names (not the Debian package names)
26 28  
... ...
app/controllers/admin/region_validators_controller.rb
... ... @@ -33,7 +33,7 @@ class RegionValidatorsController < AdminController
33 33 def load_region_and_search
34 34 @region = environment.regions.find(params[:id])
35 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 37 end
38 38 end
39 39  
... ...
app/controllers/admin/users_controller.rb
... ... @@ -18,7 +18,7 @@ class UsersController < AdminController
18 18 end
19 19 scope = scope.order('name ASC')
20 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 22 end
23 23  
24 24 def set_admin_role
... ...
app/controllers/application_controller.rb
... ... @@ -183,21 +183,19 @@ class ApplicationController < ActionController::Base
183 183 end
184 184 end
185 185  
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
  186 + include SearchTermHelper
190 187  
191   - private
  188 + def find_by_contents(asset, context, scope, query, paginate_options={:page => 1}, options={})
  189 + search = plugins.dispatch_first(:find_by_contents, asset, scope, query, paginate_options, options)
  190 + register_search_term(query, scope.count, search[:results].count, context, asset)
  191 + search
  192 + end
192 193  
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)}
  194 + def find_suggestions(query, context, asset, options={})
  195 + plugins.dispatch_first(:find_suggestions, query, context, asset, options)
197 196 end
198 197  
199 198 def private_environment?
200 199 @environment.enabled?(:restrict_to_members)
201 200 end
202   -
203 201 end
... ...
app/controllers/my_profile/cms_controller.rb
... ... @@ -23,6 +23,9 @@ class CmsController < MyProfileController
23 23 end
24 24  
25 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 30 protect_if :only => :upload_files do |c, user, profile|
28 31 article_id = c.params[:parent_id]
... ... @@ -30,7 +33,7 @@ class CmsController < MyProfileController
30 33 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
31 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 37 user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile))
35 38 end
36 39  
... ... @@ -40,7 +43,7 @@ class CmsController < MyProfileController
40 43 (user && (user.has_permission?('post_content', profile) || user.has_permission?('publish_content', profile)))
41 44 end
42 45  
43   - protect_if :only => [:destroy, :publish] do |c, user, profile|
  46 + protect_if :only => :destroy do |c, user, profile|
44 47 profile.articles.find(c.params[:id]).allow_post_content?(user)
45 48 end
46 49  
... ... @@ -117,7 +120,7 @@ class CmsController < MyProfileController
117 120 @success_back_to = params[:success_back_to]
118 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 124 record_coming
122 125 @type = params[:type]
123 126 if @type.blank?
... ... @@ -163,7 +166,10 @@ class CmsController < MyProfileController
163 166 if continue
164 167 redirect_to :action => 'edit', :id => @article
165 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 173 end
168 174 return
169 175 end
... ... @@ -256,28 +262,53 @@ class CmsController < MyProfileController
256 262 render :template => 'shared/update_categories', :locals => { :category => @current_category, :object_name => 'article' }
257 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 269 def publish
260 270 @article = profile.articles.find(params[:id])
261 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 289 end
269   - end.compact unless params[:marked_groups].nil?
  290 + end
  291 + end
  292 +
  293 + def publish_on_communities
270 294 if request.post?
  295 + @back_to = params[:back_to]
  296 + @article = profile.articles.find(params[:id])
271 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 301 if @marked_groups.empty?
  302 + redirect_to @back_to
273 303 return session[:notice] = _("Select some group to publish your article")
274 304 end
275 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 307 begin
278   - task.finish unless item[:group].moderated_articles?
  308 + task.finish unless item.moderated_articles?
279 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 312 end
282 313 end
283 314 if @failed.blank?
... ... @@ -287,23 +318,27 @@ class CmsController &lt; MyProfileController
287 318 else
288 319 redirect_to @article.view_url
289 320 end
  321 + else
  322 + session[:notice] = _("Some of your publish requests couldn't be sent.")
  323 + render :action => 'publish'
290 324 end
291 325 end
292 326 end
293 327  
294 328 def publish_on_portal_community
295   - @article = profile.articles.find(params[:id])
296 329 if request.post?
297   - if environment.portal_community
  330 + @article = profile.articles.find(params[:id])
  331 + if environment.portal_enabled
298 332 task = ApproveArticle.create!(:article => @article, :name => params[:name], :target => environment.portal_community, :requestor => user)
299 333 begin
300 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 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 339 end
305 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 342 end
308 343  
309 344 if @back_to
... ... @@ -331,7 +366,7 @@ class CmsController &lt; MyProfileController
331 366  
332 367 def search
333 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 370 render :text => article_list_to_json(results), :content_type => 'application/json'
336 371 end
337 372  
... ... @@ -342,15 +377,26 @@ class CmsController &lt; MyProfileController
342 377 end
343 378  
344 379 def media_upload
345   - files_uploaded = []
346 380 parent = check_parent(params[:parent_id])
347   - files = [:file1,:file2, :file3].map { |f| params[f] }.compact
348 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 387 end
352 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 400 end
355 401  
356 402 protected
... ... @@ -445,4 +491,36 @@ class CmsController &lt; MyProfileController
445 491 end
446 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 526 end
... ...
app/controllers/my_profile/friends_controller.rb
... ... @@ -3,6 +3,7 @@ class FriendsController &lt; MyProfileController
3 3 protect 'manage_friends', :profile
4 4  
5 5 def index
  6 + @suggestions = profile.profile_suggestions.of_person.enabled.includes(:suggestion).limit(per_page)
6 7 if is_cache_expired?(profile.manage_friends_cache_key(params))
7 8 @friends = profile.friends.paginate(:per_page => per_page, :page => params[:npage])
8 9 end
... ... @@ -16,6 +17,30 @@ class FriendsController &lt; MyProfileController
16 17 end
17 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 44 protected
20 45  
21 46 class << self
... ...
app/controllers/my_profile/memberships_controller.rb
... ... @@ -20,12 +20,54 @@ class MembershipsController &lt; MyProfileController
20 20 @community.environment = environment
21 21 @back_to = params[:back_to] || url_for(:action => 'index')
22 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 30 session[:notice] = _('Your new community creation request will be evaluated by an administrator. You will be notified.')
  31 + redirect_to @back_to
26 32 end
27   - redirect_to @back_to
28 33 return
29 34 end
30 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 73 end
... ...
app/controllers/my_profile/profile_editor_controller.rb
... ... @@ -3,6 +3,10 @@ class ProfileEditorController &lt; MyProfileController
3 3 protect 'edit_profile', :profile, :except => [:destroy_profile]
4 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 10 def index
7 11 @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)}
8 12 end
... ... @@ -85,6 +89,21 @@ class ProfileEditorController &lt; MyProfileController
85 89 end
86 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 107 def deactivate_profile
89 108 if environment.admins.include?(current_person)
90 109 profile = environment.profiles.find(params[:id])
... ... @@ -116,9 +135,24 @@ class ProfileEditorController &lt; MyProfileController
116 135 protected
117 136  
118 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 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 158 end
... ...
app/controllers/public/account_controller.rb
... ... @@ -82,10 +82,12 @@ class AccountController &lt; ApplicationController
82 82 if @plugins.dispatch(:allow_user_registration).include?(false)
83 83 redirect_back_or_default(:controller => 'home')
84 84 session[:notice] = _("This environment doesn't allow user registration.")
  85 + return
85 86 end
86 87  
87 88 store_location(request.referer) unless params[:return_to] or session[:return_to]
88 89  
  90 + # Tranforming to boolean
89 91 @block_bot = !!session[:may_be_a_bot]
90 92 @invitation_code = params[:invitation_code]
91 93 begin
... ... @@ -129,8 +131,8 @@ class AccountController &lt; ApplicationController
129 131 check_join_in_community(@user)
130 132 go_to_signup_initial_page
131 133 else
  134 + redirect_to :controller => :home, :action => :welcome, :template_id => (@user.person.template && @user.person.template.id)
132 135 session[:notice] = _('Thanks for registering!')
133   - @register_pending = true
134 136 end
135 137 end
136 138 end
... ... @@ -461,6 +463,8 @@ class AccountController &lt; ApplicationController
461 463 redirect_to user.url
462 464 when 'user_control_panel'
463 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 468 else
465 469 redirect_back_or_default(default)
466 470 end
... ...
app/controllers/public/home_controller.rb
... ... @@ -18,4 +18,10 @@ class HomeController &lt; PublicController
18 18 @no_design_blocks = true
19 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 27 end
... ...
app/controllers/public/invite_controller.rb
... ... @@ -4,8 +4,15 @@ class InviteController &lt; PublicController
4 4 before_filter :login_required
5 5 before_filter :check_permissions_to_invite
6 6  
7   - def select_address_book
  7 + def invite_friends
8 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 16 if request.post?
10 17 contact_list = ContactList.create
11 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 29 webmail_import_addresses = params[:webmail_import_addresses]
23 30 contacts_to_invite = Invitation.join_contacts(manual_import_addresses, webmail_import_addresses)
24 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 33 session[:notice] = _('Your invitations are being sent.')
27 34 if profile.person?
28 35 redirect_to :controller => 'profile', :action => 'friends'
... ... @@ -52,16 +59,36 @@ class InviteController &lt; PublicController
52 59 def cancel_fetching_emails
53 60 contact_list = ContactList.find(params[:contact_list])
54 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 87 end
57 88  
58 89 protected
59 90  
60 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 93 end
66   -
67 94 end
... ...
app/controllers/public/profile_search_controller.rb
... ... @@ -9,9 +9,12 @@ class ProfileSearchController &lt; PublicController
9 9 @q = params[:q]
10 10 unless @q.blank?
11 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 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 18 end
16 19 end
17 20 end
... ...
app/controllers/public/search_controller.rb
... ... @@ -4,11 +4,11 @@ class SearchController &lt; PublicController
4 4 include SearchHelper
5 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 13 # Backwards compatibility with old URLs
14 14 def redirect_asset_param
... ... @@ -20,7 +20,7 @@ class SearchController &lt; PublicController
20 20  
21 21 def index
22 22 @searches = {}
23   - @order = []
  23 + @assets = []
24 24 @names = {}
25 25 @results_only = true
26 26  
... ... @@ -28,7 +28,7 @@ class SearchController &lt; PublicController
28 28 load_query
29 29 @asset = key
30 30 send(key)
31   - @order << key
  31 + @assets << key
32 32 @names[key] = _(description)
33 33 end
34 34 @asset = nil
... ... @@ -42,7 +42,7 @@ class SearchController &lt; PublicController
42 42 # view the summary of one category
43 43 def category_index
44 44 @searches = {}
45   - @order = []
  45 + @assets = []
46 46 @names = {}
47 47 limit = MULTIPLE_SEARCH_LIMIT
48 48 [
... ... @@ -53,7 +53,7 @@ class SearchController &lt; PublicController
53 53 [ :communities, _('Communities'), :recent_communities ],
54 54 [ :articles, _('Contents'), :recent_articles ]
55 55 ].each do |asset, name, filter|
56   - @order << asset
  56 + @assets << asset
57 57 @searches[asset]= {:results => @category.send(filter, limit)}
58 58 raise "No total_entries for: #{asset}" unless @searches[asset][:results].respond_to?(:total_entries)
59 59 @names[asset] = name
... ... @@ -147,12 +147,16 @@ class SearchController &lt; PublicController
147 147 render :partial => 'events/events'
148 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 155 protected
152 156  
153 157 def load_query
154 158 @asset = (params[:asset] || params[:action]).to_sym
155   - @order ||= [@asset]
  159 + @assets ||= [@asset]
156 160 @searches ||= {}
157 161  
158 162 @query = params[:query] || ''
... ... @@ -173,13 +177,22 @@ class SearchController &lt; PublicController
173 177 end
174 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 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 191 render_not_found
179 192 return
180 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 196 @searching = {}
184 197 @titles = {}
185 198 @enabled_searches.each do |key, name|
... ... @@ -189,11 +202,11 @@ class SearchController &lt; PublicController
189 202 @names = @titles if @names.nil?
190 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 210 end
198 211 end
199 212  
... ... @@ -217,7 +230,7 @@ class SearchController &lt; PublicController
217 230 end
218 231  
219 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 234 end
222 235  
223 236 private
... ... @@ -232,4 +245,14 @@ class SearchController &lt; PublicController
232 245 20
233 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 258 end
... ...
app/helpers/application_helper.rb
... ... @@ -1307,8 +1307,19 @@ module ApplicationHelper
1307 1307 end
1308 1308 end
1309 1309  
1310   - def remove_content_button(action)
1311   - @plugins.dispatch("content_remove_#{action.to_s}", @page).include?(true)
  1310 + def content_remove_spread(content)
  1311 + !content.public? || content.folder? || (profile == user && user.communities.blank? && !environment.portal_enabled)
  1312 + end
  1313 +
  1314 + def remove_content_button(action, content)
  1315 + method_name = "content_remove_#{action.to_s}"
  1316 + plugin_condition = @plugins.dispatch(method_name, content).include?(true)
  1317 + begin
  1318 + core_condition = self.send(method_name, content)
  1319 + rescue NoMethodError
  1320 + core_condition = false
  1321 + end
  1322 + core_condition || plugin_condition
1312 1323 end
1313 1324  
1314 1325 def template_options(kind, field_name)
... ... @@ -1411,6 +1422,43 @@ module ApplicationHelper
1411 1422 content_tag('ul', article.versions.map {|v| link_to("r#{v.version}", @page.url.merge(:version => v.version))})
1412 1423 end
1413 1424  
  1425 + def search_input_with_suggestions(name, asset, default, options = {})
  1426 + text_field_tag name, default, options.merge({:class => 'search-input-with-suggestions', 'data-asset' => asset})
  1427 + end
  1428 +
  1429 + def profile_suggestion_profile_connections(suggestion)
  1430 + profiles = suggestion.profile_connections.first(4).map do |profile|
  1431 + link_to(profile_image(profile, :icon, :title => profile.name), profile.url, :class => 'profile-suggestion-connection-icon')
  1432 + end
  1433 +
  1434 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1435 + 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
  1436 +
  1437 + if profiles.present?
  1438 + content_tag(:div, profiles.join , :class => 'profile-connections')
  1439 + else
  1440 + ''
  1441 + end
  1442 + end
  1443 +
  1444 + def profile_suggestion_tag_connections(suggestion)
  1445 + tags = suggestion.tag_connections.first(4).map do |tag|
  1446 + tag.name + ', '
  1447 + end
  1448 + last_tag = tags.pop
  1449 + tags << last_tag.strip.chop if last_tag.present?
  1450 + title = tags.join
  1451 +
  1452 + controller_target = suggestion.suggestion_type == 'Person' ? :friends : :memberships
  1453 + 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
  1454 +
  1455 + if tags.present?
  1456 + content_tag(:div, tags.join, :class => 'tag-connections', :title => title)
  1457 + else
  1458 + ''
  1459 + end
  1460 + end
  1461 +
1414 1462 def labelled_colorpicker_field(human_name, object_name, method, options = {})
1415 1463 options[:id] ||= 'text-field-' + FormsHelper.next_id_number
1416 1464 content_tag('label', human_name, :for => options[:id], :class => 'formlabel') +
... ...
app/helpers/cms_helper.rb
... ... @@ -40,12 +40,8 @@ module CmsHelper
40 40 end
41 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 45 end
50 46  
51 47 def display_delete_button(article)
... ...
app/helpers/forms_helper.rb
... ... @@ -265,7 +265,7 @@ module FormsHelper
265 265 )
266 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 269 if find_options.empty?
270 270 folders = profile.folders
271 271 else
... ... @@ -276,7 +276,7 @@ module FormsHelper
276 276 select_tag(
277 277 field_id,
278 278 options_for_select(
279   - [[profile.identifier, '']] +
  279 + [[(extra_options[:root_label] || profile.identifier), '']] +
280 280 folders.collect {|f| [ profile.identifier + '/' + f.full_name, f.id.to_s ] },
281 281 default_value.to_s
282 282 ),
... ...
app/helpers/layout_helper.rb
... ... @@ -48,6 +48,7 @@ module LayoutHelper
48 48 'thickbox',
49 49 'lightbox',
50 50 'colorbox',
  51 + 'selectordie',
51 52 'inputosaurus',
52 53 pngfix_stylesheet_path,
53 54 ] + tokeninput_stylesheets
... ...
app/helpers/search_helper.rb
... ... @@ -5,20 +5,23 @@ module SearchHelper
5 5 BLOCKS_SEARCH_LIMIT = 24
6 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 27 COMMON_PROFILE_LIST_BLOCK = [
... ... @@ -56,7 +59,7 @@ module SearchHelper
56 59 end
57 60  
58 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 63 end
61 64  
62 65 def display_results(searches=nil, asset=nil)
... ... @@ -93,6 +96,16 @@ module SearchHelper
93 96 end
94 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 109 def display_selector(asset, display, float = 'right')
97 110 display = nil if display.blank?
98 111 display ||= asset_class(asset).default_search_display
... ... @@ -107,36 +120,32 @@ module SearchHelper
107 120 end
108 121 end
109 122  
110   - def filter_selector(asset, filter, float = 'right')
  123 + def filters(asset)
  124 + return if !asset
111 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 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 149 end
141 150  
142 151 end
... ...
app/helpers/search_term_helper.rb 0 → 100644
... ... @@ -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/token_helper.rb
... ... @@ -12,6 +12,7 @@ module TokenHelper
12 12 options[:search_delay] ||= 1000
13 13 options[:prevent_duplicates] ||= true
14 14 options[:backspace_delete_item] ||= false
  15 + options[:zindex] ||= 999
15 16 options[:focus] ||= false
16 17 options[:avoid_enter] ||= true
17 18 options[:on_result] ||= 'null'
... ... @@ -31,6 +32,7 @@ module TokenHelper
31 32 searchDelay: #{options[:search_delay].to_json},
32 33 preventDuplicates: #{options[:prevent_duplicates].to_json},
33 34 backspaceDeleteItem: #{options[:backspace_delete_item].to_json},
  35 + zindex: #{options[:zindex].to_json},
34 36 queryParam: #{options[:query_param].to_json},
35 37 tokenLimit: #{options[:token_limit].to_json},
36 38 onResult: #{options[:on_result]},
... ...
app/mailers/user_mailer.rb
... ... @@ -41,6 +41,23 @@ class UserMailer &lt; ActionMailer::Base
41 41 )
42 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 61 class Job < Struct.new(:user, :method)
45 62 def perform
46 63 UserMailer.send(method, user).deliver
... ...
app/models/add_friend.rb
... ... @@ -14,6 +14,11 @@ class AddFriend &lt; Task
14 14 alias :friend :target
15 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 22 def perform
18 23 target.add_friend(requestor, group_for_friend)
19 24 requestor.add_friend(target, group_for_person)
... ... @@ -48,4 +53,8 @@ class AddFriend &lt; Task
48 53 {:type => :profile_image, :profile => requestor, :url => requestor.url}
49 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 60 end
... ...
app/models/add_member.rb
... ... @@ -10,6 +10,10 @@ class AddMember &lt; Task
10 10  
11 11 settings_items :roles
12 12  
  13 + after_create do |task|
  14 + remove_from_suggestion_list(task)
  15 + end
  16 +
13 17 def perform
14 18 if !self.roles or (self.roles.uniq.compact.length == 1 and self.roles.uniq.compact.first.to_i.zero?)
15 19 self.roles = [Profile::Roles.member(organization.environment.id).id]
... ... @@ -46,4 +50,9 @@ class AddMember &lt; Task
46 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 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 58 end
... ...
app/models/article.rb
... ... @@ -14,20 +14,17 @@ class Article &lt; ActiveRecord::Base
14 14 acts_as_having_image
15 15  
16 16 SEARCHABLE_FIELDS = {
17   - :name => 10,
18   - :abstract => 3,
19   - :body => 2,
20   - :slug => 1,
21   - :filename => 1,
  17 + :name => {:label => _('Name'), :weight => 10},
  18 + :abstract => {:label => _('Abstract'), :weight => 3},
  19 + :body => {:label => _('Content'), :weight => 2},
  20 + :slug => {:label => _('Slug'), :weight => 1},
  21 + :filename => {:label => _('Filename'), :weight => 1},
22 22 }
23 23  
24   - SEARCH_FILTERS = %w[
25   - more_recent
26   - more_popular
27   - more_comments
28   - ]
29   -
30   - SEARCH_DISPLAYS = %w[full]
  24 + SEARCH_FILTERS = {
  25 + :order => %w[more_recent more_popular more_comments],
  26 + :display => %w[full]
  27 + }
31 28  
32 29 def self.default_search_display
33 30 'full'
... ... @@ -477,7 +474,9 @@ class Article &lt; ActiveRecord::Base
477 474 scope :no_folders, lambda {|profile|{:conditions => ['articles.type NOT IN (?)', profile.folder_types]}}
478 475 scope :galleries, :conditions => [ "articles.type IN ('Gallery')" ]
479 476 scope :images, :conditions => { :is_image => true }
  477 + scope :no_images, :conditions => { :is_image => false }
480 478 scope :text_articles, :conditions => [ 'articles.type IN (?)', text_article_types ]
  479 + scope :files, :conditions => { :type => 'UploadedFile' }
481 480 scope :with_types, lambda { |types| { :conditions => [ 'articles.type IN (?)', types ] } }
482 481  
483 482 scope :more_popular, :order => 'hits DESC'
... ... @@ -528,7 +527,10 @@ class Article &lt; ActiveRecord::Base
528 527 end
529 528  
530 529 alias :allow_delete? :allow_post_content?
531   - alias :allow_spread? :allow_post_content?
  530 +
  531 + def allow_spread?(user = nil)
  532 + user && public?
  533 + end
532 534  
533 535 def allow_create?(user)
534 536 allow_post_content?(user) || allow_publish_content?(user)
... ...
app/models/category.rb
... ... @@ -3,10 +3,10 @@ class Category &lt; ActiveRecord::Base
3 3 attr_accessible :name, :parent_id, :display_color, :display_in_menu, :image_builder, :environment, :parent
4 4  
5 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 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 3 attr_accessible :name, :environment
4 4  
5 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 11 belongs_to :environment
... ...
app/models/comment.rb
1 1 class Comment < ActiveRecord::Base
2 2  
3 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 9 attr_accessible :body, :author, :name, :email, :title, :reply_of_id, :source
... ...
app/models/communities_block.rb
... ... @@ -14,19 +14,17 @@ class CommunitiesBlock &lt; ProfileListBlock
14 14 _('This block displays the communities in which the user is a member.')
15 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 22 def footer
18 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 28 end
31 29 end
32 30  
... ...
app/models/create_enterprise.rb
... ... @@ -73,7 +73,13 @@ class CreateEnterprise &lt; Task
73 73  
74 74 # sets the associated region for the enterprise creation
75 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 84 @region = value
79 85 self.region_id = value.id
... ...
app/models/enterprise.rb
... ... @@ -4,7 +4,10 @@ class Enterprise &lt; Organization
4 4  
5 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 12 def self.type_name
10 13 _('Enterprise')
... ...
app/models/environment.rb
... ... @@ -10,6 +10,7 @@ class Environment &lt; ActiveRecord::Base
10 10 self.partial_updates = false
11 11  
12 12 has_many :tasks, :dependent => :destroy, :as => 'target'
  13 + has_many :search_terms, :as => :context
13 14  
14 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 86 end
86 87  
87 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 92 end
90 93  
91 94 # returns the available features for a Environment, in the form of a
... ... @@ -155,7 +158,8 @@ class Environment &lt; ActiveRecord::Base
155 158 'site_homepage' => _('Redirects the user to the environment homepage.'),
156 159 'user_profile_page' => _('Redirects the user to his profile page.'),
157 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 164 end
161 165 validates_inclusion_of :redirection_after_signup, :in => Environment.signup_redirection_options.keys, :allow_nil => true
... ... @@ -824,6 +828,10 @@ class Environment &lt; ActiveRecord::Base
824 828 "home-page-news/#{cache_key}-#{language}"
825 829 end
826 830  
  831 + def portal_enabled
  832 + portal_community && enabled?('use_portal_community')
  833 + end
  834 +
827 835 def notification_emails
828 836 [contact_email].select(&:present?) + admins.map(&:email)
829 837 end
... ...
app/models/invitation.rb
... ... @@ -51,7 +51,10 @@ class Invitation &lt; Task
51 51 next if contact_to_invite == _("Firstname Lastname <friend@email.com>")
52 52  
53 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 58 friend_name = match[1].strip
56 59 friend_email = match[2]
57 60 elsif match = contact_to_invite.strip.match(Noosfero::Constants::EMAIL_FORMAT)
... ... @@ -61,11 +64,15 @@ class Invitation &lt; Task
61 64 next
62 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 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 76 {:person => person, :target => user.person}
70 77 end
71 78  
... ...
app/models/invite_friend.rb
1 1 class InviteFriend < Invitation
2 2  
3 3 settings_items :group_for_person, :group_for_friend
  4 + before_create :check_for_invitation_existence
4 5  
5 6 def perform
6 7 person.add_friend(friend, group_for_person)
... ... @@ -41,4 +42,11 @@ class InviteFriend &lt; Invitation
41 42 ].join("\n\n")
42 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 52 end
... ...
app/models/invite_member.rb
... ... @@ -2,6 +2,7 @@ class InviteMember &lt; Invitation
2 2  
3 3 settings_items :community_id, :type => :integer
4 4 validates_presence_of :community_id
  5 + before_create :check_for_invitation_existence
5 6  
6 7 def community
7 8 Community.find(community_id)
... ... @@ -39,6 +40,14 @@ class InviteMember &lt; Invitation
39 40 _('%{requestor} invited you to join %{community}.') % {:requestor => requestor.name, :community => community.name}
40 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 51 def expanded_message
43 52 super.gsub /<community>/, community.name
44 53 end
... ... @@ -53,4 +62,11 @@ class InviteMember &lt; Invitation
53 62 ].join("\n\n")
54 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 72 end
... ...
app/models/license.rb
... ... @@ -3,8 +3,8 @@ class License &lt; ActiveRecord::Base
3 3 attr_accessible :name, :url
4 4  
5 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 10 belongs_to :environment
... ...
app/models/national_region.rb
1 1 class NationalRegion < ActiveRecord::Base
2 2  
3 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 8 def self.search_city(city_name, like = false, state = nil)
... ...
app/models/organization.rb
... ... @@ -3,10 +3,11 @@ class Organization &lt; Profile
3 3  
4 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 12 settings_items :closed, :type => :boolean, :default => false
12 13 def closed?
... ... @@ -176,4 +177,8 @@ class Organization &lt; Profile
176 177 self.visible = false
177 178 save!
178 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 184 end
... ...
app/models/person.rb
... ... @@ -3,10 +3,11 @@ class Person &lt; Profile
3 3  
4 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 12 def self.type_name
12 13 _('Person')
... ... @@ -21,16 +22,34 @@ class Person &lt; Profile
21 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 31 scope :by_role, lambda { |roles|
25 32 roles = [roles] unless roles.kind_of?(Array)
26 33 { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',
27 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 51 permissions += plugins.map do |plugin|
33   - plugin.has_permission?(self, permission, profile)
  52 + plugin.has_permission?(self, permission, resource)
34 53 end
35 54 permissions.include?(true)
36 55 end
... ... @@ -65,6 +84,10 @@ roles] }
65 84 has_and_belongs_to_many :acepted_forums, :class_name => 'Forum', :join_table => 'terms_forum_people'
66 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 91 scope :more_popular, :order => 'friends_count DESC'
69 92  
70 93 scope :abusers, :joins => :abuse_complaints, :conditions => ['tasks.status = 3'], :select => 'DISTINCT profiles.*'
... ... @@ -497,6 +520,15 @@ roles] }
497 520 person.notifier.reschedule_next_notification_mail
498 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 532 protected
501 533  
502 534 def followed_by?(profile)
... ...
app/models/product.rb
1 1 class Product < ActiveRecord::Base
2 2  
3 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 13 attr_accessible :name, :product_category, :highlighted, :price, :enterprise, :image_builder, :description, :available, :qualifiers, :unit_id, :discount, :inputs, :qualifiers_list
15 14  
... ...
app/models/profile.rb
... ... @@ -3,7 +3,7 @@
3 3 # which by default is the one returned by Environment:default.
4 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 8 # use for internationalizable human type names in search facets
9 9 # reimplement on subclasses
... ... @@ -12,16 +12,15 @@ class Profile &lt; ActiveRecord::Base
12 12 end
13 13  
14 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 25 def self.default_search_display
27 26 'compact'
... ... @@ -140,6 +139,17 @@ class Profile &lt; ActiveRecord::Base
140 139  
141 140 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
142 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 +
143 153 def scraps(scrap=nil)
144 154 scrap = scrap.is_a?(Scrap) ? scrap.id : scrap
145 155 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap)
... ... @@ -155,6 +165,7 @@ class Profile &lt; ActiveRecord::Base
155 165 settings_items :public_content, :type => :boolean, :default => true
156 166 settings_items :description
157 167 settings_items :fields_privacy, :type => :hash, :default => {}
  168 + settings_items :email_suggestions, :type => :boolean, :default => false
158 169  
159 170 validates_length_of :description, :maximum => 550, :allow_nil => true
160 171  
... ... @@ -219,6 +230,8 @@ class Profile &lt; ActiveRecord::Base
219 230  
220 231 has_many :abuse_complaints, :foreign_key => 'requestor_id', :dependent => :destroy
221 232  
  233 + has_many :profile_suggestions, :foreign_key => :suggestion_id, :dependent => :destroy
  234 +
222 235 def top_level_categorization
223 236 ret = {}
224 237 self.profile_categorizations.each do |c|
... ... @@ -514,6 +527,14 @@ class Profile &lt; ActiveRecord::Base
514 527 generate_url(:profile => identifier, :controller => 'profile', :action => 'index')
515 528 end
516 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 +
517 538 def generate_url(options)
518 539 url_options.merge(options)
519 540 end
... ... @@ -603,7 +624,7 @@ private :generate_url, :url_options
603 624 end
604 625  
605 626 def copy_article_tree(article, parent=nil)
606   - return if article.is_a?(RssFeed)
  627 + return if !copy_article?(article)
607 628 original_article = self.articles.find_by_name(article.name)
608 629 if original_article
609 630 num = 2
... ... @@ -623,6 +644,11 @@ private :generate_url, :url_options
623 644 end
624 645 end
625 646  
  647 + def copy_article?(article)
  648 + !article.is_a?(RssFeed) &&
  649 + !(is_template && article.slug=='welcome-page')
  650 + end
  651 +
626 652 # Adds a person as member of this Profile.
627 653 def add_member(person)
628 654 if self.has_members?
... ... @@ -632,6 +658,8 @@ private :generate_url, :url_options
632 658 self.affiliate(person, Profile::Roles.admin(environment.id)) if members.count == 0
633 659 self.affiliate(person, Profile::Roles.member(environment.id))
634 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
635 663 else
636 664 raise _("%s can't have members") % self.class.name
637 665 end
... ... @@ -769,7 +797,10 @@ private :generate_url, :url_options
769 797 end
770 798  
771 799 def admins
772   - 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)
773 804 end
774 805  
775 806 def enable_contact?
... ... @@ -967,4 +998,14 @@ private :generate_url, :url_options
967 998 def preferred_login_redirection
968 999 redirection_after_login.blank? ? environment.redirection_after_login : redirection_after_login
969 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 +
970 1011 end
... ...
app/models/profile_suggestion.rb 0 → 100644
... ... @@ -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 3 attr_accessible :name, :environment
4 4  
5 5 SEARCHABLE_FIELDS = {
6   - :name => 1,
  6 + :name => {:label => _('Name'), :weight => 1},
7 7 }
8 8  
9 9 belongs_to :environment
... ...
app/models/scrap.rb
... ... @@ -3,7 +3,7 @@ class Scrap &lt; ActiveRecord::Base
3 3 attr_accessible :content, :sender_id, :receiver_id, :scrap_id
4 4  
5 5 SEARCHABLE_FIELDS = {
6   - :content => 1,
  6 + :content => {:label => _('Content'), :weight => 1},
7 7 }
8 8 validates_presence_of :content
9 9 validates_presence_of :sender_id, :receiver_id
... ...
app/models/search_term.rb 0 → 100644
... ... @@ -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 @@
  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 @@
  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/user.rb
... ... @@ -11,6 +11,10 @@ class User &lt; ActiveRecord::Base
11 11 N_('Password confirmation')
12 12 N_('Terms accepted')
13 13  
  14 + SEARCHABLE_FIELDS = {
  15 + :email => {:label => _('Email'), :weight => 5},
  16 + }
  17 +
14 18 def self.[](login)
15 19 self.find_by_login(login)
16 20 end
... ...
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 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 3 </div>
4   -
5 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/site_info.html.erb
... ... @@ -10,9 +10,12 @@
10 10 :content => (render :partial => 'site_info', :locals => {:f => f})} %>
11 11 <% tabs << {:title => _('Terms of use'), :id => 'terms-of-use',
12 12 :content => (render :partial => 'terms_of_use', :locals => {:f => f})} %>
13   - <% tabs << {:title => _('Signup welcome text'), :id => 'signup-welcome-text',
  13 + <% #TODO I renamed the labels of signup-welcome-text and signup-welcome-page
  14 + # so texts more meaningful but I'm not rewriting every variable or reference to
  15 + # this. I leave this task to whoever thinks this is too annoying.%>
  16 + <% tabs << {:title => _('Signup welcome email'), :id => 'signup-welcome-text',
14 17 :content => (render :partial => 'signup_welcome_text', :locals => {:f => f})} %>
15   - <% tabs << {:title => _('Signup welcome message'), :id => 'signup-welcome-message',
  18 + <% tabs << {:title => _('Signup welcome page'), :id => 'signup-welcome-message',
16 19 :content => (render :partial => 'signup_welcome_screen', :locals => {:f => f}) }%>
17 20 <%= render_tabs(tabs) %>
18 21 <% button_bar do %>
... ...
app/views/blocks/communities.html.erb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<% if owner.kind_of?(Profile) %>
  2 + <%= link_to s_('communities|View all'), {:profile => owner.identifier, :controller => 'profile', :action => 'communities'}, :class => 'view-all' %>
  3 +<% elsif owner.kind_of?(Environment) %>
  4 + <%= link_to s_('communities|View all'), {:controller => 'search', :action => 'communities'}, :class => 'view-all' %>
  5 +<% end %>
  6 +
  7 +<% if user && user == profile && suggestions && !suggestions.empty? %>
  8 + <div class='suggestions-block common-profile-list-block'>
  9 + <h4 class='block-subtitle'><%= _('Some suggestions for you') %></h4>
  10 + <div class='profiles-suggestions'>
  11 + <%= render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => suggestions, :collection => :communities_suggestions, :per_page => 3 } %>
  12 + </div>
  13 + <div class='more-suggestions'>
  14 + <%= link_to _('See all suggestions'), profile.communities_suggestions_url %>
  15 + </div>
  16 + </div>
  17 +<% end %>
... ...
app/views/cms/_drag_and_drop_note.html.erb
1 1 <p>
2 2 <em>
3 3 <%= _('Drag images to add them to the text.') %>
4   - <%= _('Drag file names to the text to add links.') %>
  4 + <%= _('Click on file names to add links to the text.') %>
5 5 </em>
6 6 </p>
... ...
app/views/cms/_media_new_folder.html.erb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +<%= form_tag({:action => 'new', :profile => profile.identifier}, :id => 'new-folder-dialog', :title => _('Create new folder'), :style => 'display: none;') do %>
  2 + <%= select_profile_folder(
  3 + _('Choose parent folder:'),
  4 + :parent_id, profile, default_folder, {}, {},
  5 + "type='Folder' or type='Gallery'")
  6 + %>
  7 +
  8 + <%= labelled_radio_button _('Gallery'), :folder_type, 'Gallery', true %>
  9 + <%= labelled_radio_button _('Folder'), :folder_type, 'Folder', false %>
  10 +
  11 + <%= labelled_form_field _('Name:'), text_field_tag(:new_folder, nil, 'data-url' => url_for({:action => 'new', :profile => profile.identifier})) %>
  12 + <% button_bar do %>
  13 + <%= submit_button(:newfolder, _('Create')) %>
  14 + <% end %>
  15 +<% end %>
... ...
app/views/cms/_published_media_items.html.erb 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +<% file_types.each do |key, header| %>
  2 + <% display = @recent_files[key].present? ? '' : 'none' %>
  3 + <div class='<%= key.to_s %>' style='display: <%= display%>;'>
  4 + <div class='section-title'>
  5 + <h3><%= header %></h3>
  6 + <% if @recent_files[key].total_pages > 1 %>
  7 + <%= link_to(_('View all'), {:controller => 'cms', :action => 'view_all_media', :profile => profile.identifier, :key => key}, :class => 'view-all colorbox', 'data-key' => key) %>
  8 + <% end %>
  9 + </div>
  10 + <%= render :partial => "cms/media_panel/list_published_media_items", :locals => { key: key, show_pagination_links: false } %>
  11 + </div>
  12 +<% end %>
... ...
app/views/cms/_text_editor_sidebar.html.erb
  1 +<% default_folder = content_id_to_str default_folder_for_image_upload(profile) %>
  2 +
1 3 <div class='text-editor-sidebar'>
2 4 <span class='button-add' data-value='<%= _('Add to the text') %>'></span>
3 5 <span class='button-zoom' data-value='<%= _('Zoom in') %>'></span>
4 6 <span class='button-close' data-value='<%= _('Close') %>'></span>
5 7  
  8 + <div class='header'><strong><%= _('Insert media') %></strong><%= button('vertical-toggle', _('Show/Hide'), '#') %></div>
  9 +
6 10 <%= render(:partial => 'textile_quick_reference') if @article.is_a?(TextileArticle) %>
7 11 <div class='text-editor-sidebar-box' id='media-upload-box'>
8   - <div class='header'><strong><%= _('Insert media') %></strong></div>
9 12 <div id='media-upload-form'>
10 13 <%= form_tag({ :action => 'media_upload' }, :multipart => true) do %>
11 14 <div class='formfield'>
12   - <% default_folder = content_id_to_str default_folder_for_image_upload(profile) %>
13 15 <%= select_profile_folder(
14 16 _('Choose folder to upload files:'),
15 17 :parent_id, profile, default_folder, {}, {},
16 18 "type='Folder' or type='Gallery'"
17 19 ) %>
18 20 </div>
19   - <p><%= file_field_tag('file1') %></p>
20   - <p><%= file_field_tag('file2') %></p>
21   - <p><%= file_field_tag('file3') %></p>
22   - <% button_bar do %>
23   - <%= submit_button(:save, _('Upload')) %>
24   - <% end %>
  21 + <%= button(:newfolder, _('New folder'), '#', :id => 'new-folder-button') %>
  22 + <p><%= file_field_tag('file', :multiple => true) %></p>
25 23 <% end %>
26 24 </div>
27   - <div id='media-upload-results' style='display: none'>
28   - <%= render :partial => 'drag_and_drop_note' %>
29   - <div class='items'>
30   - </div>
31   - <p><%= link_to(_('Upload more files ...'), '#', :id => 'media-upload-more-files')%></p>
  25 + <div class='hide-and-show-uploads'>
  26 + <%= link_to(_('Hide all uploads'), nil, :id => 'hide-uploads', :style => 'display: none;', 'data-bootstraped' => false) %>
  27 + <%= link_to(_('Show all uploads'), nil, :id => 'show-uploads', :style => 'display: none;') %>
32 28 </div>
33 29 </div>
34   - <div id='media-search-box' class='text-editor-sidebar-box'>
35   - <div class='header'><strong><%= _('Media search') %></strong></div>
36   - <p>
37   - <%= form_tag({ :action => 'search' }) do %>
38   - <span class='formfield'>
39   - <input name='q' type='text' id='media-search-query' style='width: 250px;'/>
40   - </span>
41   - <%= submit_button :search, _('Search'), :id => 'media-search-button' %>
42   - <% end %>
43   - </p>
44   - <div id='media-search-results' style='display: none'>
45   - <%= render :partial => 'drag_and_drop_note' %>
46   - <div class='items'>
47   - </div>
  30 +
  31 + <div id='published-media' class='text-editor-sidebar-box' data-url='<%= url_for({:controller => 'cms', :action => 'published_media_items', :profile => profile.identifier}) %>'>
  32 + <%= select_profile_folder(nil, :parent_id, profile, 'recent-media', {}, {},
  33 + "type='Folder' or type='Gallery'", {:root_label => _('Recent media')}) %>
  34 + <%= labelled_form_field _('Search'), text_field_tag('q') %>
  35 + <%= render :partial => 'drag_and_drop_note' %>
  36 + <div class='items'>
  37 + <%= render :partial => 'published_media_items' %>
48 38 </div>
49 39 </div>
50 40 </div>
51 41  
  42 +<script id="template-upload" type="text/x-tmpl">
  43 + <div id="file-{%= o.id %}" class="upload" title="{%= o.name %}">
  44 + <div class="file-name">{%=o.name%}</div>
  45 + <div class="percentage"></div>
  46 + <div class="progress"><div class="bar" style="width: 0%;"></div></div>
  47 + </div>
  48 +</script>
52 49  
  50 +<%= render :partial => 'media_new_folder', :locals => {:default_folder => default_folder} %>
  51 +<%= javascript_include_tag 'jquery.fileupload.js', 'tmpl.js', 'media-panel.js' %>
... ...
app/views/cms/_textile_quick_reference.html.erb
... ... @@ -15,6 +15,7 @@
15 15 <pre># <%= _('first item') %>
16 16 # <%= _('second item') %></pre>
17 17 <p><%= h(_('For code, use HTML tags <pre> and <code>, and indent the code inside them:')) %>
  18 + </p>
18 19 <pre>
19 20 &lt;pre&gt;
20 21 &lt;code&gt;
... ...
app/views/cms/edit.html.erb
1 1 <%= error_messages_for 'article' %>
2 2  
3   -<div class='<%= (environment.enabled?('media_panel') ? 'with_media_panel' : 'no_media_panel') %>'>
  3 +<% show_media_panel = environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage].any?{|klass| @article.kind_of?(klass)} %>
  4 +
  5 +<div class='<%= (show_media_panel ? 'with_media_panel' : 'no_media_panel') %>'>
4 6 <%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %>
5 7  
6 8 <%= hidden_field_tag("type", @type) if @type %>
... ... @@ -66,7 +68,7 @@
66 68 <% end %>
67 69 </div>
68 70  
69   -<% if environment.enabled?('media_panel') && [TinyMceArticle, TextileArticle, Event, EnterpriseHomepage].any?{|klass| @article.kind_of?(klass)} %>
  71 +<% if show_media_panel %>
70 72 <%= render :partial => 'text_editor_sidebar' %>
71 73 <% end %>
72 74  
... ...
app/views/cms/media_panel/_generic.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div class="item file <%= icon_for_article(@file) %>" data-item="div">
  2 + <div>
  3 + <a class="add-to-text" href="<%= url_for(@file.url) %>" title="<%= @file.title %>"><%= @file.title %></a>
  4 + </div>
  5 +</div>
... ...
app/views/cms/media_panel/_image.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<div class="item image" data-item="span" title="<%= @file.name %>">
  2 + <span>
  3 + <img src="<%= @file.public_filename(:uploaded) %>"/>
  4 + </span>
  5 + <div class="controls image-controls">
  6 + <a class="button icon-add add-to-text" href="#"><span><%= _('Add to the text') %></span></a>
  7 + <a class="button icon-zoom zoom" href="#" title="<%= _('Zoom in') %>"><span><%= _('Zoom in') %></span></a>
  8 + </div>
  9 +</div>
... ...
app/views/cms/media_panel/_list_published_media_items.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<% @recent_files[key].each do |file| %>
  2 + <% @file = file %>
  3 + <%= render :partial => "cms/media_panel/#{key.to_s.singularize}" %>
  4 +<% end %>
  5 +<%= pagination_links @recent_files[key] if show_pagination_links %>
... ...
app/views/cms/media_upload.js.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +loadPublishedMedia();
... ...
app/views/cms/publish.html.erb
1   -<h1><%= _('Select the groups where you want to publish your article') %></h1>
  1 +<div class="select-publish-target">
  2 +
  3 +<h2><%= _('Where do you want to publish this article?') %></h2>
2 4  
3 5 <% if !@failed.blank? %>
4 6 <div class="errorExplanation" id="errorExplanation">
... ... @@ -14,15 +16,54 @@
14 16 </div>
15 17 <% end %>
16 18  
17   -<%= form_tag do%>
18   - <%= hidden_field_tag :back_to, @back_to %>
19   - <% @groups.each do |group| %>
20   - <%= labelled_check_box group.name, "marked_groups[#{group.id}][group_id]", group.id, @marked_groups.include?(group) %><br />
21   - <%= labelled_text_field _('Title') + ': ', "marked_groups[#{group.id}][name]", @article.name, :style => 'width: 100%' %>
22   - <hr />
  19 +<ul class='publish-targets'>
  20 + <% if profile != user %>
  21 + <li onmouseover="javascript: jQuery(this).addClass('mouseover')" onmouseout="jQuery(this).removeClass('mouseover')">
  22 + <strong><%= _("Publish this article on your profile") %></strong>
  23 + <div class='description'><%= _('You can publish this article on your profile where your friends and followers will see.') %></div>
  24 + <%= form_tag do %>
  25 + <%= hidden_field_tag :back_to, @back_to %>
  26 + <%= labelled_form_field _('Title'), text_field_tag('name', @article.name) %>
  27 +
  28 + <% button_bar do %>
  29 + <%= submit_button 'spread', _('Spread this') %>
  30 + <% end %>
  31 + <% end %>
  32 + </li>
23 33 <% end %>
24 34  
25   - <% button_bar do %>
26   - <%= submit_button 'spread', _('Spread this'), :cancel => @back_to %>
  35 + <% if user.communities.present? %>
  36 + <li onmouseover="javascript: jQuery(this).addClass('mouseover')" onmouseout="jQuery(this).removeClass('mouseover')">
  37 + <strong><%= _("Publish this article on communities you are part of") %></strong>
  38 + <div class='description'><%= _('You can submit this article to one or more communities you are a member of, just search for the community below.') %></div>
  39 + <%= form_tag :action => 'publish_on_communities', :id => @article.id do %>
  40 + <%= hidden_field_tag :back_to, @back_to %>
  41 + <% search_action = url_for(:action => 'search_communities_to_publish') %>
  42 + <%= token_input_field_tag(:q, 'search-communities-to-publish', search_action, { :hint_text => _('Type in a search for your community'), :zindex => 10000, :focus => false }) %>
  43 + <%= labelled_form_field _('Title'), text_field_tag('name', @article.name) %>
  44 + <% button_bar do %>
  45 + <%= submit_button 'spread', _('Spread this') %>
  46 + <% end %>
  47 + <% end %>
  48 + </li>
27 49 <% end %>
28   -<% end %>
  50 +
  51 +
  52 + <% if environment.portal_enabled %>
  53 + <li onmouseover="javascript: jQuery(this).addClass('mouseover')" onmouseout="jQuery(this).removeClass('mouseover')">
  54 + <strong><%= _("Publish your article on portal community") %></strong>
  55 + <div class='description'><%= _('You can suggest this article to the portal community, where it can show up on the homepage.') %></div>
  56 +
  57 + <%= form_tag :action => 'publish_on_portal_community', :id => @article.id do %>
  58 + <%= hidden_field_tag :back_to, @back_to %>
  59 + <%= labelled_form_field _('Title'), text_field_tag('name', @article.name) %>
  60 +
  61 + <% button_bar do %>
  62 + <%= submit_button 'spread', _('Spread this') %>
  63 + <% end %>
  64 + <% end %>
  65 + </li>
  66 + <% end %>
  67 +</ul>
  68 +
  69 +</div>
... ...
app/views/cms/view.html.erb
... ... @@ -2,7 +2,7 @@
2 2 <%= _('Content management') %>
3 3 </h1>
4 4  
5   -<% if user.can_change_homepage? && !remove_content_button(:home) %>
  5 +<% if user.can_change_homepage? && !remove_content_button(:home, profile.home_page) %>
6 6 <div class="cms-homepage">
7 7 <%= _('Profile homepage:') %>
8 8 <% if profile.home_page %>
... ... @@ -66,17 +66,17 @@
66 66 <%= short_description %>
67 67 </td>
68 68 <td class="article-controls">
69   - <%= expirable_button article, :edit, _('Edit'), {:action => 'edit', :id => article.id} if !remove_content_button(:edit) %>
  69 + <%= expirable_button article, :edit, _('Edit'), {:action => 'edit', :id => article.id} if !remove_content_button(:edit, article) %>
70 70 <%= button_without_text :eyes, _('Public view'), article.view_url %>
71   - <%= display_spread_button(profile, article) unless article.folder? || remove_content_button(:spread)%>
72   - <% if user.can_change_homepage? && !remove_content_button(:home) %>
  71 + <%= display_spread_button(article) unless remove_content_button(:spread, article) %>
  72 + <% if user.can_change_homepage? && !remove_content_button(:home, article) %>
73 73 <% if profile.home_page != article %>
74 74 <%= expirable_button article, :home, _('Use as homepage'), { :action => 'set_home_page', :id => article.id }, :method => :post %>
75 75 <% else %>
76 76 <%= button_without_text(:'home-not', _('Reset homepage'), { :action => 'set_home_page', :id => nil }, :method => :post) %>
77 77 <% end %>
78 78 <% end %>
79   - <%= display_delete_button(article) if !remove_content_button(:delete) %>
  79 + <%= display_delete_button(article) if !remove_content_button(:delete, article) %>
80 80 </td>
81 81 </tr>
82 82 <% end %>
... ...
app/views/cms/view_all_media.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<h1><%= file_types[@key] %></h1>
  2 +
  3 +<div class='view-all-media view-all-<%= @key %>'>
  4 + <%= render :partial => "cms/media_panel/list_published_media_items", :locals => { key: @key, show_pagination_links: true } %>
  5 +</div>
  6 +
  7 +<%= javascript_include_tag 'media-panel.js' %>
... ...
app/views/cms/view_all_media.js.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +jQuery('.view-all-media').html('<%= escape_javascript(render :partial => "cms/media_panel/list_published_media_items", :locals => { key: @key, show_pagination_links: true }) %>');
... ...
app/views/content_viewer/_article_toolbar.html.erb
... ... @@ -2,46 +2,40 @@
2 2 <div id="article-actions">
3 3  
4 4  
5   - <% if @page.allow_edit?(user) && !remove_content_button(:edit) %>
  5 + <% if @page.allow_edit?(user) && !remove_content_button(:edit, @page) %>
6 6 <% content = content_tag('span', label_for_edit_article(@page)) %>
7 7 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id }) %>
8 8 <%= expirable_button @page, :edit, content, url %>
9 9 <% end %>
10 10  
11   - <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete)%>
  11 + <% if @page != profile.home_page && !@page.has_posts? && @page.allow_delete?(user) && !remove_content_button(:delete, @page)%>
12 12 <% content = content_tag( 'span', _('Delete') ) %>
13 13 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'destroy', :id => @page.id}) %>
14 14 <% options = {:method => :post, :confirm => delete_article_message(@page)} %>
15 15 <%= expirable_button @page, :delete, content, url, options %>
16 16 <% end %>
17 17  
18   - <% if !@page.folder? && @page.allow_spread?(user) && !remove_content_button(:spread) %>
19   - <% content = content_tag( 'span', _('Spread this') ) %>
20   - <% url = nil %>
21   - <% if profile.kind_of?(Person) %>
22   - <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page.id }) %>
23   - <% elsif profile.kind_of?(Community) && environment.portal_community %>
24   - <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish_on_portal_community', :id => @page.id }) %>
25   - <% end %>
26   - <%= expirable_button @page, :spread, content, url if url %>
  18 + <% if @page.allow_spread?(user) && !remove_content_button(:spread, @page) %>
  19 + <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'publish', :id => @page.id }) %>
  20 + <%= expirable_button @page, :spread, content_tag( 'span', _('Spread this') ), url, {:class => 'colorbox'} if url %>
27 21 <% end %>
28 22  
29 23 <% if !@page.gallery? && (@page.allow_create?(user) || (@page.parent && @page.parent.allow_create?(user))) %>
30   - <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale) %>
  24 + <% if @page.translatable? && !@page.native_translation.language.blank? && !remove_content_button(:locale, @page) %>
31 25 <% content = _('Add translation') %>
32 26 <% parent_id = (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)) %>
33 27 <% url = profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => parent_id, :type => @page.type, :article => { :translation_of_id => @page.native_translation.id })%>
34 28 <%= expirable_button @page, :locale, content, url %>
35 29 <% end %>
36 30  
37   - <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) unless remove_content_button(:new) %>
  31 + <%= colorbox_button(:new, label_for_new_article(@page), profile.admin_url.merge(:controller => 'cms', :action => 'new', :parent_id => (@page.folder? ? @page : (@page.parent.nil? ? nil : @page.parent)))) unless remove_content_button(:new, @page) %>
38 32 <% end %>
39 33  
40 34 <% if @page.accept_uploads? && @page.allow_create?(user) %>
41   - <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:upload)%>
  35 + <%= button('upload-file', _('Upload files'), profile.admin_url.merge(:controller => 'cms', :action => 'upload_files', :parent_id => (@page.folder? ? @page : @page.parent))) unless remove_content_button(:upload, @page)%>
42 36 <% end %>
43 37  
44   - <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest) %>
  38 + <% if !@page.allow_create?(user) && profile.community? && (@page.blog? || @page.parent && @page.parent.blog?) && !remove_content_button(:suggest, @page) %>
45 39 <% content = content_tag( 'span', _('Suggest an article') ) %>
46 40 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'suggest_an_article'}) %>
47 41 <% options = {:id => 'suggest-article-link'} %>
... ...
app/views/content_viewer/versioned_article.html.erb
... ... @@ -7,7 +7,7 @@
7 7 <div id="article-actions">
8 8 <%= button(:clock, _('All versions'), {:controller => 'content_viewer', :profile => profile.identifier, :action => 'article_versions'}, :id => 'article-versions-link') %>
9 9  
10   - <% if @page.allow_edit?(user) && !remove_content_button(:undo) %>
  10 + <% if @page.allow_edit?(user) && !remove_content_button(:undo, @page) %>
11 11 <% content = content_tag('span', _('Revert to this version')) %>
12 12 <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id, :version => @version }) %>
13 13 <%= expirable_button @page, :undo, content, url, :id => 'article-revert-version-link' %>
... ...
app/views/enterprise_registration/creation.html.erb
1 1 <h1><%= _('Enterprise registration completed') %></h1>
  2 +<p><%= _("Your enterprise (%s) was successfully registered.") % @enterprise.name %></p>
  3 +<p><%= link_to _('You can manage your enterprise now.'), @enterprise.admin_url %></p>
2 4  
3   -<p>
4   -<%= _("Your enterprise (%s) was successfully registered.") % @enterprise.name %>
5   -</p>
  5 +<%= render :partial => 'shared/template_welcome_page', :locals => {:template => @enterprise.template, :header => _("What can I do with a %s?")} %>
  6 +
  7 +<% button_bar do %>
  8 + <%= button :back, _('Back'), {:controller => 'memberships', :action => 'index', :profile => user.identifier} %>
  9 +<% end %>
6 10  
7   -<p>
8   -<%= link_to _('You can manage your enterprise now.'), @enterprise.admin_url %>
9   -</p>
... ...
app/views/friends/_profile_list.html.erb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +<ul class="profile-list">
  2 + <% profiles.each do |profile| %>
  3 + <li>
  4 + <%= link_to_profile profile_image(profile) + '<br/>' + profile.short_name,
  5 + profile.identifier, :class => 'profile-link' %>
  6 + <div class="controll">
  7 + <%= button_without_text :remove, content_tag('span',_('remove')),
  8 + { :action => 'remove', :id => profile.id },
  9 + :title => _('remove') %>
  10 + <%= button_without_text 'menu-mail', content_tag('span',_('contact')),
  11 + profile.url.merge(:controller => 'contact', :action => 'new', :profile => profile.identifier),
  12 + :title => _('contact') %>
  13 + </div><!-- end class="controll" -->
  14 + </li>
  15 + <% end %>
  16 +</ul>
... ...
app/views/friends/connections.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<h1><%= _("Connections with %s") % @suggestion.suggestion.name %></h1>
  2 +
  3 +<% button_bar do %>
  4 + <%= button(:back, _('Go to friends list'), :controller => 'friends') %>
  5 +<% end %>
  6 +
  7 +<%= render :partial => 'shared/profile_connections', :locals => { :suggestion => @suggestion, :tags => @tags, :profiles => @profiles } %>
... ...
app/views/friends/index.html.erb
... ... @@ -10,46 +10,28 @@
10 10 <%= link_to _('Do you want to see other people in this environment?'), :controller => 'search', :action => 'assets', :asset => 'people' %>
11 11 </em>
12 12 </p>
13   - <% else %>
14   - <% button_bar do %>
15   - <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %>
16   - <%= button(:search, _('Find people'), :controller => 'search', :action => 'assets', :asset => 'people') %>
17   - <% unless @plugins.dispatch(:remove_invite_friends_button).include?(true) %>
18   - <%= button(:search, _('Invite people from my e-mail contacts'), :controller => 'invite', :action => 'select_address_book') %>
19   - <% end %>
20   - <% end %>
21 13 <% end %>
22 14  
23   - <ul class="profile-list">
24   - <% @friends.each do |friend| %>
25   - <li>
26   - <%= link_to_profile profile_image(friend) + '<br/>' + friend.short_name,
27   - friend.identifier, :class => 'profile-link' %>
28   - <div class="controll">
29   - <%= link_to content_tag('span',_('remove')),
30   - { :action => 'remove', :id => friend.id },
31   - :class => 'button icon-remove',
32   - :title => _('remove') %>
33   - <%= link_to content_tag('span',_('contact')),
34   - friend.url.merge(:controller => 'contact', :action => 'new', :profile => friend.identifier),
35   - :class => 'button icon-menu-mail',
36   - :title => _('contact') %>
37   - </div><!-- end class="controll" -->
38   - </li>
39   - <% end %>
40   - </ul>
41   - <div id='pagination-friends'>
42   - <%= pagination_links @friends, :param_name => 'npage' %>
43   - </div>
44   -
45 15 <% button_bar do %>
46 16 <%= button(:back, _('Back to control panel'), :controller => 'profile_editor') %>
47 17 <%= button(:search, _('Find people'), :controller => 'search', :action => 'assets', :asset => 'people') %>
48 18 <% unless @plugins.dispatch(:remove_invite_friends_button).include?(true) %>
49   - <%= button(:search, _('Invite people from my e-mail contacts'), :controller => 'invite', :action => 'select_address_book') %>
  19 + <%= button(:person, _('Invite people'), :controller => 'invite', :action => 'invite_friends') %>
50 20 <% end %>
51 21 <% end %>
  22 +
  23 + <%= render :partial => 'profile_list', :locals => { :profiles => @friends } %>
  24 +
  25 + <br style="clear:both" />
  26 + <%= pagination_links @friends, :param_name => 'npage' %>
52 27 <% end %>
53 28  
54   -</div><!-- end id="manage_friends" -->
  29 +<% unless @suggestions.empty? %>
  30 + <br style="clear:both" />
  31 + <h2><%= _("Friends suggestions") %></h2>
  32 + <div class="profiles-suggestions">
  33 + <%= render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :friends_suggestions, :per_page => 12 } %>
  34 + </div>
  35 +<% end %>
55 36  
  37 +</div><!-- end id="manage_friends" -->
... ...
app/views/friends/remove_suggestion.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div id="remove_suggestion">
  2 + <h1><%= _('Removing suggestion for friend: %s') % @person.name %></h1>
  3 +
  4 + <%= render :partial => 'shared/remove_suggestion', :locals => { :suggestion => @person } %>
  5 +</div>
... ...
app/views/friends/suggest.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<h1><%= _("Friends suggestions for %s") % profile.name %></h1>
  2 +
  3 +<% button_bar do %>
  4 + <%= button(:back, _('Go to friends list'), :controller => 'friends') %>
  5 +<% end %>
  6 +
  7 +<div class="profiles-suggestions">
  8 + <%= render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :friends_suggestions, :per_page => 12 } %>
  9 +</div>
... ...
app/views/home/welcome.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<% default_message = defined?(default_message) ? default_message : false %>
  2 +
  3 +<div id='thanks-for-signing'>
  4 + <h1><%= _("Welcome to %s!") % environment.name %></h1>
  5 + <% if environment.has_custom_welcome_screen? && !default_message %>
  6 + <%= environment.settings[:signup_welcome_screen_body].html_safe %>
  7 + <% else %>
  8 + <h3><%= _("Thanks for signing up, we're thrilled to have you on our social network!") %></h3>
  9 + <% if @display_confirmation_tips %>
  10 + <p><%= _("Firstly, some tips for getting started:") %></p>
  11 + <h4><%= _("Confirm your account!") %></h4>
  12 + <p><%= _("You should receive a welcome email from us shortly. Please take a second to follow the link within to confirm your account.") %></p>
  13 + <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>
  14 + <% else %>
  15 + <h4><%= _("Wait for admin approvement!") %></h4>
  16 + <p><%= _("The administrators will evaluate your signup request for approvement.") %></p>
  17 + <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>
  18 + <% end %>
  19 + <h4><%= _("What to do next?") %></h4>
  20 + <p><%= _("Access your %s and see your face on the network!") %
  21 + (user.present? ? link_to(_('Profile'), {:controller => 'profile', :profile => user.identifier}, :target => '_blank') : 'Profile') %>
  22 + <%= _("You can also explore your %s to customize your profile. Here are some %s on what you can do there.") %
  23 + [user.present? ? link_to(_('Control Panel'), {:controller => 'profile_editor', :profile => user.identifier}, :target => '_blank') : 'Control Panel',
  24 + link_to(_('tips'), {:controller => 'doc', :action => 'topic', :section => 'user', :topic => 'editing-person-info'}, :target => '_blank')] %></p>
  25 + <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!") % link_to(_('Invite and find'), {:controller => 'doc', :action => 'topic', :section => 'user', :topic => 'invite-contacts'}, :target => '_blank') %></p>
  26 + <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>
  27 + <p><%= _("Start exploring and have fun!") %></p>
  28 + <% end %>
  29 + <%= render :partial => 'shared/template_welcome_page', :locals => {:template => @person_template, :header => _("What can I do as a %s?")} %>
  30 +</div>
... ...
app/views/invite/_personalize_invitation_mail.html.erb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +<br/>
  2 +
  3 +<%= link_to ('Personalize invitation mail'), nil, :onclick => "jQuery('#invitation-mail_template').show(); return false;" %>
  4 +
  5 +<div id='invitation-mail_template' style='display:none'>
  6 + <%= h _("Now enter an invitation message. You must keep the <url> code in your invitation text. When your friends receive the invitation e-mail, <url> will be replaced by a link that they need to click to activate their account. <user> and <friend> codes will be replaced by your name and friend name, but they are optional.") %>
  7 + <%= labelled_form_field(_('Invitation text:'), text_area_tag(:mail_template, mail_template, :cols => 72, :rows => 8)) %>
  8 +</div>
... ...
app/views/invite/_select_address_book.html.erb 0 → 100644
... ... @@ -0,0 +1,41 @@
  1 +<% header ||='h2' %>
  2 +<<%= header %>><%= _('Step 1 of 2: Select address book') %></<%= header %>>
  3 +
  4 +<%= form_tag do %>
  5 +
  6 + <%= [
  7 + radio_button_tag(:import_from, "manual", @import_from == "manual", :onclick => 'hide_invite_friend_login_password()') + content_tag('label', _('Manually (empty field)'), :for => "import_from_manual"),
  8 + radio_button_tag(:import_from, "gmail", @import_from == "gmail", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Gmail', :for => 'import_from_gmail'),
  9 + radio_button_tag(:import_from, "yahoo", @import_from == "yahoo", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Yahoo', :for => "import_from_yahoo"),
  10 + radio_button_tag(:import_from, "hotmail", @import_from == "hotmail", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Hotmail', :for => "import_from_hotmail")
  11 + ].join("\n<br/>\n") %>
  12 +
  13 + <script type="text/javascript">
  14 + function hide_invite_friend_login_password() {
  15 + $('invite-friends-login-password').hide();
  16 + }
  17 + function show_invite_friend_login_password(option) {
  18 + if (option == 'hotmail') {
  19 + $('hotmail_username_tip').show();
  20 + } else {
  21 + $('hotmail_username_tip').hide();
  22 + }
  23 + $('invite-friends-login-password').show();
  24 + $('login').focus();
  25 + }
  26 + </script>
  27 + <div id='invite-friends-login-password' <%= "style='display: none;'" if (@import_from == 'manual') %>>
  28 + <div id='hotmail_username_tip'>
  29 + <%= ui_icon('ui-icon-alert') %>
  30 + <%= _('Please type your username in the format yourname@example.com') %>
  31 + </div>
  32 +
  33 + <%= labelled_form_field(_("Username") + ":", text_field_tag(:login, @login)) %>
  34 + <%= labelled_form_field(_("Password") + ":", password_field_tag(:password)) %>
  35 + </div>
  36 +
  37 + <% button_bar do %>
  38 + <%= submit_button(:forward, _("Next")) %>
  39 + <% end %>
  40 + <p><%= _("We won't store your password or contact anyone without your permission.") %></p>
  41 +<% end %>
... ...
app/views/invite/invite_friends.html.erb 0 → 100644
... ... @@ -0,0 +1,42 @@
  1 +<% if profile.person? %>
  2 + <h1><%= _('Ask for friendship') %></h1>
  3 + <% description = _('You can search for user profiles and ask them to become your friends.') %>
  4 +<% else %>
  5 + <h1><%= _('Invite people to join') %></h1>
  6 + <% description = _('You can search for user profiles and invite them to join this group.') %>
  7 +<% end %>
  8 +
  9 +<h3>
  10 + <%= _("Choose person by:") %>
  11 +</h3>
  12 +
  13 +<p>
  14 + <%= labelled_radio_button _("Name"), :invite_friend_by, 1, true, :id => "invite_friend_by_name", :class => "invite_friend_by" %>
  15 + <%= labelled_radio_button _("Email"), :invite_friend_by, 2, false, :id => "invite_friend_by_email", :class => "invite_friend_by" %>
  16 +</p>
  17 +
  18 +<div class='invite_by_name'>
  19 + <p><%= description %></p>
  20 + <%= form_tag :action => 'invite_registered_friend' do %>
  21 + <% search_action = url_for(:action => 'search') %>
  22 + <%= token_input_field_tag(
  23 + :q, 'search-people', search_action,
  24 + { :hint_text => _('Type in the person\'s %{search_fields}') % {:search_fields => @search_fields},
  25 + :focus => false }) %>
  26 +
  27 + <% button_bar do %>
  28 + <%= submit_button('save', _('Invite'))%>
  29 + <%= button('cancel', _('Cancel'), profile.url)%>
  30 + <% end %>
  31 + <% end %>
  32 +</div>
  33 +
  34 +<div class='invite_by_email' style="display: none;">
  35 + <h2><%= _('Invite people from my e-mail contacts') %></h2>
  36 + <% header = 'h3' %>
  37 +
  38 +<%= render :partial => 'invite/select_address_book', :locals => {:header => header} %>
  39 +</div>
  40 +
  41 +<div id="loadingScreen"></div>
  42 +<%= javascript_include_tag 'invite' %>
... ...
app/views/invite/select_address_book.html.erb
... ... @@ -1,51 +0,0 @@
1   -<% if profile.person? %>
2   - <h1><%= _('Invite your friends') %></h1>
3   -<% else %>
4   - <h1><%= _('Invite your friends to join %s') % profile.name %></h1>
5   -<% end %>
6   -
7   -<h2><%= _('Step 1 of 2: Select address book') %></h2>
8   -
9   -<%= form_tag do %>
10   -
11   - <%= [
12   - radio_button_tag(:import_from, "manual", @import_from == "manual", :onclick => 'hide_invite_friend_login_password()') + content_tag('label', _('Manually (empty field)'), :for => "import_from_manual"),
13   - radio_button_tag(:import_from, "gmail", @import_from == "gmail", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Gmail', :for => 'import_from_gmail'),
14   - radio_button_tag(:import_from, "yahoo", @import_from == "yahoo", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Yahoo', :for => "import_from_yahoo"),
15   - radio_button_tag(:import_from, "hotmail", @import_from == "hotmail", :onclick => 'show_invite_friend_login_password(this.value)') + content_tag('label', 'Hotmail', :for => "import_from_hotmail")
16   - ].join("\n<br/>\n") %>
17   -
18   - <script type="text/javascript">
19   - function hide_invite_friend_login_password() {
20   - $('invite-friends-login-password').hide();
21   - }
22   - function show_invite_friend_login_password(option) {
23   - if (option == 'hotmail') {
24   - $('hotmail_username_tip').show();
25   - } else {
26   - $('hotmail_username_tip').hide();
27   - }
28   - $('invite-friends-login-password').show();
29   - $('login').focus();
30   - }
31   - </script>
32   - <div id='invite-friends-login-password' <%= "style='display: none;'" if (@import_from == 'manual') %>>
33   - <div id='hotmail_username_tip'>
34   - <%= ui_icon('ui-icon-alert') %>
35   - <%= _('Please type your username in the format yourname@example.com') %>
36   - </div>
37   -
38   - <%= labelled_form_field(_("Username") + ":", text_field_tag(:login, @login)) %>
39   - <%= labelled_form_field(_("Password") + ":", password_field_tag(:password)) %>
40   - </div>
41   -
42   - <% button_bar do %>
43   - <%= submit_button(:forward, _("Next")) %>
44   - <% end %>
45   - <p><%= _("We won't store your password or contact anyone without your permission.") %></p>
46   -<% end %>
47   -
48   -<div id="loadingScreen"></div>
49   -
50   -
51   -
app/views/invite/select_friends.html.erb
1 1 <%= render :partial => 'invite/dialog_wait_loading', :locals => {:contact_list => @contact_list.id } if @import_from != 'manual' %>
2 2  
3 3 <% if profile.person? %>
4   - <h1><%= _('Invite your friends') %></h1>
  4 + <h1><%= _('Invite people') %></h1>
5 5 <% else %>
6   - <h1><%= _('Invite your friends to join %s') % profile.name %></h1>
  6 + <h1><%= _('Invite people to join') %></h1>
7 7 <% end %>
8 8  
9 9  
10   -<h2><%= _('Step 2 of 2: Selecting Friends') %></h2>
  10 +<h2><%= _('Step 2 of 2: Selecting People') %></h2>
11 11  
12   -<%= button(:back, _('Back'), { :action => 'select_address_book' }, :id => 'invitation_back_button') %>
  12 +<%= button(:back, _('Back'), { :action => 'invite_friends' }, :id => 'invitation_back_button') %>
13 13  
14 14 <p>
15   -<%= _('Indicate which friends you want to invite.') %>
  15 +<%= _('Indicate which people you want to invite.') %>
16 16 </p>
17 17  
18 18 <%= form_tag do %>
... ... @@ -30,16 +30,9 @@
30 30 </div>
31 31 <% end -%>
32 32  
33   - <br/>
34   -
35   - <%= link_to ('Personalize invitation mail'), nil, :onclick => "jQuery('#invitation-mail_template').show(); return false;" %>
36   -
37   - <div id='invitation-mail_template' style='display:none'>
38   - <%= h _("Now enter an invitation message. You must keep the <url> code in your invitation text. When your friends receive the invitation e-mail, <url> will be replaced by a link that they need to click to activate their account. <user> and <friend> codes will be replaced by your name and friend name, but they are optional.") %>
39   - <%= labelled_form_field(_('Invitation text:'), text_area_tag(:mail_template, @mail_template, :cols => 72, :rows => 8)) %>
40   - </div>
  33 + <%= render :partial => 'invite/personalize_invitation_mail', :locals => {:mail_template => @mail_template } %>
41 34  
42 35 <% button_bar do %>
43   - <%= submit_button(:ok, _("Invite my friends!")) %>
  36 + <%= submit_button(:ok, _("Invite!")) %>
44 37 <% end %>
45 38 <% end %>
... ...
app/views/layouts/_javascript.html.erb
... ... @@ -2,8 +2,8 @@
2 2 'jquery-2.1.1.min', 'jquery-migrate-1.2.1',
3 3 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox',
4 4 'jquery-ui-1.10.4/js/jquery-ui-1.10.4.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate',
5   -'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput',
6   -'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow',
  5 +'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', 'jquery.typewatch', 'jquery.textchange',
  6 +'add-and-join', 'report-abuse', 'catalog', 'manage-products', 'autogrow', 'select-or-die/_src/selectordie',
7 7 'jquery-timepicker-addon/dist/jquery-ui-timepicker-addon', 'application.js', 'rails.js', 'inputosaurus.js', :cache => 'cache/application' %>
8 8  
9 9 <% language = FastGettext.locale %>
... ...
app/views/layouts/_user.html.erb
... ... @@ -10,16 +10,17 @@
10 10 <%= _("<span class='login'>%s</span>") % thickbox_inline_popup_link('<i class="icon-menu-login"></i><strong>' + _('Login') + '</strong>', login_url, 'inlineLoginBox', :id => 'link_login') %>
11 11 <%= @plugins.dispatch(:alternative_authentication_link).collect { |content| instance_exec(&content) }.join("") %>
12 12  
13   - <div id='inlineLoginBox' style='display: none;'>
14   - <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
15   - </div>
  13 + <div id='inlineLoginBox' style='display: none;'>
  14 + <%= render :file => 'account/login', :locals => { :is_thickbox => true } %>
  15 + </div>
16 16  
17   - <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
18   - <%= _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup')%>
19   - <% end %>
20   - </span>
  17 + <% unless @plugins.dispatch(:allow_user_registration).include?(false) %>
  18 + <%= _("<span class='or'>or</span> <span class='signup'>%s</span>") % link_to('<strong>' + _('Sign up') + '</strong>', :controller => 'account', :action => 'signup')%>
  19 + <% end %>
  20 +
  21 + </span>
21 22 <% end %>
22   - <form action="/search" id="top-search" class="search_form clean" method="get">
  23 + <form action="/search/articles" id="top-search" class="search_form clean" method="get">
23 24 <input name="query" size="15" title="<%=_('Search...')%>" onfocus="this.form.className='focused';" onblur="this.form.className=''" />
24 25 <div><%=_('Press <strong>Enter</strong> to send the search query.')%></div>
25 26 <%= javascript_tag 'jQuery("#user form input").hint();' %>
... ...
app/views/memberships/connections.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<h1><%= _("Connections with %s") % @suggestion.suggestion.name %></h1>
  2 +
  3 +<% button_bar do %>
  4 + <%= button(:back, _('Go to groups list'), :controller => 'memberships') %>
  5 +<% end %>
  6 +
  7 +<%= render :partial => 'shared/profile_connections', :locals => { :suggestion => @suggestion, :tags => @tags, :profiles => @profiles } %>
... ...
app/views/memberships/index.html.erb
... ... @@ -16,6 +16,8 @@
16 16 <%= labelled_select(_('Filter')+': ', :filter_type, :first, :last, @filter, type_collection, :id => 'memberships_filter')%>
17 17 </p>
18 18  
  19 +<p><%= link_to _('See some suggestions of communities...'), :action => 'suggest' %></p>
  20 +
19 21 <% if @memberships.empty? %>
20 22 <p>
21 23 <em><%= _('No groups to list') %></em>
... ...
app/views/memberships/remove_suggestion.html.erb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<div id="remove_suggestion">
  2 + <h1><%= _('Removing suggestion for community: %s') % @community.name %></h1>
  3 +
  4 + <%= render :partial => 'shared/remove_suggestion', :locals => { :suggestion => @community } %>
  5 +</div>
... ...
app/views/memberships/suggest.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<h1><%= _("Communities suggestions for %s") % profile.name %></h1>
  2 +
  3 +<% button_bar do %>
  4 + <%= button(:back, _('Go to groups list'), :controller => 'memberships') %>
  5 +<% end %>
  6 +
  7 +<div class="profiles-suggestions">
  8 + <%= render :partial => 'shared/profile_suggestions_list', :locals => { :suggestions => @suggestions, :collection => :communities_suggestions, :per_page => 12 } %>
  9 +</div>
... ...
app/views/memberships/welcome.html.erb 0 → 100644
... ... @@ -0,0 +1,9 @@
  1 +<h1><%= _('Community created') %></h1>
  2 +<p><%= _("Your community (%s) was successfully created.") % @community.name %></p>
  3 +<p><%= link_to _('You can manage your community now.'), @community.admin_url %></p>
  4 +
  5 +<%= render :partial => 'shared/template_welcome_page', :locals => {:template => @community.template, :header => _("What can I do with a %s?")} %>
  6 +
  7 +<% button_bar do %>
  8 + <%= button :back, _('Back'), @back_to %>
  9 +<% end %>
... ...
app/views/profile/_upload_image.html.erb
... ... @@ -10,5 +10,5 @@
10 10 </div>
11 11 </div>
12 12 </div>
13   -<div title='<%= activity.target.class.short_description %>' class='profile-activity-icon icon-new icon-newgallery'></div>
  13 +<div title='<%#= activity.target.class.short_description %>' class='profile-activity-icon icon-new icon-newgallery'></div>
14 14 <br/>
... ...
app/views/profile/friends.html.erb
... ... @@ -18,7 +18,7 @@
18 18 <%= button :back, _('Go back'), { :controller => 'profile' } %>
19 19 <% if user == profile %>
20 20 <%= button :edit, _('Manage my friends'), :controller => 'friends', :action => 'index', :profile => profile.identifier %>
21   - <%= button(:search, _('Invite people from my e-mail contacts'), :controller => 'invite', :action => 'select_address_book') %>
  21 + <%= button(:person, _('Invite people'), :controller => 'invite', :action => 'invite_friends') %>
22 22 <% end %>
23 23 <% end %>
24 24  
... ...
app/views/profile/members.html.erb
... ... @@ -18,7 +18,7 @@
18 18 <%= button :back, _('Go back'), { :controller => 'profile' } %>
19 19 <% if profile.community? and user %>
20 20 <% if user.has_permission?(:invite_members, profile) %>
21   - <%= button :search, _('Invite your friends to join %s') % profile.name, :controller => 'invite', :action => 'select_address_book' %>
  21 + <%= button :person, _('Invite people to join'), :controller => 'invite', :action => 'invite_friends' %>
22 22 <% end %>
23 23 <% if user.has_permission?(:send_mail_to_members, profile) %>
24 24 <%= button :send, _('Send e-mail to members'), :controller => 'profile', :action => 'send_mail' %>
... ...
app/views/profile_editor/_moderation.html.erb
1 1 <h2><%= _('Moderation options') %></h2>
2 2 <% if profile.community? %>
3 3 <div style='margin-bottom: 1em'>
  4 + <h4><%= _('Invitation moderation:')%></h4>
  5 + </div>
  6 + <div style='margin-bottom: 0.5em'>
  7 + <%= check_box(:profile_data, :allow_members_to_invite, :style => 'float: left') %>
  8 + <div style='margin-left: 30px'>
  9 + <%= _('Allow all members to send invitation (Default: only administrator)') %>
  10 + </div>
  11 + <br>
  12 + <div class = 'invite_friends_only' >
  13 + <%= check_box(:profile_data, :invite_friends_only, :style => 'float: left') %>
  14 + <div style='margin-left: 30px'>
  15 + <%= _('Allow members to invite only friends (Default: all users)') %>
  16 + </div>
  17 + </div>
  18 + </div>
  19 + <br>
  20 +
  21 + <div style='margin-bottom: 1em'>
4 22 <%= _('New members must be approved:')%>
5 23 </div>
6 24 <div style='margin-bottom: 0.5em'>
... ... @@ -33,3 +51,5 @@
33 51 <%= _('<strong>After</strong> being published in this group (a moderator can always remove publicated articles later).') %>
34 52 </div>
35 53 </div>
  54 +
  55 +<%= javascript_include_tag('invite') %>
... ...
app/views/profile_editor/edit.html.erb
... ... @@ -52,6 +52,12 @@
52 52 'profile_data[redirect_l10n]', true, @profile.redirect_l10n
53 53 )%>
54 54  
  55 + <h2><%= _('Suggestions') %></h2>
  56 + <%= labelled_check_box(
  57 + _('Send me relationship suggestions by email'),
  58 + 'profile_data[email_suggestions]', true, @profile.email_suggestions
  59 + )%>
  60 +
55 61 <%=
56 62 @plugins.dispatch(:profile_editor_extras).map do |content|
57 63 content.kind_of?(Proc) ? self.instance_exec(&content) : content
... ...
app/views/profile_editor/index.html.erb
... ... @@ -68,6 +68,8 @@
68 68  
69 69 <%= control_panel_button(_('Manage SPAM'), 'manage-spam', :controller => 'spam', :action => 'index') %>
70 70  
  71 + <%= control_panel_button(_('Edit welcome page'), 'welcome-page', :action => 'welcome_page') if has_welcome_page %>
  72 +
71 73 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
72 74 <%= control_panel_button(button[:title], button[:icon], button[:url]) %>
73 75 <% end %>
... ...
app/views/profile_editor/welcome_page.html.erb 0 → 100644
... ... @@ -0,0 +1,21 @@
  1 +<h1><%= _('Edit welcome page') %></h1>
  2 +
  3 +<%= labelled_form_for :welcome_page do |f| %>
  4 + <%= error_messages_for :welcome_page %>
  5 +
  6 + <%= f.check_box(:published) %>
  7 + <div class='explanation'>
  8 + <%= _('Your welcome page will only be displayed if this options is selected.') %>
  9 + </div>
  10 +
  11 + <%= f.text_area(:body, :cols => 40, :style => 'width: 100%', :class => 'mceEditor') %>
  12 + <div class='explanation'>
  13 + <%= _('This page will be displayed to the user after his signup with this template.') %>
  14 + </div>
  15 +
  16 + <% button_bar do%>
  17 + <%= submit_button('save', _('Save'), :cancel => @back_to) %>
  18 + <% end %>
  19 +<% end %>
  20 +
  21 +<%= render :file => 'shared/tiny_mce' %>
... ...
app/views/profile_members/_index_buttons.html.erb
... ... @@ -2,7 +2,7 @@
2 2 <%= button :back, _('Back'), :controller => 'profile_editor' %>
3 3 <%= button :add, _('Add members'), :action => 'add_members' if profile.enterprise? %>
4 4 <% if profile.community? and user.has_permission?(:invite_members, profile) %>
5   - <%= button :search, _('Invite your friends to join %s') % profile.short_name, :controller => 'invite', :action => 'select_address_book' %>
  5 + <%= button :person, _('Invite people to join'), :controller => 'invite', :action => 'invite_friends' %>
6 6 <% end %>
7 7 <% if profile.community? and user.has_permission?(:send_mail_to_members, profile) %>
8 8 <%= button :send, _('Send e-mail to members'), :controller => 'profile', :action => 'send_mail' %>
... ...
app/views/profile_search/_results-list.html.erb 0 → 100644
... ... @@ -0,0 +1,15 @@
  1 +<div id='search-content'>
  2 + <% if @results %>
  3 + <div class='results-found-message'>
  4 + <%= _("%s results found") % @results.total_entries %>
  5 + </div>
  6 +
  7 + <ul class='results-list'>
  8 + <% @results.sort_by { |r| r.is_image? ? 0 : 1}.each do |result| %>
  9 + <%= render :partial => partial_for_class(result.class), :locals => { :article => result } %>
  10 + <% end %>
  11 + </ul>
  12 +
  13 + <%= pagination_links @results %>
  14 + <% end %>
  15 +</div>
... ...
app/views/profile_search/index.html.erb
... ... @@ -3,17 +3,7 @@
3 3  
4 4 <%= render :partial => 'shared/profile_search_form' %>
5 5  
6   - <% if @results %>
7   - <div class='results-found-message'>
8   - <%= _("%s results found") % @results.total_entries %>
9   - </div>
  6 + <%= render :partial => 'results-list' %>
10 7  
11   - <ul class='results-list'>
12   - <% @results.sort_by { |r| r.is_image? ? 0 : 1}.each do |result| %>
13   - <%= render :partial => partial_for_class(result.class), :locals => { :article => result } %>
14   - <% end %>
15   - </ul>
16   -
17   - <%= pagination_links @results %>
18   - <% end %>
19 8 </div>
  9 +<%= javascript_include_tag 'search' %>
... ...
app/views/profile_search/index.js.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +jQuery('#search-content').html('<%= escape_javascript(render :partial => "results-list") %>');
... ...
app/views/search/_compact_profile.html.erb
1   -<% filter_label = profile.send(@filter + '_label') %>
2   -<% filter_label += show_date(profile.created_at) if @filter == 'more_recent' %>
  1 +<% filter_label = profile.send(@order + '_label') %>
  2 +<% filter_label += show_date(profile.created_at) if @order == 'more_recent' %>
3 3 <li class="search-profile-item">
4 4 <%= profile_image_link profile, :portrait, 'div', filter_label %>
5 5 </li>
... ...
app/views/search/_display_results.html.erb
1 1 <div id="search-results" class="<%= !multiple_search? ? 'only-one-result-box' : 'multiple-results-boxes' %>">
2   - <% @order.each do |name| %>
  2 + <% @assets.each do |name| %>
3 3 <% search = @searches[name] %>
4 4  
5 5 <div class="search-results-<%= name %> search-results-box <%= "search-results-empty" if search[:results].blank? %>">
... ...
app/views/search/_full_enterprise.html.erb
... ... @@ -2,7 +2,7 @@
2 2 <div class="search-enterprise-item">
3 3 <div class="search-enterprise-item-column-left">
4 4 <%= profile_image_link enterprise, :portrait, 'div',
5   - @filter == 'more_recent' ? enterprise.send(@filter + '_label') + show_date(enterprise.created_at) : enterprise.send(@filter + '_label') %>
  5 + @order == 'more_recent' ? enterprise.send(@order + '_label') + show_date(enterprise.created_at) : enterprise.send(@order + '_label') %>
6 6 </div>
7 7 <div class="search-enterprise-item-column-right">
8 8 <%= link_to_homepage(enterprise.name, enterprise.identifier, :class => "search-result-title") %>
... ...