Compare View

switch
from
...
to
 
Commits (54)
Showing 300 changed files   Show diff stats

Too many changes.

To preserve performance only 100 of 300 files displayed.

app/api/entities.rb
... ... @@ -124,6 +124,7 @@ module Api
124 124 expose :type
125 125 expose :custom_header
126 126 expose :custom_footer
  127 + expose :layout_template
127 128 expose :permissions do |profile, options|
128 129 Entities.permissions_for_entity(profile, options[:current_person],
129 130 :allow_post_content?, :allow_edit?, :allow_destroy?)
... ... @@ -264,6 +265,7 @@ module Api
264 265 expose :name
265 266 expose :id
266 267 expose :description
  268 + expose :layout_template
267 269 expose :settings, if: lambda { |instance, options| options[:is_admin] }
268 270 end
269 271  
... ...
app/api/helpers.rb
... ... @@ -302,12 +302,12 @@ module Api
302 302 end
303 303  
304 304 def cant_be_saved_request!(attribute)
305   - message = _("(Invalid request) %s can't be saved") % attribute
  305 + message = _("(Invalid request) %s can't be saved").html_safe % attribute
306 306 render_api_error!(message, 400)
307 307 end
308 308  
309 309 def bad_request!(attribute)
310   - message = _("(Invalid request) %s not given") % attribute
  310 + message = _("(Invalid request) %s not given").html_safe % attribute
311 311 render_api_error!(message, 400)
312 312 end
313 313  
... ...
app/api/v1/people.rb
... ... @@ -119,6 +119,20 @@ module Api
119 119 members = select_filtered_collection_of(profile, 'members', params)
120 120 present members, :with => Entities::Person, :current_person => current_person
121 121 end
  122 +
  123 + post do
  124 + authenticate!
  125 + profile = environment.profiles.find_by id: params[:profile_id]
  126 + profile.add_member(current_person) rescue forbidden!
  127 + {pending: !current_person.is_member_of?(profile)}
  128 + end
  129 +
  130 + delete do
  131 + authenticate!
  132 + profile = environment.profiles.find_by id: params[:profile_id]
  133 + profile.remove_member(current_person)
  134 + present current_person, :with => Entities::Person, :current_person => current_person
  135 + end
122 136 end
123 137 end
124 138 end
... ...
app/controllers/admin/categories_controller.rb
... ... @@ -44,7 +44,7 @@ class CategoriesController < AdminController
44 44 if request.post?
45 45 @category.update!(params[:category])
46 46 @saved = true
47   - session[:notice] = _("Category %s saved." % @category.name)
  47 + session[:notice] = _("Category %s saved." % @category.name).html_safe
48 48 redirect_to :action => 'index'
49 49 end
50 50 rescue Exception => e
... ...
app/controllers/application_controller.rb
... ... @@ -14,6 +14,20 @@ class ApplicationController < ActionController::Base
14 14 before_filter :redirect_to_current_user
15 15  
16 16 before_filter :set_session_theme
  17 +
  18 + # FIXME: only include necessary methods
  19 + include ApplicationHelper
  20 +
  21 + # concerns
  22 + include PermissionCheck
  23 + include CustomDesign
  24 + include NeedsProfile
  25 +
  26 + # implementations
  27 + include FindByContents
  28 + include Noosfero::Plugin::HotSpot
  29 + include SearchTermHelper
  30 +
17 31 def set_session_theme
18 32 if params[:theme]
19 33 session[:theme] = environment.theme_ids.include?(params[:theme]) ? params[:theme] : nil
... ... @@ -48,7 +62,6 @@ class ApplicationController < ActionController::Base
48 62 end
49 63 end
50 64  
51   - include ApplicationHelper
52 65 layout :get_layout
53 66 def get_layout
54 67 return false if request.format == :js or request.xhr?
... ... @@ -74,9 +87,6 @@ class ApplicationController < ActionController::Base
74 87 helper :document
75 88 helper :language
76 89  
77   - include DesignHelper
78   - include PermissionCheck
79   -
80 90 before_filter :set_locale
81 91 def set_locale
82 92 FastGettext.available_locales = environment.available_locales
... ... @@ -89,8 +99,6 @@ class ApplicationController < ActionController::Base
89 99 end
90 100 end
91 101  
92   - include NeedsProfile
93   -
94 102 attr_reader :environment
95 103  
96 104 # declares that the given <tt>actions</tt> cannot be accessed by other HTTP
... ... @@ -107,6 +115,10 @@ class ApplicationController &lt; ActionController::Base
107 115  
108 116 protected
109 117  
  118 + def accept_only_post
  119 + return render_not_found if !request.post?
  120 + end
  121 +
110 122 def verified_request?
111 123 super || form_authenticity_token == request.headers['X-XSRF-TOKEN']
112 124 end
... ... @@ -151,8 +163,6 @@ class ApplicationController &lt; ActionController::Base
151 163 end
152 164 end
153 165  
154   - include Noosfero::Plugin::HotSpot
155   -
156 166 # FIXME this filter just loads @plugins to children controllers and helpers
157 167 def init_noosfero_plugins
158 168 plugins
... ... @@ -184,9 +194,6 @@ class ApplicationController &lt; ActionController::Base
184 194 end
185 195 end
186 196  
187   - include SearchTermHelper
188   - include FindByContents
189   -
190 197 def find_suggestions(query, context, asset, options={})
191 198 plugins.dispatch_first(:find_suggestions, query, context, asset, options)
192 199 end
... ...
app/controllers/box_organizer_controller.rb
... ... @@ -109,7 +109,7 @@ class BoxOrganizerController &lt; ApplicationController
109 109 def show_block_type_info
110 110 type = params[:type]
111 111 if type.blank? || !available_blocks.map(&:name).include?(type)
112   - raise ArgumentError.new("Type %s is not allowed. Go away." % type)
  112 + raise ArgumentError.new("Type %s is not allowed. Go away.".html_safe % type)
113 113 end
114 114 @block = type.constantize.new
115 115 @block.box = Box.new(:owner => boxes_holder)
... ... @@ -122,7 +122,7 @@ class BoxOrganizerController &lt; ApplicationController
122 122  
123 123 def new_block(type, box)
124 124 if !available_blocks.map(&:name).include?(type)
125   - raise ArgumentError.new("Type %s is not allowed. Go away." % type)
  125 + raise ArgumentError.new("Type %s is not allowed. Go away.".html_safe % type)
126 126 end
127 127 block = type.constantize.new
128 128 box.blocks << block
... ...
app/controllers/concerns/authenticated_system.rb 0 → 100644
... ... @@ -0,0 +1,169 @@
  1 +module AuthenticatedSystem
  2 +
  3 + protected
  4 +
  5 + extend ActiveSupport::Concern
  6 +
  7 + included do
  8 + if self < ActionController::Base
  9 + around_filter :user_set_current
  10 + before_filter :override_user
  11 + before_filter :login_from_cookie
  12 + end
  13 +
  14 + # Inclusion hook to make #current_user and #logged_in?
  15 + # available as ActionView helper methods.
  16 + helper_method :current_user, :logged_in?
  17 + end
  18 +
  19 + # Returns true or false if the user is logged in.
  20 + # Preloads @current_user with the user model if they're logged in.
  21 + def logged_in?
  22 + current_user != nil
  23 + end
  24 +
  25 + # Accesses the current user from the session.
  26 + def current_user user_id = session[:user]
  27 + @current_user ||= begin
  28 + user = User.find_by id: user_id if user_id
  29 + user.session = session if user
  30 + User.current = user
  31 + user
  32 + end
  33 + end
  34 +
  35 + # Store the given user in the session.
  36 + def current_user=(new_user)
  37 + if new_user.nil?
  38 + session.delete(:user)
  39 + else
  40 + session[:user] = new_user.id
  41 + new_user.session = session
  42 + new_user.register_login
  43 + end
  44 + @current_user = User.current = new_user
  45 + end
  46 +
  47 + # See impl. from http://stackoverflow.com/a/2513456/670229
  48 + def user_set_current
  49 + User.current = current_user
  50 + yield
  51 + ensure
  52 + # to address the thread variable leak issues in Puma/Thin webserver
  53 + User.current = nil
  54 + end
  55 +
  56 + # Check if the user is authorized.
  57 + #
  58 + # Override this method in your controllers if you want to restrict access
  59 + # to only a few actions or if you want to check if the user
  60 + # has the correct rights.
  61 + #
  62 + # Example:
  63 + #
  64 + # # only allow nonbobs
  65 + # def authorize?
  66 + # current_user.login != "bob"
  67 + # end
  68 + def authorized?
  69 + true
  70 + end
  71 +
  72 + # Filter method to enforce a login requirement.
  73 + #
  74 + # To require logins for all actions, use this in your controllers:
  75 + #
  76 + # before_filter :login_required
  77 + #
  78 + # To require logins for specific actions, use this in your controllers:
  79 + #
  80 + # before_filter :login_required, :only => [ :edit, :update ]
  81 + #
  82 + # To skip this in a subclassed controller:
  83 + #
  84 + # skip_before_filter :login_required
  85 + #
  86 + def login_required
  87 + username, passwd = get_auth_data
  88 + if username && passwd
  89 + self.current_user ||= User.authenticate(username, passwd) || nil
  90 + end
  91 + if logged_in? && authorized?
  92 + true
  93 + else
  94 + if params[:require_login_popup]
  95 + render :json => { :require_login_popup => true }
  96 + else
  97 + access_denied
  98 + end
  99 + end
  100 + end
  101 +
  102 + # Redirect as appropriate when an access request fails.
  103 + #
  104 + # The default action is to redirect to the login screen.
  105 + #
  106 + # Override this method in your controllers if you want to have special
  107 + # behavior in case the user is not authorized
  108 + # to access the requested action. For example, a popup window might
  109 + # simply close itself.
  110 + def access_denied
  111 + respond_to do |accepts|
  112 + accepts.html do
  113 + if request.xhr?
  114 + render :text => _('Access denied'), :status => 401
  115 + else
  116 + store_location
  117 + redirect_to :controller => '/account', :action => 'login'
  118 + end
  119 + end
  120 + accepts.xml do
  121 + headers["Status"] = "Unauthorized"
  122 + headers["WWW-Authenticate"] = %(Basic realm="Web Password")
  123 + render :text => "Could't authenticate you", :status => '401 Unauthorized'
  124 + end
  125 + end
  126 + false
  127 + end
  128 +
  129 + # Store the URI of the current request in the session.
  130 + #
  131 + # We can return to this location by calling #redirect_back_or_default.
  132 + def store_location(location = request.url)
  133 + session[:return_to] = location
  134 + end
  135 +
  136 + # Redirect to the URI stored by the most recent store_location call or
  137 + # to the passed default.
  138 + def redirect_back_or_default(default)
  139 + if session[:return_to]
  140 + redirect_to(session.delete(:return_to))
  141 + else
  142 + redirect_to(default)
  143 + end
  144 + end
  145 +
  146 + def override_user
  147 + return if params[:override_user].blank?
  148 + return unless logged_in? and user.is_admin? environment
  149 + @current_user = nil
  150 + current_user params[:override_user]
  151 + end
  152 +
  153 + # When called with before_filter :login_from_cookie will check for an :auth_token
  154 + # cookie and log the user back in if apropriate
  155 + def login_from_cookie
  156 + return if cookies[:auth_token].blank? or logged_in?
  157 + user = User.where(remember_token: cookies[:auth_token]).first
  158 + self.current_user = user if user and user.remember_token?
  159 + end
  160 +
  161 + private
  162 + @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
  163 + # gets BASIC auth info
  164 + def get_auth_data
  165 + auth_key = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
  166 + auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
  167 + return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil]
  168 + end
  169 +end
... ...
app/controllers/concerns/custom_design.rb 0 → 100644
... ... @@ -0,0 +1,50 @@
  1 +module CustomDesign
  2 +
  3 + extend ActiveSupport::Concern
  4 +
  5 + included do
  6 + extend ClassMethods
  7 + include InstanceMethods
  8 + before_filter :load_custom_design if self.respond_to? :before_filter
  9 + end
  10 +
  11 + module ClassMethods
  12 +
  13 + def no_design_blocks
  14 + @no_design_blocks = true
  15 + end
  16 +
  17 + def use_custom_design options = {}
  18 + @custom_design = options
  19 + end
  20 +
  21 + def custom_design
  22 + @custom_design ||= {}
  23 + end
  24 +
  25 + def uses_design_blocks?
  26 + !@no_design_blocks
  27 + end
  28 +
  29 + end
  30 +
  31 + module InstanceMethods
  32 +
  33 + protected
  34 +
  35 + def uses_design_blocks?
  36 + !@no_design_blocks && self.class.uses_design_blocks?
  37 + end
  38 +
  39 + def load_custom_design
  40 + # see also: LayoutHelper#body_classes
  41 + @layout_template = self.class.custom_design[:layout_template]
  42 + end
  43 +
  44 + def custom_design
  45 + @custom_design || self.class.custom_design
  46 + end
  47 +
  48 + end
  49 +
  50 +end
... ...
app/controllers/concerns/needs_profile.rb 0 → 100644
... ... @@ -0,0 +1,40 @@
  1 +module NeedsProfile
  2 +
  3 + module ClassMethods
  4 + def needs_profile
  5 + before_filter :load_profile
  6 + end
  7 + end
  8 +
  9 + def self.included(including)
  10 + including.send(:extend, NeedsProfile::ClassMethods)
  11 + end
  12 +
  13 + def boxes_holder
  14 + profile || environment # prefers profile, but defaults to environment
  15 + end
  16 +
  17 + def profile
  18 + @profile
  19 + end
  20 +
  21 + protected
  22 +
  23 + def load_profile
  24 + if params[:profile]
  25 + params[:profile].downcase!
  26 + @profile ||= environment.profiles.where(identifier: params[:profile]).first
  27 + end
  28 +
  29 + if @profile
  30 + profile_hostname = @profile.hostname
  31 + if profile_hostname && profile_hostname != request.host
  32 + params.delete(:profile)
  33 + redirect_to(Noosfero.url_options.merge(params).merge(:host => profile_hostname))
  34 + end
  35 + else
  36 + render_not_found
  37 + end
  38 + end
  39 +
  40 +end
... ...
app/controllers/my_profile/circles_controller.rb 0 → 100644
... ... @@ -0,0 +1,58 @@
  1 +class CirclesController < MyProfileController
  2 +
  3 + before_action :accept_only_post, :only => [:create, :update, :destroy]
  4 +
  5 + def index
  6 + @circles = profile.circles
  7 + end
  8 +
  9 + def new
  10 + @circle = Circle.new
  11 + end
  12 +
  13 + def create
  14 + @circle = Circle.new(params[:circle].merge({ :person => profile }))
  15 + if @circle.save
  16 + redirect_to :action => 'index'
  17 + else
  18 + render :action => 'new'
  19 + end
  20 + end
  21 +
  22 + def xhr_create
  23 + if request.xhr?
  24 + circle = Circle.new(params[:circle].merge({:person => profile }))
  25 + if circle.save
  26 + render :partial => "circle_checkbox", :locals => { :circle => circle },
  27 + :status => 201
  28 + else
  29 + render :text => _('The circle could not be saved'), :status => 400
  30 + end
  31 + else
  32 + render_not_found
  33 + end
  34 + end
  35 +
  36 + def edit
  37 + @circle = Circle.find_by_id(params[:id])
  38 + render_not_found if @circle.nil?
  39 + end
  40 +
  41 + def update
  42 + @circle = Circle.find_by_id(params[:id])
  43 + return render_not_found if @circle.nil?
  44 +
  45 + if @circle.update(params[:circle])
  46 + redirect_to :action => 'index'
  47 + else
  48 + render :action => 'edit'
  49 + end
  50 + end
  51 +
  52 + def destroy
  53 + @circle = Circle.find_by_id(params[:id])
  54 + return render_not_found if @circle.nil?
  55 + @circle.destroy
  56 + redirect_to :action => 'index'
  57 + end
  58 +end
... ...
app/controllers/my_profile/followers_controller.rb 0 → 100644
... ... @@ -0,0 +1,43 @@
  1 +class FollowersController < MyProfileController
  2 +
  3 + before_action :only_for_person, :only => :index
  4 + before_action :accept_only_post, :only => [:update_category]
  5 +
  6 + def index
  7 + @followed_people = profile.followed_profiles.order(:type)
  8 + @profile_types = {_('All profiles') => nil}.merge(Circle.profile_types).to_a
  9 +
  10 + if params['filter'].present?
  11 + @followed_people = @followed_people.where(:type => params['filter'])
  12 + @active_filter = params['filter']
  13 + end
  14 +
  15 + @followed_people = @followed_people.paginate(:per_page => 15, :page => params[:npage])
  16 + end
  17 +
  18 + def set_category_modal
  19 + followed_profile = Profile.find(params[:followed_profile_id])
  20 + circles = Circle.where(:person => profile, :profile_type => followed_profile.class.name)
  21 + render :partial => 'followers/edit_circles_modal', :locals => { :circles => circles, :followed_profile => followed_profile }
  22 + end
  23 +
  24 + def update_category
  25 + followed_profile = Profile.find_by(:id => params["followed_profile_id"])
  26 +
  27 + selected_circles = params[:circles].map{ |circle_name, circle_id| Circle.find_by(:id => circle_id) }.select{ |c| c.present? }
  28 +
  29 + if followed_profile
  30 + profile.update_profile_circles(followed_profile, selected_circles)
  31 + render :text => _("Circles of %s updated successfully") % followed_profile.name, :status => 200
  32 + else
  33 + render :text => _("Error: No profile to follow."), :status => 400
  34 + end
  35 + end
  36 +
  37 + protected
  38 +
  39 + def only_for_person
  40 + render_not_found unless profile.person?
  41 + end
  42 +
  43 +end
... ...
app/controllers/public/account_controller.rb
... ... @@ -205,7 +205,7 @@ class AccountController &lt; ApplicationController
205 205 if params[:value].blank?
206 206 @change_password.errors[:base] << _('Can not recover user password with blank value.')
207 207 else
208   - @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".') % [fields_label, params[:value]]
  208 + @change_password.errors[:base] << _('Could not find any user with %s equal to "%s".').html_safe % [fields_label, params[:value]]
209 209 end
210 210 rescue ActiveRecord::RecordInvalid
211 211 @change_password.errors[:base] << _('Could not perform password recovery for the user.')
... ...
app/controllers/public/content_viewer_controller.rb
... ... @@ -128,9 +128,9 @@ class ContentViewerController &lt; ApplicationController
128 128 end
129 129  
130 130 unless @page.display_to?(user)
131   - if !profile.visible? || profile.secret? || (user && user.follows?(profile)) || user.blank?
  131 + if !profile.visible? || profile.secret? || (user && profile.in_social_circle?(user)) || user.blank?
132 132 render_access_denied
133   - else #!profile.public?
  133 + else
134 134 private_profile_partial_parameters
135 135 render :template => 'profile/_private_profile', :status => 403, :formats => [:html]
136 136 end
... ...
app/controllers/public/profile_controller.rb
... ... @@ -3,7 +3,9 @@ class ProfileController &lt; PublicController
3 3 needs_profile
4 4 before_filter :check_access_to_profile, :except => [:join, :join_not_logged, :index, :add]
5 5 before_filter :store_location, :only => [:join, :join_not_logged, :report_abuse, :send_mail]
6   - before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail]
  6 + before_filter :login_required, :only => [:add, :join, :leave, :unblock, :leave_scrap, :remove_scrap, :remove_activity, :view_more_activities, :view_more_network_activities, :report_abuse, :register_report, :leave_comment_on_activity, :send_mail, :follow, :unfollow]
  7 + before_filter :allow_followers?, :only => [:follow, :unfollow]
  8 + before_filter :accept_only_post, :only => [:follow, :unfollow]
7 9  
8 10 helper TagsHelper
9 11 helper ActionTrackerHelper
... ... @@ -42,8 +44,8 @@ class ProfileController &lt; PublicController
42 44 feed_writer = FeedWriter.new
43 45 data = feed_writer.write(
44 46 tagged,
45   - :title => _("%s's contents tagged with \"%s\"") % [profile.name, @tag],
46   - :description => _("%s's contents tagged with \"%s\"") % [profile.name, @tag],
  47 + :title => _("%s's contents tagged with \"%s\"").html_safe % [profile.name, @tag],
  48 + :description => _("%s's contents tagged with \"%s\"").html_safe % [profile.name, @tag],
47 49 :link => url_for(profile.url)
48 50 )
49 51 render :text => data, :content_type => "text/xml"
... ... @@ -65,6 +67,14 @@ class ProfileController &lt; PublicController
65 67 end
66 68 end
67 69  
  70 + def following
  71 + @followed_people = profile.followed_profiles.paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.followed_profiles.count)
  72 + end
  73 +
  74 + def followed
  75 + @followed_by = profile.followers.paginate(:per_page => per_page, :page => params[:npage], :total_entries => profile.followers.count)
  76 + end
  77 +
68 78 def members
69 79 if is_cache_expired?(profile.members_cache_key(params))
70 80 sort = (params[:sort] == 'desc') ? params[:sort] : 'asc'
... ... @@ -88,7 +98,7 @@ class ProfileController &lt; PublicController
88 98  
89 99 def join_modal
90 100 profile.add_member(user)
91   - session[:notice] = _('%s administrator still needs to accept you as member.') % profile.name
  101 + session[:notice] = _('%s administrator still needs to accept you as member.').html_safe % profile.name
92 102 redirect_to :action => :index
93 103 end
94 104  
... ... @@ -98,12 +108,12 @@ class ProfileController &lt; PublicController
98 108  
99 109 profile.add_member(user)
100 110 if !profile.members.include?(user)
101   - render :text => {:message => _('%s administrator still needs to accept you as member.') % profile.name}.to_json
  111 + render :text => {:message => _('%s administrator still needs to accept you as member.').html_safe % profile.name}.to_json
102 112 else
103   - render :text => {:message => _('You just became a member of %s.') % profile.name}.to_json
  113 + render :text => {:message => _('You just became a member of %s.').html_safe % profile.name}.to_json
104 114 end
105 115 else
106   - render :text => {:message => _('You are already a member of %s.') % profile.name}.to_json
  116 + render :text => {:message => _('You are already a member of %s.').html_safe % profile.name}.to_json
107 117 end
108 118 end
109 119  
... ... @@ -125,7 +135,7 @@ class ProfileController &lt; PublicController
125 135 render :text => current_person.leave(profile, params[:reload])
126 136 end
127 137 else
128   - render :text => {:message => _('You are not a member of %s.') % profile.name}.to_json
  138 + render :text => {:message => _('You are not a member of %s.').html_safe % profile.name}.to_json
129 139 end
130 140 end
131 141  
... ... @@ -145,12 +155,41 @@ class ProfileController &lt; PublicController
145 155 # FIXME this shouldn't be in Person model?
146 156 if !user.memberships.include?(profile)
147 157 AddFriend.create!(:person => user, :friend => profile)
148   - render :text => _('%s still needs to accept being your friend.') % profile.name
  158 + render :text => _('%s still needs to accept being your friend.').html_safe % profile.name
149 159 else
150   - render :text => _('You are already a friend of %s.') % profile.name
  160 + render :text => _('You are already a friend of %s.').html_safe % profile.name
  161 + end
  162 + end
  163 +
  164 + def follow
  165 + if profile.followed_by?(current_person)
  166 + render :text => _("You are already following %s.") % profile.name, :status => 400
  167 + else
  168 + selected_circles = params[:circles].map{ |circle_name, circle_id| Circle.find_by(:id => circle_id) }.select{ |c| c.present? }
  169 + if selected_circles.present?
  170 + current_person.follow(profile, selected_circles)
  171 + render :text => _("You are now following %s") % profile.name, :status => 200
  172 + else
  173 + render :text => _("Select at least one circle to follow %s.") % profile.name, :status => 400
  174 + end
151 175 end
152 176 end
153 177  
  178 + def find_profile_circles
  179 + circles = Circle.where(:person => current_person, :profile_type => profile.class.name)
  180 + render :partial => 'blocks/profile_info_actions/circles', :locals => { :circles => circles, :profile_types => Circle.profile_types.to_a }
  181 + end
  182 +
  183 + def unfollow
  184 + follower = params[:follower_id].present? ? Person.find_by(id: params[:follower_id]) : current_person
  185 +
  186 + if follower && follower.follows?(profile)
  187 + follower.unfollow(profile)
  188 + end
  189 + redirect_url = params["redirect_to"] ? params["redirect_to"] : profile.url
  190 + redirect_to redirect_url
  191 + end
  192 +
154 193 def check_friendship
155 194 unless logged_in?
156 195 render :text => ''
... ... @@ -178,7 +217,7 @@ class ProfileController &lt; PublicController
178 217 def unblock
179 218 if current_user.person.is_admin?(profile.environment)
180 219 profile.unblock
181   - session[:notice] = _("You have unblocked %s successfully. ") % profile.name
  220 + session[:notice] = _("You have unblocked %s successfully. ").html_safe % profile.name
182 221 redirect_to :controller => 'profile', :action => 'index'
183 222 else
184 223 message = _('You are not allowed to unblock enterprises in this environment.')
... ... @@ -437,4 +476,8 @@ class ProfileController &lt; PublicController
437 476 [:image, :domains, :preferred_domain, :environment]
438 477 end
439 478  
  479 + def allow_followers?
  480 + render_not_found unless profile.allow_followers?
  481 + end
  482 +
440 483 end
... ...
app/helpers/action_tracker_helper.rb
... ... @@ -14,13 +14,23 @@ module ActionTrackerHelper
14 14 }
15 15 end
16 16  
  17 + def new_follower_description ta
  18 + n_('has 1 new follower:<br />%{name}', 'has %{num} new followers:<br />%{name}', ta.get_follower_name.size).html_safe % {
  19 + num: ta.get_follower_name.size,
  20 + name: safe_join(ta.collect_group_with_index(:follower_name) do |n,i|
  21 + link_to image_tag(ta.get_follower_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/person-icon.png")),
  22 + ta.get_follower_url[i], title: n
  23 + end)
  24 + }
  25 + end
  26 +
17 27 def join_community_description ta
18   - n_('has joined 1 community:<br />%{name}'.html_safe, 'has joined %{num} communities:<br />%{name}'.html_safe, ta.get_resource_name.size) % {
  28 + n_('has joined 1 community:<br />%{name}', 'has joined %{num} communities:<br />%{name}', ta.get_resource_name.size).html_safe % {
19 29 num: ta.get_resource_name.size,
20   - name: ta.collect_group_with_index(:resource_name) do |n,i|
  30 + name: safe_join(ta.collect_group_with_index(:resource_name) do |n,i|
21 31 link = link_to image_tag(ta.get_resource_profile_custom_icon[i] || default_or_themed_icon("/images/icons-app/community-icon.png")),
22 32 ta.get_resource_url[i], title: n
23   - end.join.html_safe
  33 + end)
24 34 }
25 35 end
26 36  
... ... @@ -68,9 +78,9 @@ module ActionTrackerHelper
68 78 end
69 79  
70 80 def favorite_enterprise_description ta
71   - _('favorited enterprise %{title}') % {
  81 + (_('favorited enterprise %{title}') % {
72 82 title: link_to(truncate(ta.get_enterprise_name), ta.get_enterprise_url),
73   - }
  83 + }).html_safe
74 84 end
75 85  
76 86 end
... ...
app/helpers/application_helper.rb
... ... @@ -880,7 +880,7 @@ module ApplicationHelper
880 880 link_to_all = link_to(content_tag('strong', _('See all')), :controller => 'memberships', :profile => user.identifier)
881 881 end
882 882 link = list.map do |element|
883   - link_to(content_tag('strong', _('<span>Manage</span> %s') % element.short_name(25)), element.admin_url, :class => "icon-menu-"+element.class.identification.underscore, :title => _('Manage %s') % element.short_name)
  883 + link_to(content_tag('strong', _('<span>Manage</span> %s').html_safe % element.short_name(25)), element.admin_url, :class => "icon-menu-"+element.class.identification.underscore, :title => _('Manage %s').html_safe % element.short_name)
884 884 end
885 885 if link_to_all
886 886 link << link_to_all
... ... @@ -921,7 +921,7 @@ module ApplicationHelper
921 921 logout_link = link_to(logout_icon.html_safe, { :controller => 'account', :action => 'logout'} , :id => "logout", :title => _("Leave the system"))
922 922 join_result = safe_join(
923 923 [welcome_span.html_safe, render_environment_features(:usermenu).html_safe, admin_link.html_safe,
924   - manage_enterprises.html_safe, manage_communities.html_safe, ctrl_panel_link.html_safe,
  924 + manage_enterprises, manage_communities, ctrl_panel_link.html_safe,
925 925 pending_tasks_count.html_safe, logout_link.html_safe], "")
926 926 join_result
927 927 end
... ...
app/helpers/forms_helper.rb
... ... @@ -128,14 +128,14 @@ module FormsHelper
128 128 counter += 1
129 129 row << item
130 130 if counter % per_row == 0
131   - rows << content_tag('tr', row.join("\n"))
  131 + rows << content_tag('tr', row.join("\n").html_safe)
132 132 counter = 0
133 133 row = []
134 134 end
135 135 end
136   - rows << content_tag('tr', row.join("\n"))
  136 + rows << content_tag('tr', row.join("\n").html_safe)
137 137  
138   - content_tag('table',rows.join("\n"))
  138 + content_tag('table',rows.join("\n").html_safe)
139 139 end
140 140  
141 141 def date_field(name, value, datepicker_options = {}, html_options = {})
... ...
app/helpers/profile_helper.rb
... ... @@ -11,7 +11,7 @@ module ProfileHelper
11 11 PERSON_CATEGORIES[:location] = [:address, :address_reference, :zip_code, :city, :state, :district, :country, :nationality]
12 12 PERSON_CATEGORIES[:work] = [:organization, :organization_website, :professional_activity]
13 13 PERSON_CATEGORIES[:study] = [:schooling, :formation, :area_of_study]
14   - PERSON_CATEGORIES[:network] = [:friends, :communities, :enterprises]
  14 + PERSON_CATEGORIES[:network] = [:friends, :followers, :followed_profiles, :communities, :enterprises]
15 15 PERSON_CATEGORIES.merge!(COMMON_CATEGORIES)
16 16  
17 17 ORGANIZATION_CATEGORIES = {}
... ... @@ -42,7 +42,8 @@ module ProfileHelper
42 42 :created_at => _('Profile created at'),
43 43 :members_count => _('Members'),
44 44 :privacy_setting => _('Privacy setting'),
45   - :article_tags => _('Tags')
  45 + :article_tags => _('Tags'),
  46 + :followed_profiles => _('Following')
46 47 }
47 48  
48 49 EXCEPTION = {
... ... @@ -144,6 +145,14 @@ module ProfileHelper
144 145 link_to(n_('One picture', '%{num} pictures', gallery.images.published.count) % { :num => gallery.images.published.count }, gallery.url)
145 146 end
146 147  
  148 + def treat_followers(followers)
  149 + link_to(profile.followers.count, {:action=>"followed", :controller=>"profile", :profile=>"#{profile.identifier}"})
  150 + end
  151 +
  152 + def treat_followed_profiles(followed_profiles)
  153 + link_to(profile.followed_profiles.count, {:action=>"following", :controller=>"profile", :profile=>"#{profile.identifier}"})
  154 + end
  155 +
147 156 def treat_events(events)
148 157 link_to events.published.count, :controller => 'events', :action => 'events'
149 158 end
... ...
app/jobs/notify_activity_to_profiles_job.rb
... ... @@ -19,8 +19,8 @@ class NotifyActivityToProfilesJob &lt; Struct.new(:tracked_action_id)
19 19 # Notify the user
20 20 ActionTrackerNotification.create(:profile_id => tracked_action.user.id, :action_tracker_id => tracked_action.id)
21 21  
22   - # Notify all friends
23   - ActionTrackerNotification.connection.execute("insert into action_tracker_notifications(profile_id, action_tracker_id) select f.friend_id, #{tracked_action.id} from friendships as f where person_id=#{tracked_action.user.id} and f.friend_id not in (select atn.profile_id from action_tracker_notifications as atn where atn.action_tracker_id = #{tracked_action.id})")
  22 + # Notify all followers
  23 + ActionTrackerNotification.connection.execute("INSERT INTO action_tracker_notifications(profile_id, action_tracker_id) SELECT DISTINCT c.person_id, #{tracked_action.id} FROM profiles_circles AS p JOIN circles as c ON c.id = p.circle_id WHERE p.profile_id = #{tracked_action.user.id} AND (c.person_id NOT IN (SELECT atn.profile_id FROM action_tracker_notifications AS atn WHERE atn.action_tracker_id = #{tracked_action.id}))")
24 24  
25 25 if tracked_action.user.is_a? Organization
26 26 ActionTrackerNotification.connection.execute "insert into action_tracker_notifications(profile_id, action_tracker_id) " +
... ...
app/mailers/contact.rb
... ... @@ -47,8 +47,8 @@ class Contact
47 47 content_type: 'text/html',
48 48 to: contact.dest.notification_emails,
49 49 reply_to: contact.email,
50   - subject: "[#{contact.dest.short_name(30)}] " + contact.subject,
51   - from: "#{contact.name} <#{contact.dest.environment.noreply_email}>"
  50 + subject: "[#{contact.dest.short_name(30)}] #{contact.subject}".html_safe,
  51 + from: "#{contact.name} <#{contact.dest.environment.noreply_email}>".html_safe
52 52 }
53 53  
54 54 if contact.sender
... ...
app/mailers/environment_mailing.rb
... ... @@ -30,7 +30,7 @@ class EnvironmentMailing &lt; Mailing
30 30 end
31 31  
32 32 def signature_message
33   - _('Sent by %s.') % source.name
  33 + _('Sent by %s.').html_safe % source.name
34 34 end
35 35  
36 36 def url
... ...
app/mailers/mailing.rb
... ... @@ -2,7 +2,8 @@ require_dependency &#39;mailing_job&#39;
2 2  
3 3 class Mailing < ApplicationRecord
4 4  
5   - acts_as_having_settings :field => :data
  5 + extend ActsAsHavingSettings::ClassMethods
  6 + acts_as_having_settings field: :data
6 7  
7 8 attr_accessible :subject, :body, :data
8 9  
... ... @@ -23,11 +24,11 @@ class Mailing &lt; ApplicationRecord
23 24 end
24 25  
25 26 def generate_from
26   - "#{source.name} <#{if source.is_a? Environment then source.noreply_email else source.contact_email end}>"
  27 + "#{source.name} <#{if source.is_a? Environment then source.noreply_email else source.contact_email end}>".html_safe
27 28 end
28 29  
29 30 def generate_subject
30   - '[%s] %s' % [source.name, subject]
  31 + '[%s] %s'.html_safe % [source.name, subject]
31 32 end
32 33  
33 34 def signature_message
... ...
app/mailers/organization_mailing.rb
... ... @@ -30,7 +30,7 @@ class OrganizationMailing &lt; Mailing
30 30 end
31 31  
32 32 def signature_message
33   - _('Sent by community %s.') % source.name
  33 + _('Sent by community %s.').html_safe % source.name
34 34 end
35 35  
36 36 include Rails.application.routes.url_helpers
... ...
app/mailers/pending_task_notifier.rb
... ... @@ -12,8 +12,8 @@ class PendingTaskNotifier &lt; ApplicationMailer
12 12  
13 13 mail(
14 14 to: person.email,
15   - from: "#{person.environment.name} <#{person.environment.noreply_email}>",
16   - subject: _("[%s] Pending tasks") % person.environment.name
  15 + from: "#{person.environment.name} <#{person.environment.noreply_email}>".html_safe,
  16 + subject: _("[%s] Pending tasks").html_safe % person.environment.name
17 17 )
18 18 end
19 19  
... ...
app/mailers/scrap_notifier.rb
... ... @@ -14,8 +14,8 @@ class ScrapNotifier &lt; ApplicationMailer
14 14 @url = sender.environment.top_url
15 15 mail(
16 16 to: receiver.email,
17   - from: "#{sender.environment.name} <#{sender.environment.noreply_email}>",
18   - subject: _("[%s] You received a scrap!") % [sender.environment.name]
  17 + from: "#{sender.environment.name} <#{sender.environment.noreply_email}>".html_safe,
  18 + subject: _("[%s] You received a scrap!").html_safe % [sender.environment.name]
19 19 )
20 20 end
21 21 end
... ...
app/mailers/task_mailer.rb
... ... @@ -14,7 +14,7 @@ class TaskMailer &lt; ApplicationMailer
14 14 mail(
15 15 to: task.target.notification_emails.compact,
16 16 from: self.class.generate_from(task),
17   - subject: "[%s] %s" % [task.environment.name, task.target_notification_description]
  17 + subject: "[%s] %s".html_safe % [task.environment.name, task.target_notification_description]
18 18 )
19 19 end
20 20  
... ... @@ -27,7 +27,7 @@ class TaskMailer &lt; ApplicationMailer
27 27 mail(
28 28 to: task.friend_email,
29 29 from: self.class.generate_from(task),
30   - subject: '[%s] %s' % [ task.requestor.environment.name, task.target_notification_description ]
  30 + subject: '[%s] %s'.html_safe % [ task.requestor.environment.name, task.target_notification_description ]
31 31 )
32 32 end
33 33  
... ... @@ -43,7 +43,7 @@ class TaskMailer &lt; ApplicationMailer
43 43 mail_with_template(
44 44 to: task.requestor.notification_emails,
45 45 from: self.class.generate_from(task),
46   - subject: '[%s] %s' % [task.requestor.environment.name, task.target_notification_description],
  46 + subject: '[%s] %s'.html_safe % [task.requestor.environment.name, task.target_notification_description],
47 47 email_template: task.email_template,
48 48 template_params: {:environment => task.requestor.environment, :task => task, :message => @message, :url => @url, :requestor => task.requestor}
49 49 )
... ...
app/mailers/user_mailer.rb
... ... @@ -13,8 +13,8 @@ class UserMailer &lt; ApplicationMailer
13 13  
14 14 mail(
15 15 to: user_email,
16   - from: "#{user.environment.name} <#{user.environment.contact_email}>",
17   - subject: _("[%{environment}] Welcome to %{environment} mail!") % { :environment => user.environment.name }
  16 + from: "#{user.environment.name} <#{user.environment.contact_email}>".html_safe,
  17 + subject: _("[%{environment}] Welcome to %{environment} mail!").html_safe % { :environment => user.environment.name }
18 18 )
19 19 end
20 20  
... ... @@ -30,7 +30,7 @@ class UserMailer &lt; ApplicationMailer
30 30 mail_with_template(
31 31 from: "#{user.environment.name} <#{user.environment.contact_email}>",
32 32 to: user.email,
33   - subject: _("[%s] Activate your account") % [user.environment.name],
  33 + subject: _("[%s] Activate your account").html_safe % [user.environment.name],
34 34 template_params: {:environment => user.environment, :activation_code => @activation_code, :redirection => @redirection, :join => @join, :person => user.person, :url => @url},
35 35 email_template: user.environment.email_templates.find_by_template_type(:user_activation),
36 36 )
... ... @@ -44,8 +44,8 @@ class UserMailer &lt; ApplicationMailer
44 44 mail(
45 45 content_type: 'text/html',
46 46 to: user.email,
47   - from: "#{user.environment.name} <#{user.environment.contact_email}>",
48   - subject: email_subject.blank? ? _("Welcome to environment %s") % [user.environment.name] : email_subject,
  47 + from: "#{user.environment.name} <#{user.environment.contact_email}>".html_safe,
  48 + subject: email_subject.blank? ? _("Welcome to environment %s").html_safe % [user.environment.name] : email_subject,
49 49 body: @body
50 50 )
51 51 end
... ... @@ -63,8 +63,8 @@ class UserMailer &lt; ApplicationMailer
63 63 mail(
64 64 content_type: 'text/html',
65 65 to: user.email,
66   - from: "#{user.environment.name} <#{user.environment.contact_email}>",
67   - subject: _("[%s] What about grow up your network?") % user.environment.name
  66 + from: "#{user.environment.name} <#{user.environment.contact_email}>".html_safe,
  67 + subject: _("[%s] What about grow up your network?").html_safe % user.environment.name
68 68 )
69 69 end
70 70  
... ...
app/models/abuse_complaint.rb
... ... @@ -25,7 +25,7 @@ class AbuseComplaint &lt; Task
25 25 end
26 26  
27 27 def title
28   - abuse_reports.count > 1 ? (_('Abuse complaint (%s)') % abuse_reports.count) :_('Abuse complaint')
  28 + abuse_reports.count > 1 ? (_('Abuse complaint (%s)').html_safe % abuse_reports.count) :_('Abuse complaint')
29 29 end
30 30  
31 31 def linked_subject
... ... @@ -57,15 +57,15 @@ class AbuseComplaint &lt; Task
57 57 end
58 58  
59 59 def task_activated_message
60   - _('Your profile was reported by the users of %s due to inappropriate behavior. The administrators of the environment are now reviewing the report. To solve this misunderstanding, please contact the administrators.') % environment.name
  60 + _('Your profile was reported by the users of %s due to inappropriate behavior. The administrators of the environment are now reviewing the report. To solve this misunderstanding, please contact the administrators.').html_safe % environment.name
61 61 end
62 62  
63 63 def task_finished_message
64   - _('Your profile was disabled by the administrators of %s due to inappropriate behavior. To solve this misunderstanding please contact them.') % environment.name
  64 + _('Your profile was disabled by the administrators of %s due to inappropriate behavior. To solve this misunderstanding please contact them.').html_safe % environment.name
65 65 end
66 66  
67 67 def target_notification_description
68   - _('%s was reported due to inappropriate behavior.') % reported.name
  68 + _('%s was reported due to inappropriate behavior.').html_safe % reported.name
69 69 end
70 70  
71 71 def target_notification_message
... ...
app/models/add_member.rb
... ... @@ -22,6 +22,7 @@ class AddMember &lt; Task
22 22 self.roles = [Profile::Roles.member(organization.environment.id).id]
23 23 end
24 24 target.affiliate(requestor, self.roles.select{|r| !r.to_i.zero? }.map{|i| Role.find(i)})
  25 + person.follow(organization, Circle.find_or_create_by(:person => person, :name =>_('memberships'), :profile_type => 'Community'))
25 26 end
26 27  
27 28 def title
... ... @@ -56,7 +57,7 @@ class AddMember &lt; Task
56 57 def target_notification_description
57 58 requestor_email = " (#{requestor.email})" if requestor.may_display_field_to?("email")
58 59  
59   - _("%{requestor}%{requestor_email} wants to be a member of '%{organization}'.") % {:requestor => requestor.name, :requestor_email => requestor_email, :organization => organization.name}
  60 + _("%{requestor}%{requestor_email} wants to be a member of '%{organization}'.").html_safe % {:requestor => requestor.name, :requestor_email => requestor_email, :organization => organization.name}
60 61 end
61 62  
62 63 def target_notification_message
... ...
app/models/article.rb
... ... @@ -13,7 +13,9 @@ class Article &lt; ApplicationRecord
13 13 :image_builder, :show_to_followers, :archived,
14 14 :author, :display_preview, :published_at, :person_followers
15 15  
  16 + extend ActsAsHavingImage::ClassMethods
16 17 acts_as_having_image
  18 +
17 19 include Noosfero::Plugin::HotSpot
18 20  
19 21 SEARCHABLE_FIELDS = {
... ... @@ -91,7 +93,8 @@ class Article &lt; ApplicationRecord
91 93 has_many :article_categorizations_including_virtual, :class_name => 'ArticleCategorization'
92 94 has_many :categories_including_virtual, :through => :article_categorizations_including_virtual, :source => :category
93 95  
94   - acts_as_having_settings :field => :setting
  96 + extend ActsAsHavingSettings::ClassMethods
  97 + acts_as_having_settings field: :setting
95 98  
96 99 settings_items :display_hits, :type => :boolean, :default => true
97 100 settings_items :author_name, :type => :string, :default => ""
... ... @@ -242,6 +245,7 @@ class Article &lt; ApplicationRecord
242 245 acts_as_taggable
243 246 N_('Tag list')
244 247  
  248 + extend ActsAsFilesystem::ActsMethods
245 249 acts_as_filesystem
246 250  
247 251 acts_as_versioned
... ... @@ -534,13 +538,13 @@ class Article &lt; ApplicationRecord
534 538  
535 539 scope :display_filter, lambda {|user, profile|
536 540 return published if (user.nil? && profile && profile.public?)
537   - return [] if user.nil? || (profile && !profile.public? && !user.follows?(profile))
  541 + return [] if user.nil? || (profile && !profile.public? && !profile.in_social_circle?(user))
538 542 where(
539 543 [
540 544 "published = ? OR last_changed_by_id = ? OR profile_id = ? OR ?
541 545 OR (show_to_followers = ? AND ? AND profile_id IN (?))", true, user.id, user.id,
542 546 profile.nil? ? false : user.has_permission?(:view_private_content, profile),
543   - true, (profile.nil? ? true : user.follows?(profile)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
  547 + true, (profile.nil? ? true : profile.in_social_circle?(user)), ( profile.nil? ? (user.friends.select('profiles.id')) : [profile.id])
544 548 ]
545 549 )
546 550 }
... ...
app/models/block.rb
... ... @@ -17,6 +17,7 @@ class Block &lt; ApplicationRecord
17 17 belongs_to :mirror_block, :class_name => "Block"
18 18 has_many :observers, :class_name => "Block", :foreign_key => "mirror_block_id"
19 19  
  20 + extend ActsAsHavingSettings::ClassMethods
20 21 acts_as_having_settings
21 22  
22 23 scope :enabled, -> { where :enabled => true }
... ... @@ -88,7 +89,7 @@ class Block &lt; ApplicationRecord
88 89 end
89 90  
90 91 def display_to_user?(user)
91   - display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && user.follows?(owner))
  92 + display_user == 'all' || (user.nil? && display_user == 'not_logged') || (user && display_user == 'logged') || (user && display_user == 'followers' && owner.in_social_circle?(user))
92 93 end
93 94  
94 95 def display_always(context)
... ...
app/models/blog.rb
... ... @@ -2,7 +2,9 @@ class Blog &lt; Folder
2 2  
3 3 attr_accessible :visualization_format
4 4  
  5 + extend ActsAsHavingPosts::ClassMethods
5 6 acts_as_having_posts
  7 +
6 8 include PostsLimit
7 9  
8 10 #FIXME This should be used until there is a migration to fix all blogs that
... ...
app/models/category.rb
... ... @@ -21,6 +21,7 @@ class Category &lt; ApplicationRecord
21 21  
22 22 scope :on_level, -> parent { where :parent_id => parent }
23 23  
  24 + extend ActsAsFilesystem::ActsMethods
24 25 acts_as_filesystem
25 26  
26 27 has_many :article_categorizations
... ... @@ -35,6 +36,7 @@ class Category &lt; ApplicationRecord
35 36 has_many :people, :through => :profile_categorizations, :source => :profile, :class_name => 'Person'
36 37 has_many :communities, :through => :profile_categorizations, :source => :profile, :class_name => 'Community'
37 38  
  39 + extend ActsAsHavingImage::ClassMethods
38 40 acts_as_having_image
39 41  
40 42 before_save :normalize_display_color
... ...
app/models/circle.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +class Circle < ApplicationRecord
  2 + has_many :profile_followers
  3 + belongs_to :person
  4 +
  5 + attr_accessible :name, :person, :profile_type
  6 +
  7 + validates :name, presence: true
  8 + validates :person_id, presence: true
  9 + validates :profile_type, presence: true
  10 + validates :person_id, :uniqueness => {:scope => :name, :message => "can't add two circles with the same name"}
  11 +
  12 + validate :profile_type_must_be_in_list
  13 +
  14 + scope :by_owner, -> person{
  15 + where(:person => person)
  16 + }
  17 +
  18 + scope :with_name, -> name{
  19 + where(:name => name)
  20 + }
  21 +
  22 + def self.profile_types
  23 + {
  24 + _("Person") => Person.name,
  25 + _("Community") => Community.name,
  26 + _("Enterprise") => Enterprise.name
  27 + }
  28 + end
  29 +
  30 + def profile_type_must_be_in_list
  31 + valid_profile_types = Circle.profile_types.values
  32 + unless self.profile_type.in? valid_profile_types
  33 + self.errors.add(:profile_type, "invalid profile type")
  34 + end
  35 + end
  36 +
  37 +end
... ...
app/models/comment.rb
... ... @@ -38,6 +38,7 @@ class Comment &lt; ApplicationRecord
38 38  
39 39 validate :article_archived?
40 40  
  41 + extend ActsAsHavingSettings::ClassMethods
41 42 acts_as_having_settings
42 43  
43 44 xss_terminate :only => [ :body, :title, :name ], :on => 'validation'
... ...
app/models/community.rb
... ... @@ -2,7 +2,7 @@ class Community &lt; Organization
2 2  
3 3 attr_accessible :accessor_id, :accessor_type, :role_id, :resource_id, :resource_type
4 4 attr_accessible :address_reference, :district, :tag_list, :language, :description
5   - attr_accessible :requires_email
  5 +
6 6 after_destroy :check_invite_member_for_destroy
7 7  
8 8 def self.type_name
... ... @@ -13,9 +13,6 @@ class Community &lt; Organization
13 13 N_('Language')
14 14  
15 15 settings_items :language
16   - settings_items :requires_email, :type => :boolean
17   -
18   - alias_method :requires_email?, :requires_email
19 16  
20 17 extend SetProfileRegionFromCityState::ClassMethods
21 18 set_profile_region_from_city_state
... ...
app/models/concerns/acts_as_filesystem.rb 0 → 100644
... ... @@ -0,0 +1,265 @@
  1 +module ActsAsFilesystem
  2 +
  3 + module ActsMethods
  4 +
  5 + # Declares the ActiveRecord model to acts like a filesystem: objects are
  6 + # arranged in a tree (liks acts_as_tree), and . The underlying table must
  7 + # have the following fields:
  8 + #
  9 + # * name (+:string+) - the title of the object
  10 + # * slug (+:string+)- the title turned in a URL-friendly string (downcased,
  11 + # non-ascii chars transliterated into ascii, all sequences of
  12 + # non-alphanumericd characters changed into dashed)
  13 + # * path (+:text+)- stores the full path of the object (the full path of
  14 + # the parent, a "/" and the slug of the object)
  15 + # * children_count - a cache of the number of children elements.
  16 + def acts_as_filesystem
  17 + # a filesystem is a tree
  18 + acts_as_tree :counter_cache => :children_count
  19 +
  20 + extend ClassMethods
  21 + include InstanceMethods
  22 + if self.has_path?
  23 + after_update :update_children_path
  24 + before_create :set_path
  25 + include InstanceMethods::PathMethods
  26 + end
  27 +
  28 + before_save :set_ancestry
  29 + end
  30 +
  31 + end
  32 +
  33 + module ClassMethods
  34 +
  35 + def build_ancestry(parent_id = nil, ancestry = '')
  36 + ActiveRecord::Base.transaction do
  37 + self.base_class.where(parent_id: parent_id).each do |node|
  38 + node.update_column :ancestry, ancestry
  39 +
  40 + build_ancestry node.id, (ancestry.empty? ? "#{node.formatted_ancestry_id}" :
  41 + "#{ancestry}#{node.ancestry_sep}#{node.formatted_ancestry_id}")
  42 + end
  43 + end
  44 +
  45 + #raise "Couldn't reach and set ancestry on every record" if self.base_class.where('ancestry is null').count != 0
  46 + end
  47 +
  48 + def has_path?
  49 + (['name', 'slug', 'path'] - self.column_names).blank?
  50 + end
  51 +
  52 + end
  53 +
  54 + module InstanceMethods
  55 +
  56 + def ancestry_column
  57 + 'ancestry'
  58 + end
  59 + def ancestry_sep
  60 + '.'
  61 + end
  62 + def has_ancestry?
  63 + self.class.column_names.include? self.ancestry_column
  64 + end
  65 +
  66 + def formatted_ancestry_id
  67 + "%010d" % self.id if self.id
  68 + end
  69 +
  70 + def ancestry
  71 + self[ancestry_column]
  72 + end
  73 + def ancestor_ids
  74 + return nil if !has_ancestry? or ancestry.nil?
  75 + @ancestor_ids ||= ancestry.split(ancestry_sep).map{ |id| id.to_i }
  76 + end
  77 +
  78 + def ancestry=(value)
  79 + self[ancestry_column] = value
  80 + end
  81 + def set_ancestry
  82 + return unless self.has_ancestry?
  83 + if self.ancestry.nil? or (new_record? or parent_id_changed?) or recalculate_path
  84 + self.ancestry = self.hierarchy(true)[0...-1].map{ |p| p.formatted_ancestry_id }.join(ancestry_sep)
  85 + end
  86 + end
  87 +
  88 + def descendents_options
  89 + ["#{self.ancestry_column} LIKE ?", "%#{self.formatted_ancestry_id}%"]
  90 + end
  91 + def descendents
  92 + self.class.where descendents_options
  93 + end
  94 +
  95 + # calculates the level of the record in the records hierarchy. Top-level
  96 + # records have level 0; the children of the top-level records have
  97 + # level 1; the children of records with level 1 have level 2, and so on.
  98 + #
  99 + # A level 0
  100 + # / \
  101 + # B C level 1
  102 + # / \ / \
  103 + # E F G H level 2
  104 + # ...
  105 + def level
  106 + self.hierarchy.size - 1
  107 + end
  108 +
  109 + # Is this record a top-level record?
  110 + def top_level?
  111 + self.parent.nil?
  112 + end
  113 +
  114 + # Is this record a leaf in the hierarchy tree of records?
  115 + #
  116 + # Being a leaf means that this record has no subrecord.
  117 + def leaf?
  118 + self.children.empty?
  119 + end
  120 +
  121 + def top_ancestor
  122 + if has_ancestry? and !ancestry.blank?
  123 + self.class.base_class.find_by id: self.top_ancestor_id
  124 + else
  125 + self.hierarchy.first
  126 + end
  127 + end
  128 + def top_ancestor_id
  129 + if has_ancestry? and !ancestry.nil?
  130 + self.ancestor_ids.first
  131 + else
  132 + self.hierarchy.first.id
  133 + end
  134 + end
  135 +
  136 + # returns the full hierarchy from the top-level item to this one. For
  137 + # example, if item1 has a children item2 and item2 has a children item3,
  138 + # then item3's hierarchy would be [item1, item2, item3].
  139 + #
  140 + # If +reload+ is passed as +true+, then the hierarchy is reload (usefull
  141 + # when the ActiveRecord object was modified in some way, or just after
  142 + # changing parent)
  143 + def hierarchy(reload = false)
  144 + @hierarchy = nil if reload or recalculate_path
  145 +
  146 + if @hierarchy.nil?
  147 + @hierarchy = []
  148 +
  149 + if !reload and !recalculate_path and ancestor_ids
  150 + objects = self.class.base_class.where(id: ancestor_ids)
  151 + ancestor_ids.each{ |id| @hierarchy << objects.find{ |t| t.id == id } }
  152 + @hierarchy << self
  153 + else
  154 + item = self
  155 + while item
  156 + @hierarchy.unshift(item)
  157 + item = item.parent
  158 + end
  159 + end
  160 + end
  161 +
  162 + @hierarchy
  163 + end
  164 +
  165 + def map_traversal(&block)
  166 + result = []
  167 + current_level = [self]
  168 +
  169 + while !current_level.empty?
  170 + result += current_level
  171 + ids = current_level.select {|item| item.children_count > 0}.map(&:id)
  172 + break if ids.empty?
  173 + current_level = self.class.base_class.where(parent_id: ids)
  174 + end
  175 + block ||= (lambda { |x| x })
  176 + result.map(&block)
  177 + end
  178 +
  179 + def all_children
  180 + res = map_traversal
  181 + res.shift
  182 + res
  183 + end
  184 +
  185 + #####
  186 + # Path methods
  187 + # These methods are used when _path_, _name_ and _slug_ attributes exist
  188 + # and should be calculated based on the tree
  189 + #####
  190 + module PathMethods
  191 + # used to know when to trigger batch renaming
  192 + attr_accessor :recalculate_path
  193 +
  194 + # calculates the full path to this record using parent's path.
  195 + def calculate_path
  196 + self.hierarchy.map{ |obj| obj.slug }.join('/')
  197 + end
  198 + def set_path
  199 + if self.path == self.slug && !self.top_level?
  200 + self.path = self.calculate_path
  201 + end
  202 + end
  203 + def explode_path
  204 + path.split(/\//)
  205 + end
  206 +
  207 + def update_children_path
  208 + if self.recalculate_path
  209 + self.children.each do |child|
  210 + child.path = child.calculate_path
  211 + child.recalculate_path = true
  212 + child.save!
  213 + end
  214 + end
  215 + self.recalculate_path = false
  216 + end
  217 +
  218 + # calculates the full name of a record by accessing the name of all its
  219 + # ancestors.
  220 + #
  221 + # If you have this record hierarchy:
  222 + # Record "A"
  223 + # Record "B"
  224 + # Record "C"
  225 + #
  226 + # Then Record "C" will have "A/B/C" as its full name.
  227 + def full_name(sep = '/')
  228 + self.hierarchy.map {|item| item.name || '?' }.join(sep)
  229 + end
  230 +
  231 + # gets the name without leading parents. Useful when dividing records
  232 + # in top-level groups and full names must not include the top-level
  233 + # record which is already a emphasized label
  234 + def full_name_without_leading(count, sep = '/')
  235 + parts = self.full_name(sep).split(sep)
  236 + count.times { parts.shift }
  237 + parts.join(sep)
  238 + end
  239 +
  240 + def set_name(value)
  241 + if self.name != value
  242 + self.recalculate_path = true
  243 + end
  244 + self[:name] = value
  245 + end
  246 +
  247 + # sets the name of the record. Also sets #slug accordingly.
  248 + def name=(value)
  249 + self.set_name(value)
  250 + unless self.name.blank?
  251 + self.slug = self.name.to_slug
  252 + end
  253 + end
  254 +
  255 + # sets the slug of the record. Also sets the path with the new slug value.
  256 + def slug=(value)
  257 + self[:slug] = value
  258 + unless self.slug.blank?
  259 + self.path = self.calculate_path
  260 + end
  261 + end
  262 + end
  263 + end
  264 +end
  265 +
... ...
app/models/concerns/acts_as_having_boxes.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +module ActsAsHavingBoxes
  2 +
  3 + module ClassMethods
  4 + def acts_as_having_boxes
  5 + has_many :boxes, -> { order :position }, as: :owner, dependent: :destroy
  6 + self.send(:include, ActsAsHavingBoxes)
  7 + end
  8 + end
  9 +
  10 + module BlockArray
  11 + def find(id)
  12 + select { |item| item.id == id.to_i }.first
  13 + end
  14 + end
  15 +
  16 + def blocks(reload = false)
  17 + if (reload)
  18 + @blocks = nil
  19 + end
  20 + if @blocks.nil?
  21 + @blocks = boxes.includes(:blocks).inject([]) do |acc,obj|
  22 + acc.concat(obj.blocks)
  23 + end
  24 + @blocks.send(:extend, BlockArray)
  25 + end
  26 + @blocks
  27 + end
  28 +
  29 + # returns 3 unless the class table has a boxes_limit column. In that case
  30 + # return the value of the column.
  31 + def boxes_limit layout_template = nil
  32 + layout_template ||= self.layout_template
  33 + @boxes_limit ||= LayoutTemplate.find(layout_template).number_of_boxes || 3
  34 + end
  35 +
  36 +end
  37 +
... ...
app/models/concerns/acts_as_having_image.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +module ActsAsHavingImage
  2 +
  3 + module ClassMethods
  4 + def acts_as_having_image
  5 + belongs_to :image, dependent: :destroy
  6 + scope :with_image, -> { where "#{table_name}.image_id IS NOT NULL" }
  7 + scope :without_image, -> { where "#{table_name}.image_id IS NULL" }
  8 + attr_accessible :image_builder
  9 + include ActsAsHavingImage
  10 + end
  11 + end
  12 +
  13 + def image_builder=(img)
  14 + if image && image.id == img[:id]
  15 + image.attributes = img
  16 + else
  17 + build_image(img)
  18 + end unless img[:uploaded_data].blank?
  19 + if img[:remove_image] == 'true'
  20 + self.image_id = nil
  21 + end
  22 + end
  23 +
  24 +end
  25 +
... ...
app/models/concerns/acts_as_having_posts.rb 0 → 100644
... ... @@ -0,0 +1,49 @@
  1 +module ActsAsHavingPosts
  2 +
  3 + module ClassMethods
  4 + def acts_as_having_posts(scope = nil)
  5 + has_many :posts, -> {
  6 + s = order('published_at DESC, id DESC').where('articles.type != ?', 'RssFeed')
  7 + s = s.instance_exec(&scope) if scope
  8 + s
  9 + }, class_name: 'Article', foreign_key: 'parent_id', source: :children
  10 +
  11 + attr_accessor :feed_attrs
  12 +
  13 + after_create do |blog|
  14 + blog.children << RssFeed.new(:name => 'feed', :profile => blog.profile)
  15 + blog.feed = blog.feed_attrs
  16 + end
  17 +
  18 + settings_items :posts_per_page, :type => :integer, :default => 5
  19 +
  20 + self.send(:include, ActsAsHavingPosts)
  21 + end
  22 + end
  23 +
  24 + def has_posts?
  25 + true
  26 + end
  27 +
  28 + def feed
  29 + children.where(:type => 'RssFeed').first
  30 + end
  31 +
  32 + def feed=(attrs)
  33 + if attrs
  34 + if self.feed
  35 + self.feed.update(attrs)
  36 + else
  37 + self.feed_attrs = attrs
  38 + end
  39 + end
  40 + self.feed
  41 + end
  42 +
  43 + def name=(value)
  44 + self.set_name(value)
  45 + self.slug = self.slug.blank? ? self.name.to_slug : self.slug.to_slug
  46 + end
  47 +
  48 +end
  49 +
... ...
app/models/concerns/acts_as_having_settings.rb 0 → 100644
... ... @@ -0,0 +1,89 @@
  1 +# declare missing types
  2 +module ActiveRecord
  3 + module Type
  4 + class Symbol < Value
  5 + def cast_value value
  6 + value.to_sym
  7 + end
  8 + end
  9 + class Array < Value
  10 + def cast_value value
  11 + ::Array.wrap(value)
  12 + end
  13 + end
  14 + class Hash < Value
  15 + def cast_value value
  16 + h = ::Hash[value]
  17 + h.symbolize_keys!
  18 + h
  19 + end
  20 + end
  21 + end
  22 +end
  23 +
  24 +module ActsAsHavingSettings
  25 +
  26 + def self.type_cast value, type
  27 + # do not cast nil
  28 + return value if value.nil?
  29 + type.send :cast_value, value
  30 + end
  31 +
  32 + module ClassMethods
  33 +
  34 + def acts_as_having_settings(*args)
  35 + options = args.last.is_a?(Hash) ? args.pop : {}
  36 + field = (options[:field] || :settings).to_sym
  37 +
  38 + serialize field, Hash
  39 + class_attribute :settings_field
  40 + self.settings_field = field
  41 +
  42 + class_eval do
  43 + def settings_field
  44 + self[self.class.settings_field] ||= Hash.new
  45 + end
  46 +
  47 + def setting_changed? setting_field
  48 + setting_field = setting_field.to_sym
  49 + changed_settings = self.changes[self.class.settings_field]
  50 + return false if changed_settings.nil?
  51 +
  52 + old_setting_value = changed_settings.first.nil? ? nil : changed_settings.first[setting_field]
  53 + new_setting_value = changed_settings.last[setting_field]
  54 + old_setting_value != new_setting_value
  55 + end
  56 + end
  57 +
  58 + settings_items *args
  59 + end
  60 +
  61 + def settings_items *names
  62 +
  63 + options = names.extract_options!
  64 + default = options[:default]
  65 + type = options[:type]
  66 + type = if type.present? then ActiveRecord::Type.const_get(type.to_s.camelize.to_sym).new else nil end
  67 +
  68 + names.each do |setting|
  69 + # symbolize key
  70 + setting = setting.to_sym
  71 +
  72 + define_method setting do
  73 + h = send self.class.settings_field
  74 + val = h[setting]
  75 + # translate default value if it is used
  76 + if not val.nil? then val elsif default.is_a? String then gettext default else default end
  77 + end
  78 +
  79 + define_method "#{setting}=" do |value|
  80 + h = send self.class.settings_field
  81 + h[setting] = if type then ActsAsHavingSettings.type_cast value, type else value end
  82 + end
  83 + end
  84 + end
  85 +
  86 + end
  87 +
  88 +end
  89 +
... ...
app/models/concerns/code_numbering.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +module CodeNumbering
  2 + module ClassMethods
  3 + def code_numbering field, options = {}
  4 + class_attribute :code_numbering_field
  5 + class_attribute :code_numbering_options
  6 +
  7 + self.code_numbering_field = field
  8 + self.code_numbering_options = options
  9 +
  10 + before_create :create_code_numbering
  11 +
  12 + include CodeNumbering::InstanceMethods
  13 + end
  14 + end
  15 +
  16 + module InstanceMethods
  17 +
  18 + def code
  19 + self.attributes[self.code_numbering_field.to_s]
  20 + end
  21 +
  22 + def code_scope
  23 + scope = self.code_numbering_options[:scope]
  24 + case scope
  25 + when Symbol
  26 + self.send scope
  27 + when Proc
  28 + instance_exec &scope
  29 + else
  30 + self.class
  31 + end
  32 + end
  33 +
  34 + def code_maximum
  35 + self.code_scope.maximum(self.code_numbering_field) || 0
  36 + end
  37 +
  38 + def create_code_numbering
  39 + max = self.code_numbering_options[:start].to_i - 1 if self.code_numbering_options[:start]
  40 + max = self.code_maximum
  41 + self.send "#{self.code_numbering_field}=", max+1
  42 + end
  43 +
  44 + def reset_scope_code_numbering
  45 + max = self.code_numbering_options[:start].to_i - 1 if self.code_numbering_options[:start]
  46 + max ||= 1
  47 +
  48 + self.code_scope.order(:created_at).each do |record|
  49 + record.update_column self.code_numbering_field, max
  50 + max += 1
  51 + end
  52 + self.reload
  53 + end
  54 +
  55 + end
  56 +end
  57 +
... ...
app/models/concerns/customizable.rb 0 → 100644
... ... @@ -0,0 +1,124 @@
  1 +module Customizable
  2 +
  3 + def self.included(base)
  4 + base.attr_accessible :custom_values
  5 + base.extend ClassMethods
  6 + end
  7 +
  8 + module ClassMethods
  9 + def acts_as_customizable(options = {})
  10 + attr_accessor :custom_values
  11 + has_many :custom_field_values, :dependent => :delete_all, :as => :customized
  12 + send :include, Customizable::InstanceMethods
  13 + after_save :save_custom_values
  14 + validate :valid_custom_values?
  15 + end
  16 +
  17 + def active_custom_fields environment
  18 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.active}
  19 + end
  20 +
  21 + def required_custom_fields environment
  22 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.required}
  23 + end
  24 +
  25 + def signup_custom_fields environment
  26 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.signup}
  27 + end
  28 +
  29 + def custom_fields environment
  30 + environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type)}
  31 + end
  32 +
  33 + def customized_ancestors_list
  34 + current=self
  35 + result=[]
  36 + while current.instance_methods.include? :custom_value do
  37 + result << current.name
  38 + current=current.superclass
  39 + end
  40 + result
  41 + end
  42 +
  43 + end
  44 +
  45 + module InstanceMethods
  46 +
  47 + def valid_custom_values?
  48 + is_valid = true
  49 + parse_custom_values.each do |cv|
  50 + unless cv.valid?
  51 + name = cv.custom_field.name
  52 + errors.add(name, cv.errors.messages[name.to_sym].first)
  53 + is_valid = false
  54 + end
  55 + end
  56 + is_valid
  57 + end
  58 +
  59 + def customized_class
  60 + current=self.class
  61 + while current.instance_methods.include? :custom_fields do
  62 + result=current
  63 + current=current.superclass
  64 + end
  65 + result.name
  66 + end
  67 +
  68 + def is_public(field_name)
  69 + cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
  70 + cv.nil? ? false : cv.public
  71 + end
  72 +
  73 + def public_values
  74 + self.custom_field_values.select{|cv| cv.public}
  75 + end
  76 +
  77 + def custom_value(field_name)
  78 + cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
  79 + cv.nil? ? default_value_for(field_name) : cv.value
  80 + end
  81 +
  82 + def default_value_for(field_name)
  83 + field=self.class.custom_fields(environment).detect {|c| c.name == field_name}
  84 + field.nil? ? nil : field.default_value
  85 + end
  86 +
  87 + def parse_custom_values
  88 + return_list = []
  89 + return return_list if custom_values.blank?
  90 + custom_values.each_pair do |key, value|
  91 + custom_field = environment.custom_fields.detect{|cf|cf.name==key}
  92 + next if custom_field.blank?
  93 + custom_field_value = self.custom_field_values(true).detect{|cv| cv.custom_field.name==key}
  94 +
  95 + if custom_field_value.nil?
  96 + custom_field_value = CustomFieldValue.new
  97 + custom_field_value.custom_field = custom_field
  98 + custom_field_value.customized = self
  99 + end
  100 +
  101 + if value.is_a?(Hash)
  102 + custom_field_value.value = value['value'].to_s
  103 + if value.has_key?('public')
  104 + is_public = value['public']=="true" || value['public']==true
  105 + custom_field_value.public = is_public
  106 + else
  107 + custom_field_value.public = false
  108 + end
  109 + else
  110 + custom_field_value.value = value.to_s
  111 + custom_field_value.public = false
  112 + end
  113 + return_list << custom_field_value
  114 + end
  115 + return_list
  116 + end
  117 +
  118 + def save_custom_values
  119 + parse_custom_values.each(&:save)
  120 + end
  121 +
  122 + end
  123 +end
  124 +
... ...
app/models/concerns/delayed_attachment_fu.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +module DelayedAttachmentFu
  2 +
  3 + module ClassMethods
  4 + def delay_attachment_fu_thumbnails
  5 + include DelayedAttachmentFu::InstanceMethods
  6 + after_create do |file|
  7 + if file.thumbnailable?
  8 + Delayed::Job.enqueue CreateThumbnailsJob.new(file.class.name, file.id)
  9 + end
  10 + end
  11 + end
  12 + end
  13 +
  14 + module InstanceMethods
  15 + # skip processing with RMagick
  16 + def process_attachment
  17 + end
  18 +
  19 + def after_process_attachment
  20 + save_to_storage
  21 + @temp_paths.clear
  22 + @saved_attachment = nil
  23 + run_callbacks :after_attachment_saved
  24 + end
  25 +
  26 + def create_thumbnails
  27 + if thumbnailable?
  28 + self.class.with_image(full_filename) do |img|
  29 + self.width = img.columns
  30 + self.height = img.rows
  31 + self.save!
  32 + end
  33 + self.class.attachment_options[:thumbnails].each do |suffix, size|
  34 + self.create_or_update_thumbnail(self.full_filename, suffix, size)
  35 + end
  36 + self.thumbnails_processed = true
  37 + self.save!
  38 + end
  39 + end
  40 +
  41 + def public_filename(size=nil)
  42 + force, size = true, nil if size == :uploaded
  43 + if !self.thumbnailable? || self.thumbnails_processed || force
  44 + super size
  45 + else
  46 + size ||= :thumb
  47 + '/images/icons-app/image-loading-%s.png' % size
  48 + end
  49 + end
  50 +
  51 +
  52 + end
  53 +end
  54 +
  55 +
... ...
app/models/concerns/set_profile_region_from_city_state.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +module SetProfileRegionFromCityState
  2 +
  3 + module ClassMethods
  4 + def set_profile_region_from_city_state
  5 + before_save :region_from_city_and_state
  6 +
  7 + include InstanceMethods
  8 + alias_method_chain :city=, :region
  9 + alias_method_chain :state=, :region
  10 + end
  11 + end
  12 +
  13 + module InstanceMethods
  14 + include Noosfero::Plugin::HotSpot
  15 +
  16 + def city_with_region=(value)
  17 + self.city_without_region = value
  18 + @change_region = true
  19 + end
  20 +
  21 + def state_with_region=(value)
  22 + self.state_without_region = value
  23 + @change_region = true
  24 + end
  25 +
  26 + def region_from_city_and_state
  27 + if @change_region
  28 + self.region = nil
  29 + state = search_region(State, self.state)
  30 + self.region = search_region(City.where(:parent_id => state.id), self.city) if state
  31 + end
  32 + end
  33 +
  34 + private
  35 +
  36 + def search_region(scope, query)
  37 + return nil if !query
  38 + query = query.downcase.strip
  39 + scope.where(['lower(name)=? OR lower(abbreviation)=? OR lower(acronym)=?', query, query, query]).first
  40 + end
  41 +
  42 + end
  43 +
  44 +end
... ...
app/models/concerns/translatable_content.rb 0 → 100644
... ... @@ -0,0 +1,8 @@
  1 +module TranslatableContent
  2 +
  3 + def translatable?
  4 + return false if self.profile && !self.profile.environment.languages.present?
  5 + parent.nil? || !parent.forum?
  6 + end
  7 +
  8 +end
... ...
app/models/concerns/white_list_filter.rb 0 → 100644
... ... @@ -0,0 +1,37 @@
  1 +module WhiteListFilter
  2 +
  3 + def check_iframe_on_content(content, trusted_sites)
  4 + if content.blank? || !content.include?('iframe')
  5 + return content
  6 + end
  7 + content.gsub!(/<iframe[^>]*>\s*<\/iframe>/i) do |iframe|
  8 + result = ''
  9 + unless iframe =~ /src=['"].*src=['"]/
  10 + trusted_sites.each do |trusted_site|
  11 + re_dom = trusted_site.gsub('.', '\.')
  12 + if iframe =~ /src=["'](https?:)?\/\/(www\.)?#{re_dom}\//
  13 + result = iframe
  14 + end
  15 + end
  16 + end
  17 + result
  18 + end
  19 + content
  20 + end
  21 +
  22 + module ClassMethods
  23 + def filter_iframes(*opts)
  24 + options = opts.last.is_a?(Hash) && opts.pop || {}
  25 + white_list_method = options[:whitelist] || :iframe_whitelist
  26 + opts.each do |field|
  27 + before_validation do |obj|
  28 + obj.check_iframe_on_content(obj.send(field), obj.send(white_list_method))
  29 + end
  30 + end
  31 + end
  32 + end
  33 +
  34 + def self.included(c)
  35 + c.send(:extend, WhiteListFilter::ClassMethods)
  36 + end
  37 +end
... ...
app/models/create_community.rb
... ... @@ -12,6 +12,7 @@ class CreateCommunity &lt; Task
12 12 attr_accessible :environment, :requestor, :target
13 13 attr_accessible :reject_explanation, :template_id
14 14  
  15 + extend ActsAsHavingImage::ClassMethods
15 16 acts_as_having_image
16 17  
17 18 DATA_FIELDS = Community.fields + ['name', 'closed', 'description']
... ...
app/models/enterprise.rb
... ... @@ -174,8 +174,4 @@ class Enterprise &lt; Organization
174 174 ''
175 175 end
176 176  
177   - def followed_by? person
178   - super or self.fans.where(id: person.id).count > 0
179   - end
180   -
181 177 end
... ...
app/models/environment.rb
... ... @@ -200,6 +200,7 @@ class Environment &lt; ApplicationRecord
200 200 # Relationships and applied behaviour
201 201 # #################################################
202 202  
  203 + extend ActsAsHavingBoxes::ClassMethods
203 204 acts_as_having_boxes
204 205  
205 206 after_create do |env|
... ... @@ -251,7 +252,8 @@ class Environment &lt; ApplicationRecord
251 252 # #################################################
252 253  
253 254 # store the Environment settings as YAML-serialized Hash.
254   - acts_as_having_settings :field => :settings
  255 + extend ActsAsHavingSettings::ClassMethods
  256 + acts_as_having_settings field: :settings
255 257  
256 258 # introduce and explain to users something about the signup
257 259 settings_items :signup_intro, :type => String
... ...
app/models/event.rb
1   -require 'noosfero/translatable_content'
2 1 require 'builder'
3 2  
4 3 class Event < Article
... ... @@ -138,7 +137,7 @@ class Event &lt; Article
138 137 false
139 138 end
140 139  
141   - include Noosfero::TranslatableContent
  140 + include TranslatableContent
142 141 include MaybeAddHttp
143 142  
144 143 end
... ...
app/models/favorite_enterprise_person.rb
... ... @@ -7,6 +7,10 @@ class FavoriteEnterprisePerson &lt; ApplicationRecord
7 7 belongs_to :enterprise
8 8 belongs_to :person
9 9  
  10 + after_create do |favorite|
  11 + favorite.person.follow(favorite.enterprise, Circle.find_or_create_by(:person => favorite.person, :name =>_('favorites'), :profile_type => 'Enterprise'))
  12 + end
  13 +
10 14 protected
11 15  
12 16 def is_trackable?
... ...
app/models/folder.rb
... ... @@ -10,7 +10,8 @@ class Folder &lt; Article
10 10 errors.add(:parent, "A folder should not belong to a blog.") if parent && parent.blog?
11 11 end
12 12  
13   - acts_as_having_settings :field => :setting
  13 + extend ActsAsHavingSettings::ClassMethods
  14 + acts_as_having_settings field: :setting
14 15  
15 16 xss_terminate :only => [ :name, :body ], :with => 'white_list', :on => 'validation'
16 17  
... ...
app/models/forum.rb
1 1 class Forum < Folder
2 2  
  3 + extend ActsAsHavingPosts::ClassMethods
3 4 acts_as_having_posts -> { reorder 'updated_at DESC' }
  5 +
4 6 include PostsLimit
5 7  
6 8 attr_accessible :has_terms_of_use, :terms_of_use, :topic_creation
... ...
app/models/friendship.rb
... ... @@ -9,11 +9,19 @@ class Friendship &lt; ApplicationRecord
9 9 after_create do |friendship|
10 10 Friendship.update_cache_counter(:friends_count, friendship.person, 1)
11 11 Friendship.update_cache_counter(:friends_count, friendship.friend, 1)
  12 +
  13 + circles = friendship.group.blank? ? ['friendships'] : friendship.group.split(',').map(&:strip)
  14 + circles.each do |circle|
  15 + friendship.person.follow(friendship.friend, Circle.find_or_create_by(:person => friendship.person, :name => circle, :profile_type => 'Person'))
  16 + end
12 17 end
13 18  
14 19 after_destroy do |friendship|
15 20 Friendship.update_cache_counter(:friends_count, friendship.person, -1)
16 21 Friendship.update_cache_counter(:friends_count, friendship.friend, -1)
  22 +
  23 + circle = Circle.find_by(:person => friendship.person, :name => (friendship.group.blank? ? 'friendships': friendship.group) )
  24 + friendship.person.remove_profile_from_circle(friendship.friend, circle) if circle
17 25 end
18 26  
19 27 def self.remove_friendship(person1, person2)
... ...
app/models/image.rb
... ... @@ -23,6 +23,7 @@ class Image &lt; ApplicationRecord
23 23  
24 24 validates_attachment :size => N_("{fn} of uploaded file was larger than the maximum size of 5.0 MB").fix_i18n
25 25  
  26 + extend DelayedAttachmentFu::ClassMethods
26 27 delay_attachment_fu_thumbnails
27 28  
28 29 postgresql_attachment_fu
... ...
app/models/organization.rb
... ... @@ -2,6 +2,10 @@
2 2 class Organization < 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 + attr_accessible :requires_email
  6 +
  7 + settings_items :requires_email, type: :boolean
  8 + alias_method :requires_email?, :requires_email
5 9  
6 10 SEARCH_FILTERS = {
7 11 :order => %w[more_recent more_popular more_active],
... ...
app/models/person.rb
... ... @@ -8,7 +8,6 @@ class Person &lt; Profile
8 8 :display => %w[compact]
9 9 }
10 10  
11   -
12 11 def self.type_name
13 12 _('Person')
14 13 end
... ... @@ -93,6 +92,7 @@ class Person &lt; Profile
93 92 has_many :following_articles, :class_name => 'Article', :through => :article_followers, :source => :article
94 93 has_many :friendships, :dependent => :destroy
95 94 has_many :friends, :class_name => 'Person', :through => :friendships
  95 + has_many :circles
96 96  
97 97 scope :online, -> {
98 98 joins(:user).where("users.chat_status != '' AND users.chat_status_at >= ?", DateTime.now - User.expires_chat_status_every.minutes)
... ... @@ -200,6 +200,33 @@ class Person &lt; Profile
200 200 end
201 201 end
202 202  
  203 + def follow(profile, circles)
  204 + circles = [circles] unless circles.is_a?(Array)
  205 + circles.each do |new_circle|
  206 + ProfileFollower.create(profile: profile, circle: new_circle)
  207 + end
  208 + end
  209 +
  210 + def update_profile_circles(profile, new_circles)
  211 + profile_circles = ProfileFollower.with_profile(profile).with_follower(self).map(&:circle)
  212 + circles_to_add = new_circles - profile_circles
  213 + circles_to_remove = profile_circles - new_circles
  214 + circles_to_add.each do |new_circle|
  215 + ProfileFollower.create(profile: profile, circle: new_circle)
  216 + end
  217 +
  218 + ProfileFollower.where('circle_id IN (?) AND profile_id = ?',
  219 + circles_to_remove.map(&:id), profile.id).destroy_all
  220 + end
  221 +
  222 + def unfollow(profile)
  223 + ProfileFollower.with_follower(self).with_profile(profile).destroy_all
  224 + end
  225 +
  226 + def remove_profile_from_circle(profile, circle)
  227 + ProfileFollower.with_profile(profile).with_circle(circle).destroy_all
  228 + end
  229 +
203 230 def already_request_friendship?(person)
204 231 person.tasks.where(requestor_id: self.id, type: 'AddFriend', status: Task::Status::ACTIVE).first
205 232 end
... ... @@ -580,9 +607,12 @@ class Person &lt; Profile
580 607 person.has_permission?(:manage_friends, self)
581 608 end
582 609  
583   - protected
  610 + def followed_profiles
  611 + Profile.followed_by self
  612 + end
584 613  
585   - def followed_by?(profile)
586   - self == profile || self.is_a_friend?(profile)
  614 + def in_social_circle?(person)
  615 + self.is_a_friend?(person) || super
587 616 end
  617 +
588 618 end
... ...
app/models/profile.rb
... ... @@ -5,7 +5,7 @@ class Profile &lt; ApplicationRecord
5 5  
6 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,
7 7 :redirection_after_login, :custom_url_redirection,
8   - :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification
  8 + :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :profile_admin_mail_notification, :allow_followers
9 9  
10 10 # use for internationalizable human type names in search facets
11 11 # reimplement on subclasses
... ... @@ -88,6 +88,8 @@ class Profile &lt; ApplicationRecord
88 88 }
89 89  
90 90 acts_as_accessible
  91 +
  92 + include Customizable
91 93 acts_as_customizable
92 94  
93 95 include Noosfero::Plugin::HotSpot
... ... @@ -185,6 +187,21 @@ class Profile &lt; ApplicationRecord
185 187 Person.members_of(self).by_role(roles)
186 188 end
187 189  
  190 + extend ActsAsHavingSettings::ClassMethods
  191 + acts_as_having_settings field: :data
  192 +
  193 + def settings
  194 + data
  195 + end
  196 +
  197 + settings_items :redirect_l10n, :type => :boolean, :default => false
  198 + settings_items :public_content, :type => :boolean, :default => true
  199 + settings_items :description
  200 + settings_items :fields_privacy, :type => :hash, :default => {}
  201 + settings_items :email_suggestions, :type => :boolean, :default => false
  202 + settings_items :profile_admin_mail_notification, :type => :boolean, :default => true
  203 +
  204 + extend ActsAsHavingBoxes::ClassMethods
188 205 acts_as_having_boxes
189 206  
190 207 acts_as_taggable
... ... @@ -203,6 +220,23 @@ class Profile &lt; ApplicationRecord
203 220 scope :more_active, -> { order 'activities_count DESC' }
204 221 scope :more_recent, -> { order "created_at DESC" }
205 222  
  223 + scope :followed_by, -> person{
  224 + distinct.select('profiles.*').
  225 + joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
  226 + joins('left join circles ON circles.id = profiles_circles.circle_id').
  227 + where('circles.person_id = ?', person.id)
  228 + }
  229 +
  230 + scope :in_circle, -> circle{
  231 + distinct.select('profiles.*').
  232 + joins('left join profiles_circles ON profiles_circles.profile_id = profiles.id').
  233 + joins('left join circles ON circles.id = profiles_circles.circle_id').
  234 + where('circles.id = ?', circle.id)
  235 + }
  236 +
  237 + settings_items :allow_followers, :type => :boolean, :default => true
  238 + alias_method :allow_followers?, :allow_followers
  239 +
206 240 acts_as_trackable :dependent => :destroy
207 241  
208 242 has_many :profile_activities
... ... @@ -215,6 +249,9 @@ class Profile &lt; ApplicationRecord
215 249  
216 250 has_many :email_templates, :foreign_key => :owner_id
217 251  
  252 + has_many :profile_followers
  253 + has_many :followers, -> { uniq }, :class_name => 'Person', :through => :profile_followers, :source => :person
  254 +
218 255 # Although this should be a has_one relation, there are no non-silly names for
219 256 # a foreign key on article to reference the template to which it is
220 257 # welcome_page... =P
... ... @@ -231,19 +268,6 @@ class Profile &lt; ApplicationRecord
231 268 scrap.nil? ? Scrap.all_scraps(self) : Scrap.all_scraps(self).find(scrap)
232 269 end
233 270  
234   - acts_as_having_settings :field => :data
235   -
236   - def settings
237   - data
238   - end
239   -
240   - settings_items :redirect_l10n, :type => :boolean, :default => false
241   - settings_items :public_content, :type => :boolean, :default => true
242   - settings_items :description
243   - settings_items :fields_privacy, :type => :hash, :default => {}
244   - settings_items :email_suggestions, :type => :boolean, :default => false
245   - settings_items :profile_admin_mail_notification, :type => :boolean, :default => true
246   -
247 271 validates_length_of :description, :maximum => 550, :allow_nil => true
248 272  
249 273 # Valid identifiers must match this format.
... ... @@ -285,6 +309,7 @@ class Profile &lt; ApplicationRecord
285 309  
286 310 has_many :files, :class_name => 'UploadedFile'
287 311  
  312 + extend ActsAsHavingImage::ClassMethods
288 313 acts_as_having_image
289 314  
290 315 has_many :tasks, :dependent => :destroy, :as => 'target'
... ... @@ -758,12 +783,13 @@ private :generate_url, :url_options
758 783  
759 784 # Adds a person as member of this Profile.
760 785 def add_member(person, attributes={})
761   - if self.has_members?
  786 + if self.has_members? && !self.secret
762 787 if self.closed? && members.count > 0
763 788 AddMember.create!(:person => person, :organization => self) unless self.already_request_membership?(person)
764 789 else
765 790 self.affiliate(person, Profile::Roles.admin(environment.id), attributes) if members.count == 0
766 791 self.affiliate(person, Profile::Roles.member(environment.id), attributes)
  792 + person.follow(self, Circle.find_or_create_by(:person => person, :name =>_('memberships'), :profile_type => 'Community'))
767 793 end
768 794 person.tasks.pending.of("InviteMember").select { |t| t.data[:community_id] == self.id }.each { |invite| invite.cancel }
769 795 remove_from_suggestion_list person
... ... @@ -1107,7 +1133,11 @@ private :generate_url, :url_options
1107 1133 end
1108 1134  
1109 1135 def followed_by?(person)
1110   - person.is_member_of?(self)
  1136 + (person == self) || (person.in? self.followers)
  1137 + end
  1138 +
  1139 + def in_social_circle?(person)
  1140 + (person == self) || (person.is_member_of?(self))
1111 1141 end
1112 1142  
1113 1143 def display_private_info_to?(user)
... ... @@ -1148,4 +1178,8 @@ private :generate_url, :url_options
1148 1178 def allow_destroy?(person = nil)
1149 1179 person.kind_of?(Profile) && person.has_permission?('destroy_profile', self)
1150 1180 end
  1181 +
  1182 + def in_circle?(circle, follower)
  1183 + ProfileFollower.with_follower(follower).with_circle(circle).with_profile(self).present?
  1184 + end
1151 1185 end
... ...
app/models/profile_follower.rb 0 → 100644
... ... @@ -0,0 +1,28 @@
  1 +class ProfileFollower < ApplicationRecord
  2 + self.table_name = :profiles_circles
  3 + track_actions :new_follower, :after_create, :keep_params => ["follower.name", "follower.url", "follower.profile_custom_icon"], :custom_user => :profile
  4 +
  5 + attr_accessible :profile, :circle
  6 +
  7 + belongs_to :profile
  8 + belongs_to :circle
  9 +
  10 + has_one :person, through: :circle
  11 + alias follower person
  12 +
  13 + validates_presence_of :profile_id, :circle_id
  14 + validates :profile_id, :uniqueness => {:scope => :circle_id, :message => "can't put a profile in the same circle twice"}
  15 +
  16 + scope :with_follower, -> person{
  17 + joins(:circle).where('circles.person_id = ?', person.id)
  18 + }
  19 +
  20 + scope :with_profile, -> profile{
  21 + where(:profile => profile)
  22 + }
  23 +
  24 + scope :with_circle, -> circle{
  25 + where(:circle => circle)
  26 + }
  27 +
  28 +end
... ...
app/models/profile_suggestion.rb
... ... @@ -17,7 +17,8 @@ class ProfileSuggestion &lt; ApplicationRecord
17 17 self.class.generate_profile_suggestions(profile_suggestion.person)
18 18 end
19 19  
20   - acts_as_having_settings :field => :categories
  20 + extend ActsAsHavingSettings::ClassMethods
  21 + acts_as_having_settings field: :categories
21 22  
22 23 validate :must_be_a_valid_category, :on => :create
23 24 def must_be_a_valid_category
... ...
app/models/task.rb
... ... @@ -11,7 +11,8 @@
11 11 # will need to declare <ttserialize</tt> itself).
12 12 class Task < ApplicationRecord
13 13  
14   - acts_as_having_settings :field => :data
  14 + extend ActsAsHavingSettings::ClassMethods
  15 + acts_as_having_settings field: :data
15 16  
16 17 module Status
17 18 # the status of tasks just created
... ...
app/models/text_article.rb
1   -require 'noosfero/translatable_content'
2   -
3 1 # a base class for all text article types.
4 2 class TextArticle < Article
5 3  
... ... @@ -9,7 +7,7 @@ class TextArticle &lt; Article
9 7 _('Article')
10 8 end
11 9  
12   - include Noosfero::TranslatableContent
  10 + include TranslatableContent
13 11  
14 12 def self.icon_name(article = nil)
15 13 if article && !article.parent.nil? && article.parent.kind_of?(Blog)
... ...
app/models/tiny_mce_article.rb
1   -require 'white_list_filter'
2   -
3 1 class TinyMceArticle < TextArticle
4 2  
5 3 def self.short_description
... ...
app/models/uploaded_file.rb
... ... @@ -84,6 +84,7 @@ class UploadedFile &lt; Article
84 84  
85 85 validates_attachment :size => N_("{fn} of uploaded file was larger than the maximum size of %{size}").sub('%{size}', self.max_size.to_humanreadable).fix_i18n
86 86  
  87 + extend DelayedAttachmentFu::ClassMethods
87 88 delay_attachment_fu_thumbnails
88 89  
89 90 postgresql_attachment_fu
... ...
app/views/account/activation_question.html.erb
... ... @@ -3,7 +3,7 @@
3 3 var answer = parseInt(form.answer.value);
4 4 var val = form.answer.value;
5 5 if (!answer || (val.length != 4) || val > <%= Time.now.year %> || val < 1900) {
6   - alert(<%= (_('The year must be between %d and %d') % [1900, Time.now.year]).inspect %>);
  6 + alert(<%= (_('The year must be between %d and %d').html_safe % [1900, Time.now.year]).inspect %>);
7 7 return false;
8 8 } else {
9 9 return true;
... ... @@ -28,9 +28,9 @@
28 28  
29 29 <p> <strong><%= _('Pay atention! You have only one chance!') %></strong> </p>
30 30  
31   - <p><%= _("This is a question to know if you really are part of this enterprise. Pay atention because you have only one chance to answer right and activate your enterprise. If you answer wrong you will not be able to activate the enterprise automaticaly and must get in touch with the admins of %s by email or phone.") % environment.name %> </p>
  31 + <p><%= _("This is a question to know if you really are part of this enterprise. Pay atention because you have only one chance to answer right and activate your enterprise. If you answer wrong you will not be able to activate the enterprise automaticaly and must get in touch with the admins of %s by email or phone.").html_safe % environment.name %> </p>
32 32  
33   - <%= ApplicationHelper::NoosferoFormBuilder::output_field(@question == :foundation_year ? (_("What year your enterprise was founded? It must have 4 digits, eg 1990. %s") % environment.tip_message_enterprise_activation_question) : _('What is the CNPJ of your enterprise?'), text_field_tag(:answer, nil, :id => 'enterprise-activation-answer')) %>
  33 + <%= ApplicationHelper::NoosferoFormBuilder::output_field(@question == :foundation_year ? (_("What year your enterprise was founded? It must have 4 digits, eg 1990. %s").html_safe % environment.tip_message_enterprise_activation_question) : _('What is the CNPJ of your enterprise?'), text_field_tag(:answer, nil, :id => 'enterprise-activation-answer')) %>
34 34  
35 35 <%= hidden_field_tag :enterprise_code, params[:enterprise_code] %>
36 36  
... ...
app/views/admin_panel/_signup_welcome_text.html.erb
1 1 <div class='description'>
2 2 <%= _('This text will be sent to new users if the feature "Send welcome e-mail to new users" is enabled on environment.') %><br/><br/>
3   - <%= _('Including %s on body, it will be replaced by the real name of the e-mail recipient.') % content_tag('code', '{user_name}') %>
  3 + <%= _('Including %s on body, it will be replaced by the real name of the e-mail recipient.').html_safe % content_tag('code', '{user_name}') %>
4 4 </div>
5 5  
6 6 <%= labelled_form_field(_('Subject'), text_field(:environment, :signup_welcome_text_subject, :style => 'width:100%')) %>
... ...
app/views/blocks/profile_info_actions/_circles.html.erb 0 → 100644
... ... @@ -0,0 +1,11 @@
  1 +<div class="circles" id='circles-list'>
  2 +
  3 + <%= form_for :circles, :url => {:controller => 'profile', :action => 'follow'}, :html => {:id => "follow-circles-form"} do |f|%>
  4 + <%= render partial: "blocks/profile_info_actions/select_circles", :locals => {:circles => circles, :followed_profile => profile, :follower => current_person } %>
  5 +
  6 + <div id="circle-actions">
  7 + <%= submit_button :ok, _("Follow") %>
  8 + <input type="button" value="<%= _("Cancel") %>" id="cancel-set-circle" class="button with-text icon-cancel"/>
  9 + </div>
  10 + <% end %>
  11 +</div>
... ...
app/views/blocks/profile_info_actions/_common.html.erb
1 1 <li><%= report_abuse(profile, :button) %></li>
  2 +<% if logged_in? && (user != profile) && profile.allow_followers? %>
  3 + <li>
  4 + <% follow = user.follows?(profile) %>
  5 + <%= button(:unfollow, content_tag('span', _('Unfollow')), {:profile => profile.identifier, :controller => 'profile', :action => 'unfollow'}, :method => :post, :id => 'action-unfollow', :title => _("Unfollow"), :style => follow ? "" : "display: none;") %>
  6 + <%= button(:ok, content_tag('span', _('Follow')), {:profile => profile.identifier, :controller => 'profile', :action => 'find_profile_circles'}, :id => 'action-follow', :title => _("Follow"), :style => follow ? "display: none;" : "") %>
  7 + <div id="circles-container" style="display: none;">
  8 + </div>
  9 + </li>
  10 +<% end %>
2 11 <%= render_environment_features(:profile_actions) %>
... ...
app/views/blocks/profile_info_actions/_select_circles.html.erb 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +<div class="circles" id='circles-list'>
  2 + <p><%= _("Select the circles for %s") % followed_profile.name %></p>
  3 + <div id="circles-checkboxes">
  4 + <% circles.each do |circle| %>
  5 + <div class="circle">
  6 + <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id, followed_profile.in_circle?(circle, follower) %>
  7 + </div>
  8 + <% end %>
  9 + </div>
  10 +
  11 + <%= button(:add, _('New Circle'), '#', :id => "new-circle") %>
  12 +
  13 + <div id="new-circle-form" style="display: none;">
  14 + <%= labelled_text_field _('Circle name') , 'circle[name]', "",:id => 'text-field-name-new-circle'%>
  15 + <%= hidden_field_tag('circle[profile_type]', followed_profile.class.name) %>
  16 +
  17 + <%= button_bar do %>
  18 + <%= button(:save, _('Create'), {:profile => follower.identifier, :controller => 'circles', :action => 'xhr_create'}, :id => "new-circle-submit") %>
  19 + <%= button(:cancel, _('Cancel'), '#', :id => "new-circle-cancel") %>
  20 + <% end %>
  21 + </div>
  22 +</div>
... ...
app/views/circles/_circle_checkbox.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<div class="circle">
  2 + <%= labelled_check_box circle.name, "circles[#{circle.name}]", circle.id %>
  3 +</div>
... ...
app/views/circles/_form.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<%= error_messages_for :circle %>
  2 +
  3 +<%= labelled_form_for :circle, :url => (mode == :edit) ? {:action => 'update', :id => circle} : {:action => 'create'} do |f| %>
  4 +
  5 + <%= required_fields_message %>
  6 +
  7 + <%= required f.text_field(:name) %>
  8 +
  9 + <%= required labelled_form_field _("Profile type"), f.select(:profile_type, Circle.profile_types.to_a) %>
  10 +
  11 + <%= button_bar do %>
  12 + <%= submit_button('save', (mode == :edit) ? _('Save changes') : _('Create circle'), :cancel => {:action => 'index'} ) %>
  13 + <% end %>
  14 +<% end %>
... ...
app/views/circles/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("Edit circle") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :edit, :circle => @circle, :profile_types => @profile_types } %>
... ...
app/views/circles/index.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<h1><%= _('Manage circles') %></h1>
  2 +
  3 +<table>
  4 + <tr>
  5 + <th><%= _('Circle name') %></th>
  6 + <th><%= _('Profile type') %></th>
  7 + <th><%= _('Actions') %></th>
  8 + </tr>
  9 + <% @circles.each do |circle| %>
  10 + <tr>
  11 + <td>
  12 + <%= circle.name %>
  13 + </td>
  14 + <td>
  15 + <%= _(circle.profile_type) %>
  16 + </td>
  17 + <td>
  18 + <div style="text-align: center;">
  19 + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => circle %>
  20 + <%= button_without_text :delete, _('Delete'), { :action => 'destroy', :id => circle }, { "data-method" => "POST" } %>
  21 + </div>
  22 + </td>
  23 + </tr>
  24 + <% end %>
  25 +</table>
  26 +
  27 +<%= button_bar do %>
  28 + <%= button :add, _('Create a new circle'), :action => 'new' %>
  29 + <%= button :back, _('Back to control panel'), :controller => 'profile_editor' %>
  30 +<% end %>
... ...
app/views/circles/new.html.erb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +<h1><%= _("New circle") %></h1>
  2 +
  3 +<%= render :partial => 'form', :locals => { :mode => :new, :circle => @circle, :profile_types => @profile_types } %>
... ...
app/views/cms/_blog.html.erb
... ... @@ -58,7 +58,7 @@
58 58 <div id="blog-image-builder">
59 59 <%= f.fields_for :image_builder, @article.image do |i| %>
60 60 <%= file_field_or_thumbnail(_('Cover image:'), @article.image, i)%>
61   - <%= _("Max size: %s (.jpg, .gif, .png)")% Image.max_size.to_humanreadable %>
  61 + <%= _("Max size: %s (.jpg, .gif, .png)").html_safe % Image.max_size.to_humanreadable %>
62 62 <% end %>
63 63 </div>
64 64  
... ...
app/views/enterprise_validation/view_processed.html.erb
1   -<h2><%= _('Processed validation request for %s ') % @processed.name %> (<%= status(@processed) %>)</h2>
  1 +<h2><%= _('Processed validation request for %s ').html_safe % @processed.name %> (<%= status(@processed) %>)</h2>
2 2  
3 3 <%= link_to _('Back'), :action => 'index' %>
4 4  
... ...
app/views/favorite_enterprises/add.html.erb
1   -<h1><%= _('Adding %s as a favorite enterprise') % @favorite_enterprise.name %></h1>
  1 +<h1><%= _('Adding %s as a favorite enterprise').html_safe % @favorite_enterprise.name %></h1>
2 2  
3 3 <p>
4   -<%= _('Are you sure you want to add %s as your favorite enterprise?') % @favorite_enterprise.name %>
  4 +<%= _('Are you sure you want to add %s as your favorite enterprise?').html_safe % @favorite_enterprise.name %>
5 5 </p>
6 6  
7 7 <%= form_tag do %>
8 8 <%= hidden_field_tag(:confirmation, 1) %>
9 9  
10   - <%= submit_button(:ok, _("Yes, I am sure"), :title => _("I want to add %s as a favorite enterprise") % @favorite_enterprise.name) %>
  10 + <%= submit_button(:ok, _("Yes, I am sure"), :title => _("I want to add %s as a favorite enterprise").html_safe % @favorite_enterprise.name) %>
11 11 <%= button(:cancel, _("No, I don't want"), :action => 'index') %>
12 12 <% end %>
... ...
app/views/file_presenter/_image.html.erb
... ... @@ -4,15 +4,15 @@
4 4 current_index = images.index(image.encapsulated_file)
5 5 total_of_images = images.count
6 6 link_to_previous = if current_index >= 1
7   - link_to(_('&laquo; Previous'), images[current_index - 1].view_url, :class => 'previous')
  7 + link_to(_('&laquo; Previous').html_safe, images[current_index - 1].view_url, :class => 'previous')
8 8 else
9   - content_tag('span', _('&laquo; Previous'), :class => 'previous')
  9 + content_tag('span', _('&laquo; Previous').html_safe, :class => 'previous')
10 10 end
11 11  
12 12 link_to_next = if current_index < total_of_images - 1
13   - link_to(_('Next &raquo;'), images[current_index + 1].view_url, :class => 'next')
  13 + link_to(_('Next &raquo;').html_safe, images[current_index + 1].view_url, :class => 'next')
14 14 else
15   - content_tag('span', _('Next &raquo;'), :class => 'next')
  15 + content_tag('span', _('Next &raquo;').html_safe, :class => 'next')
16 16 end
17 17 %>
18 18  
... ...
app/views/followers/_edit_circles_modal.html.erb 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +<div class="circles" id='circles-list'>
  2 + <%= form_for :circles, :url => {:controller => 'followers', :action => 'update_category'}, :html => {:id => "follow-circles-form"} do |f|%>
  3 + <%= render partial: "blocks/profile_info_actions/select_circles", :locals => {:circles => circles, :followed_profile => followed_profile, :follower => profile } %>
  4 +
  5 + <%= hidden_field_tag('followed_profile_id', followed_profile.id) %>
  6 +
  7 + <div id="circle-actions">
  8 + <div id="actions-container">
  9 + <%= submit_button('save', _('Save')) %>
  10 + <%= modal_close_button _("Cancel") %>
  11 + </div>
  12 + </div>
  13 + <% end %>
  14 +</div>
... ...
app/views/followers/_profile_list.html.erb 0 → 100644
... ... @@ -0,0 +1,17 @@
  1 +<ul class="profile-list">
  2 + <% profiles.each do |followed_profile| %>
  3 + <li>
  4 + <%= link_to_profile profile_image(followed_profile) + tag('br') + followed_profile.short_name,
  5 + followed_profile.identifier, :class => 'profile-link' %>
  6 + <div class="controll">
  7 + <%= button_without_text :remove, content_tag('span',_('unfollow')),
  8 + { :controller => "profile", :profile => followed_profile.identifier, :follower_id => profile.id,
  9 + :action => 'unfollow', :redirect_to => url_for({:controller => "followers", :profile => profile.identifier}) },
  10 + :method => :post, :title => _('remove') %>
  11 + <%= modal_icon_button :edit, _('change category'),
  12 + url_for(:controller => 'followers', :action => 'set_category_modal',
  13 + :followed_profile_id => followed_profile.id) %>
  14 + </div><!-- end class="controll" -->
  15 + </li>
  16 + <% end %>
  17 +</ul>
... ...
app/views/followers/index.html.erb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +<div id="manage_followed people">
  2 +
  3 +<h1><%= _("%s is following") % profile.name %></h1>
  4 +
  5 +<% cache_timeout(profile.manage_friends_cache_key(params), 4.hours) do %>
  6 + <% if @followed_people.empty? %>
  7 + <p>
  8 + <em>
  9 + <%= _("You don't follow anybody yet.") %>
  10 + </em>
  11 + </p>
  12 + <% end %>
  13 +
  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 + <% end %>
  18 +
  19 + <%= labelled_select(_('Profile type')+': ', :filter_profile_type, :last, :first, @active_filter, @profile_types, :id => "profile-type-filter") %>
  20 +
  21 + <%= render :partial => 'profile_list', :locals => { :profiles => @followed_people } %>
  22 +
  23 + <br style="clear:both" />
  24 + <%= pagination_links @followed_people, :param_name => 'npage' %>
  25 +<% end %>
  26 +
  27 +</div>
... ...
app/views/friends/remove.html.erb
1 1 <div id="remove_friend">
2 2  
3   -<h1><%= _('Removing friend: %s') % @friend.name %></h1>
  3 +<h1><%= _('Removing friend: %s').html_safe % @friend.name %></h1>
4 4  
5 5 <%= profile_image @friend, :thumb, :class => 'friend_picture' %>
6 6  
7 7 <p>
8   -<%= _('Are you sure you want to remove %s from your friends list?') % @friend.name %>
  8 +<%= _('Are you sure you want to remove %s from your friends list?').html_safe % @friend.name %>
9 9 </p>
10 10  
11 11 <p>
12 12 <em>
13   -<%= _('Note that %s will still have you as a friend, unless he/she also wants to remove you from his/her friend list.') % @friend.name %>
  13 +<%= _('Note that %s will still have you as a friend, unless he/she also wants to remove you from his/her friend list.').html_safe % @friend.name %>
14 14 </em>
15 15 </p>
16 16  
... ...
app/views/home/welcome.html.erb
1 1 <% default_message = defined?(default_message) ? default_message : false %>
2 2  
3 3 <div id='thanks-for-signing'>
4   - <h1><%= _("Welcome to %s!") % environment.name %></h1>
  4 + <h1><%= _("Welcome to %s!").html_safe % environment.name %></h1>
5 5 <% if environment.has_custom_welcome_screen? && !default_message %>
6 6 <%= environment.settings[:signup_welcome_screen_body].html_safe %>
7 7 <% else %>
... ... @@ -10,21 +10,21 @@
10 10 <p><%= _("Firstly, some tips for getting started:") %></p>
11 11 <h4><%= _("Confirm your account!") %></h4>
12 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>
  13 + <p><%= _("You won't appear as %s until your account is confirmed.").html_safe % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
14 14 <% else %>
15 15 <h4><%= _("Wait for admin approvement!") %></h4>
16 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>
  17 + <p><%= _("You won't appear as %s until your account is approved.").html_safe % link_to(_('user'), {:controller => :search, :action => :people, :filter => 'more_recent'}, :target => '_blank') %></p>
18 18 <% end %>
19 19 <h4><%= _("What to do next?") %></h4>
20   - <p><%= _("Access your %s and see your face on the network!") %
  20 + <p><%= _("Access your %s and see your face on the network!").html_safe %
21 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.") %
  22 + <%= _("You can also explore your %s to customize your profile. Here are some %s on what you can do there.").html_safe %
23 23 [user.present? ? link_to(_('Control Panel'), {:controller => 'profile_editor', :profile => user.identifier}, :target => '_blank') : 'Control Panel',
24 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>
  25 + <p><%= _("%s your Gmail, Yahoo and Hotmail contacts!").html_safe % 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!").html_safe % link_to(_('Documentation'), {:controller => 'doc'}, :target => '_blank') %></p>
27 27 <p><%= _("Start exploring and have fun!") %></p>
28 28 <% end %>
29   - <%= render :partial => 'shared/template_welcome_page', :locals => {:template => @person_template, :header => _("What can I do as a %s?")} %>
  29 + <%= render :partial => 'shared/template_welcome_page', :locals => {:template => @person_template, :header => _("What can I do as a %s?").html_safe} %>
30 30 </div>
... ...
app/views/person_notifier/mailer/_create_article.html.erb
... ... @@ -5,7 +5,7 @@
5 5 <td>
6 6 <p>
7 7 <span style="font-size: 14px;"><%= link_to activity.user.short_name(20), activity.user.url %></span>
8   - <span style="font-size: 14px;"><%= _("has published on community %s") % link_to(activity.target.profile.short_name(20), activity.target.profile.url, :style => "color: #333; font-weight: bold; text-decoration: none;") if activity.target.profile.is_a?(Community) %></span>
  8 + <span style="font-size: 14px;"><%= _("has published on community %s").html_safe % link_to(activity.target.profile.short_name(20), activity.target.profile.url, :style => "color: #333; font-weight: bold; text-decoration: none;") if activity.target.profile.is_a?(Community) %></span>
9 9 <span style="font-size: 10px; color: #929292; float:right;"><%= time_ago_in_words(activity.created_at) %></span>
10 10 </p>
11 11 <p>
... ...
app/views/person_notifier/mailer/_new_follower.html.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= render :partial => 'default_activity', :locals => { :activity => activity } %>
... ...
app/views/person_notifier/mailer/_profile_comments.html.erb
1 1 <% if activity.comments_count > 2 %>
2 2 <div style="font-size: 10px;">
3 3 <% if activity.params['url'].blank? %>
4   - <%= _("%s comments") % activity.comments_count %>
  4 + <%= _("%s comments").html_safe % activity.comments_count %>
5 5 <% else %>
6   - <%= link_to(_("View all %s comments") % activity.comments_count, activity.params['url']) %>
  6 + <%= link_to(_("View all %s comments").html_safe % activity.comments_count, activity.params['url']) %>
7 7 <% end %>
8 8 </div>
9 9 <% else %>
... ...
app/views/person_notifier/mailer/content_summary.html.erb
... ... @@ -5,7 +5,7 @@
5 5 <%= link_to @url, :style => "text-decoration: none;" do %>
6 6 <span style="font-weight:bold;font-size: 28px;margin: 0;color: white;background-color: #AAAAAA;padding: 5px;"><%= @environment.name %></span>
7 7 <% end %>
8   - <span style="font-weight:bold;color: #333;font-size:19px;margin-left: 8px;"><%= _("%s's Notifications") % @profile.name %></h3>
  8 + <span style="font-weight:bold;color: #333;font-size:19px;margin-left: 8px;"><%= _("%s's Notifications").html_safe % @profile.name %></h3>
9 9 </div>
10 10 <div style="margin: 0 20px 20px 20px;border-top:1px solid #e2e2e2;">
11 11 <% if @tasks.present? %>
... ... @@ -34,7 +34,7 @@
34 34  
35 35 <div style="color:#444444;font-size:11px;margin-bottom: 20px;">
36 36 <p style="margin:0"><%= _("Greetings,") %></p>
37   - <p style="margin:0"><%= _('%s team.') % @environment.name %></p>
  37 + <p style="margin:0"><%= _('%s team.').html_safe % @environment.name %></p>
38 38 <p style="margin:0"><%= link_to @url, url_for(@url) %></p>
39 39 </div>
40 40 </div>
... ...
app/views/profile/_follow.html.erb 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +<% cache_timeout(profile.friends_cache_key(params), 4.hours) do %>
  2 + <ul class='profile-list'>
  3 + <% follow.each do |follower| %>
  4 + <%= profile_image_link(follower) %>
  5 + <% end%>
  6 + </ul>
  7 +
  8 + <div id='pagination-profiles'>
  9 + <%= pagination_links follow, :param_name => 'npage' %>
  10 + </div>
  11 +<% end %>
  12 +
  13 +<%= button_bar do %>
  14 + <%= button :back, _('Go back'), { :controller => 'profile' } %>
  15 + <% if user == profile %>
  16 + <%= button :edit, _('Manage followed people'), :controller => 'friends', :action => 'index', :profile => profile.identifier %>
  17 + <% end %>
  18 +<% end %>
... ...
app/views/profile/_new_follower.html.erb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +<%= render :partial => 'default_activity', :locals => { :activity => activity, :tab_action => tab_action } %>
... ...
app/views/profile/_private_profile.html.erb
... ... @@ -13,5 +13,5 @@
13 13 <%= button(:add, content_tag('span', _('Add friend')), profile.add_url, :class => 'add-friend', :title => _("Add friend"), :style => 'position: relative;') %>
14 14 <% end %>
15 15 <%= button :back, _('Go back'), :back %>
16   - <%= button :home, _("Go to %s home page") % environment.name, :controller => 'home' %>
  16 + <%= button :home, _("Go to %s home page").html_safe % environment.name, :controller => 'home' %>
17 17 <% end %>
... ...
app/views/profile/_profile_comments.html.erb
... ... @@ -5,7 +5,7 @@
5 5 <% if activity.comments_count > 0 %>
6 6 <div id="profile-wall-activities-comments-more-<%= activity.id %>" class="profile-wall-activities-comments" >
7 7 <div class='view-all-comments icon-chat'>
8   - <%= link_to(n_('View comment', "View all %s comments", activity.comments_count) % activity.comments_count, :profile => profile.identifier, :controller => 'profile', :action => 'more_comments', :activity => activity, :comment_page => (1)) %>
  8 + <%= link_to(n_('View comment', "View all %s comments".html_safe, activity.comments_count) % activity.comments_count, :profile => profile.identifier, :controller => 'profile', :action => 'more_comments', :activity => activity, :comment_page => (1)) %>
9 9 </div>
10 10 </div>
11 11 <% end %>
... ...
app/views/profile/_profile_scraps.html.erb
... ... @@ -23,7 +23,7 @@
23 23 <% if scrap.replies.count > 0 %>
24 24 <div id="profile-wall-activities-comments-more-<%= activity.id %>" class="profile-wall-activities-comments">
25 25 <div class='view-all-comments icon-chat'>
26   - <%= link_to(n_('View comment', "View all %s comments", scrap.replies.count) % scrap.replies.count, :profile => profile.identifier, :controller => 'profile', :action => 'more_replies', :activity => activity, :comment_page => (1)) %>
  26 + <%= link_to(n_('View comment', "View all %s comments".html_safe, scrap.replies.count) % scrap.replies.count, :profile => profile.identifier, :controller => 'profile', :action => 'more_replies', :activity => activity, :comment_page => (1)) %>
27 27 </div>
28 28 </div>
29 29 <% end %>
... ...
app/views/profile/followed.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="common-profile-list-block">
  2 +
  3 +<h1><%= _("%s is followed by") % profile.name %></h1>
  4 +
  5 +<%= render :partial => 'follow', :locals => {:follow => @followed_by} %>
  6 +
  7 +</div>
... ...
app/views/profile/following.html.erb 0 → 100644
... ... @@ -0,0 +1,7 @@
  1 +<div class="common-profile-list-block">
  2 +
  3 +<h1><%= _("%s is following") % profile.name %></h1>
  4 +
  5 +<%= render :partial => 'follow', :locals => {:follow => @followed_people} %>
  6 +
  7 +</div>
... ...
app/views/profile_editor/edit.html.erb
1   -<h1><%= _('Profile settings for %s') % profile.name %></h1>
  1 +<h1><%= _('Profile settings for %s').html_safe % profile.name %></h1>
2 2  
3 3 <%= error_messages_for :profile_data %>
4 4  
... ... @@ -18,14 +18,16 @@
18 18 </div>
19 19 <div id="profile_change_picture">
20 20 <%= f.fields_for :image_builder, @profile.image do |i| %>
21   - <%= file_field_or_thumbnail(_('Image:'), @profile.image, i) %><%= _("Max size: %s (.jpg, .gif, .png)")% Image.max_size.to_humanreadable %>
  21 + <%= file_field_or_thumbnail(_('Image:'), @profile.image, i) %><%= _("Max size: %s (.jpg, .gif, .png)").html_safe % Image.max_size.to_humanreadable %>
22 22 <% end %>
23 23 </div>
24 24  
25 25 <h2><%= _('Privacy options') %></h2>
26   -
  26 + <div>
  27 + <%= labelled_check_box _("Allow other users to follow me"), 'profile_data[allow_followers]', true, @profile.allow_followers?, :class => "person-can-be-followed" %>
  28 + </div>
27 29 <% if profile.person? %>
28   - <div>
  30 + <div id="profile_allow_follows">
29 31 <%= labelled_radio_button _('Public &mdash; show my contents to all internet users').html_safe, 'profile_data[public_profile]', true, @profile.public_profile? %>
30 32 </div>
31 33 <div>
... ...
app/views/profile_editor/index.html.erb
... ... @@ -72,6 +72,11 @@
72 72  
73 73 <%= control_panel_button(_('Email Templates'), 'email-templates', :controller => :profile_email_templates) if profile.organization? %>
74 74  
  75 + <% if profile.person? %>
  76 + <%= control_panel_button(_('Manage followed profiles'), 'edit-profile', :controller => :followers) %>
  77 + <%= control_panel_button(_('Manage circles'), 'edit-profile-group', :controller => :circles) %>
  78 + <% end %>
  79 +
75 80 <% @plugins.dispatch(:control_panel_buttons).each do |button| %>
76 81 <%= control_panel_button(button[:title], button[:icon], button[:url], button[:html_options]) %>
77 82 <% end %>
... ...
app/views/search/_search_content.html.erb
1 1 <div id='search-content'>
2 2 <div class='total'>
3   - <%= n_('Total of 1 result', 'Total of %s results', @searches[@asset][:results].total_entries) % @searches[@asset][:results].total_entries.inspect %>
  3 + <%= n_('Total of 1 result', 'Total of %s results'.html_safe, @searches[@asset][:results].total_entries) % @searches[@asset][:results].total_entries.inspect %>
4 4 </div>
5 5  
6 6 <%= display_results(@searches, @asset) %>
... ...