Commit 3926c76a757800dd1433a8f9cb7dab9d5250954d

Authored by Michel Felipe
1 parent 8c31af4c

Remove admins.map that generate a SQL query for each user causing performance problems.

app/models/organization.rb
@@ -8,6 +8,30 @@ class Organization < Profile @@ -8,6 +8,30 @@ class Organization < Profile
8 :display => %w[compact] 8 :display => %w[compact]
9 } 9 }
10 10
  11 + # An Organization is considered visible to a given person if one of the
  12 +# following conditions are met:
  13 +# 1) The user is an environment administrator.
  14 +# 2) The user is an administrator of the organization.
  15 +# 3) The user is a member of the organization and the organization is
  16 +# visible.
  17 +# 4) The user is not a member of the organization but the organization is
  18 +# visible, public and enabled.
  19 + def self.visible_for_person(person)
  20 + joins('LEFT JOIN "role_assignments" ON ("role_assignments"."resource_id" = "profiles"."id"
  21 + AND "role_assignments"."resource_type" = \'Profile\') OR (
  22 + "role_assignments"."resource_id" = "profiles"."environment_id" AND
  23 + "role_assignments"."resource_type" = \'Environment\' )')
  24 + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"')
  25 + .where(
  26 + ['( (roles.key = ? OR roles.key = ?) AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? )
  27 + OR
  28 + ( ( ( role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR
  29 + ( profiles.public_profile = ? AND profiles.enabled = ? ) ) AND
  30 + ( profiles.visible = ? ) )',
  31 + 'profile_admin', 'environment_administrator', Profile.name, person.id,
  32 + Profile.name, person.id, true, true, true]
  33 + ).uniq
  34 + end
11 35
12 settings_items :closed, :type => :boolean, :default => false 36 settings_items :closed, :type => :boolean, :default => false
13 def closed? 37 def closed?
@@ -154,7 +178,8 @@ class Organization < Profile @@ -154,7 +178,8 @@ class Organization < Profile
154 end 178 end
155 179
156 def notification_emails 180 def notification_emails
157 - emails = [contact_email].select(&:present?) + admins.map(&:email) 181 + # TODO: Add performance improvement here!
  182 + emails = [contact_email].select(&:present?) + admins([:user]).pluck(:email)
158 if emails.empty? 183 if emails.empty?
159 emails << environment.contact_email 184 emails << environment.contact_email
160 end 185 end
app/models/person.rb
1 # A person is the profile of an user holding all relationships with the rest of the system 1 # A person is the profile of an user holding all relationships with the rest of the system
2 class Person < Profile 2 class Person < Profile
3 3
4 - attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone, :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference, :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization_website 4 + attr_accessible :organization, :contact_information, :sex, :birth_date, :cell_phone,
  5 + :comercial_phone, :jabber_id, :personal_website, :nationality, :address_reference,
  6 + :district, :schooling, :schooling_status, :formation, :custom_formation, :area_of_study,
  7 + :custom_area_of_study, :professional_activity, :organization_website, :following_articiles
5 8
6 SEARCH_FILTERS = { 9 SEARCH_FILTERS = {
7 :order => %w[more_recent more_popular more_active], 10 :order => %w[more_recent more_popular more_active],
@@ -16,10 +19,12 @@ class Person &lt; Profile @@ -16,10 +19,12 @@ class Person &lt; Profile
16 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)} 19 acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)}
17 acts_as_accessor 20 acts_as_accessor
18 21
19 - scope :members_of, lambda { |resources| 22 + scope :members_of, lambda { |resources, extra_joins = nil|
20 resources = [resources] if !resources.kind_of?(Array) 23 resources = [resources] if !resources.kind_of?(Array)
21 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ') 24 conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ')
22 - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } 25 + joins = [:role_assignments]
  26 + joins += extra_joins if extra_joins.is_a? Array
  27 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => [conditions] }
23 } 28 }
24 29
25 scope :not_members_of, lambda { |resources| 30 scope :not_members_of, lambda { |resources|
@@ -28,10 +33,11 @@ class Person &lt; Profile @@ -28,10 +33,11 @@ class Person &lt; Profile
28 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] } 33 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "role_assignments" ON "role_assignments"."accessor_id" = "profiles"."id" AND "role_assignments"."accessor_type" = (\'Profile\') WHERE "profiles"."type" IN (\'Person\') AND (%s))' % conditions] }
29 } 34 }
30 35
31 - scope :by_role, lambda { |roles| 36 + scope :by_role, lambda { |roles, extra_joins = nil|
32 roles = [roles] unless roles.kind_of?(Array) 37 roles = [roles] unless roles.kind_of?(Array)
33 - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)',  
34 -roles] } 38 + joins = [:role_assignments]
  39 + joins += extra_joins if extra_joins.is_a? Array
  40 + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => ['role_assignments.role_id IN (?)',roles] }
35 } 41 }
36 42
37 scope :not_friends_of, lambda { |resources| 43 scope :not_friends_of, lambda { |resources|
@@ -39,6 +45,18 @@ roles] } @@ -39,6 +45,18 @@ roles] }
39 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] } 45 { :select => 'DISTINCT profiles.*', :conditions => ['"profiles"."id" NOT IN (SELECT DISTINCT profiles.id FROM "profiles" INNER JOIN "friendships" ON "friendships"."person_id" = "profiles"."id" WHERE "friendships"."friend_id" IN (%s))' % resources.map(&:id)] }
40 } 46 }
41 47
  48 + scope :visible_for_person, lambda { |person|
  49 + joins('LEFT JOIN "role_assignments" ON
  50 + "role_assignments"."resource_id" = "profiles"."environment_id" AND
  51 + "role_assignments"."resource_type" = \'Environment\'')
  52 + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"')
  53 + .joins('LEFT JOIN "friendships" ON "friendships"."friend_id" = "profiles"."id"')
  54 + .where(
  55 + ['( roles.key = ? AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR (
  56 + ( ( friendships.person_id = ? ) OR (profiles.public_profile = ?)) AND (profiles.visible = ?) )', 'environment_administrator', Profile.name, person.id, person.id, true, true]
  57 + ).uniq
  58 + }
  59 +
42 def has_permission_with_admin?(permission, resource) 60 def has_permission_with_admin?(permission, resource)
43 return true if resource.blank? || resource.admins.include?(self) 61 return true if resource.blank? || resource.admins.include?(self)
44 return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self) 62 return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self)
@@ -68,6 +86,10 @@ roles] } @@ -68,6 +86,10 @@ roles] }
68 memberships.where('role_assignments.role_id = ?', role.id) 86 memberships.where('role_assignments.role_id = ?', role.id)
69 end 87 end
70 88
  89 + #Article followers relation
  90 + has_many :article_followers, :dependent => :destroy
  91 + has_many :following_articiles, :class_name => 'Article', :through => :article_followers, :source => :article
  92 +
71 has_many :friendships, :dependent => :destroy 93 has_many :friendships, :dependent => :destroy
72 has_many :friends, :class_name => 'Person', :through => :friendships 94 has_many :friends, :class_name => 'Person', :through => :friendships
73 95
@@ -125,6 +147,11 @@ roles] } @@ -125,6 +147,11 @@ roles] }
125 self.tracked_notifications.exists?(activity) 147 self.tracked_notifications.exists?(activity)
126 end 148 end
127 149
  150 + def can_post_content?(profile, parent=nil)
  151 + (!parent.nil? && (parent.allow_create?(self))) ||
  152 + (self.has_permission?('post_content', profile) || self.has_permission?('publish_content', profile))
  153 + end
  154 +
128 # Sets the identifier for this person. Raises an exception when called on a 155 # Sets the identifier for this person. Raises an exception when called on a
129 # existing person (since peoples' identifiers cannot be changed) 156 # existing person (since peoples' identifiers cannot be changed)
130 def identifier=(value) 157 def identifier=(value)
@@ -187,9 +214,11 @@ roles] } @@ -187,9 +214,11 @@ roles] }
187 organization 214 organization
188 organization_website 215 organization_website
189 contact_phone 216 contact_phone
190 - contact_information 217 + contact_informatioin
191 ] 218 ]
192 219
  220 + xss_terminate :only => [ :custom_footer, :custom_header, :description, :preferred_domain, :nickname, :sex, :nationality, :country, :state, :city, :district, :zip_code, :address, :address_reference, :cell_phone, :comercial_phone, :personal_website, :jabber_id, :schooling, :formation, :custom_formation, :area_of_study, :custom_area_of_study, :professional_activity, :organization, :organization_website, :contact_phone, :contact_information ], :with => 'white_list'
  221 +
193 validates_multiparameter_assignments 222 validates_multiparameter_assignments
194 223
195 def self.fields 224 def self.fields
app/models/profile.rb
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 # which by default is the one returned by Environment:default. 3 # which by default is the one returned by Environment:default.
4 class Profile < ActiveRecord::Base 4 class Profile < ActiveRecord::Base
5 5
6 - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret 6 + attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :email_suggestions, :allow_members_to_invite, :invite_friends_only, :secret, :custom_fields, :administrator_mail_notification, :region
7 7
8 # use for internationalizable human type names in search facets 8 # use for internationalizable human type names in search facets
9 # reimplement on subclasses 9 # reimplement on subclasses
@@ -22,6 +22,34 @@ class Profile &lt; ActiveRecord::Base @@ -22,6 +22,34 @@ class Profile &lt; ActiveRecord::Base
22 :display => %w[compact] 22 :display => %w[compact]
23 } 23 }
24 24
  25 + settings_items :custom_fields, :default => {}
  26 +
  27 + def custom_field_value(field)
  28 + if !self.custom_fields.blank?
  29 + self.custom_fields[field][:value]
  30 + else
  31 + ''
  32 + end
  33 + end
  34 +
  35 + def custom_field_title(field)
  36 + if !self.custom_fields.blank?
  37 + self.custom_fields[field][:title]
  38 + else
  39 + ''
  40 + end
  41 + end
  42 +
  43 + def custom_fields_template
  44 + fields = {}
  45 + fields = self.template.custom_fields unless self.template.blank?
  46 + fields
  47 + end
  48 +
  49 + def custom_fields_template_title(field)
  50 + self.template.custom_fields[field][:title]
  51 + end
  52 +
25 NUMBER_OF_BOXES = 4 53 NUMBER_OF_BOXES = 4
26 54
27 def self.default_search_display 55 def self.default_search_display
@@ -79,6 +107,7 @@ class Profile &lt; ActiveRecord::Base @@ -79,6 +107,7 @@ class Profile &lt; ActiveRecord::Base
79 'invite_members' => N_('Invite members'), 107 'invite_members' => N_('Invite members'),
80 'send_mail_to_members' => N_('Send e-Mail to members'), 108 'send_mail_to_members' => N_('Send e-Mail to members'),
81 'manage_custom_roles' => N_('Manage custom roles'), 109 'manage_custom_roles' => N_('Manage custom roles'),
  110 + 'manage_email_templates' => N_('Manage Email Templates'),
82 } 111 }
83 112
84 acts_as_accessible 113 acts_as_accessible
@@ -100,6 +129,50 @@ class Profile &lt; ActiveRecord::Base @@ -100,6 +129,50 @@ class Profile &lt; ActiveRecord::Base
100 } 129 }
101 scope :no_templates, {:conditions => {:is_template => false}} 130 scope :no_templates, {:conditions => {:is_template => false}}
102 131
  132 + # Returns a scoped object to select profiles in a given location or in a radius
  133 + # distance from the given location center.
  134 + # The parameter can be the `request.params` with the keys:
  135 + # * `country`: Country code string.
  136 + # * `state`: Second-level administrative country subdivisions.
  137 + # * `city`: City full name for center definition, or as set by users.
  138 + # * `lat`: The latitude to define the center of georef search.
  139 + # * `lng`: The longitude to define the center of georef search.
  140 + # * `distance`: Define the search radius in kilometers.
  141 + # NOTE: This method may return an exception object, to inform filter error.
  142 + # When chaining scopes, is hardly recommended you to add this as the last one,
  143 + # if you can't be sure about the provided parameters.
  144 + def self.by_location(params)
  145 + params = params.with_indifferent_access
  146 + if params[:distance].blank?
  147 + where_code = []
  148 + [ :city, :state, :country ].each do |place|
  149 + unless params[place].blank?
  150 + # ... So we must to find on this named location
  151 + # TODO: convert location attrs to a table collumn
  152 + where_code << "(profiles.data like '%#{place}: #{params[place]}%')"
  153 + end
  154 + end
  155 + self.where where_code.join(' AND ')
  156 + else # Filter in a georef circle
  157 + unless params[:lat].blank? && params[:lng].blank?
  158 + lat, lng = [ params[:lat].to_f, params[:lng].to_f ]
  159 + end
  160 + if !lat
  161 + location = [ params[:city], params[:state], params[:country] ].compact.join(', ')
  162 + if location.blank?
  163 + return Exception.new (
  164 + _('You must to provide `lat` and `lng`, or `city` and `country` to define the center of the search circle, defined by `distance`.')
  165 + )
  166 + end
  167 + lat, lng = Noosfero::GeoRef.location_to_georef location
  168 + end
  169 + dist = params[:distance].to_f
  170 + self.where "#{Noosfero::GeoRef.sql_dist lat, lng} <= #{dist}"
  171 + end
  172 + end
  173 +
  174 + include TimeScopes
  175 +
103 def members 176 def members
104 scopes = plugins.dispatch_scopes(:organization_members, self) 177 scopes = plugins.dispatch_scopes(:organization_members, self)
105 scopes << Person.members_of(self) 178 scopes << Person.members_of(self)
@@ -119,8 +192,8 @@ class Profile &lt; ActiveRecord::Base @@ -119,8 +192,8 @@ class Profile &lt; ActiveRecord::Base
119 alias_method_chain :count, :distinct 192 alias_method_chain :count, :distinct
120 end 193 end
121 194
122 - def members_by_role(roles)  
123 - Person.members_of(self).by_role(roles) 195 + def members_by_role(roles, with_entities = nil)
  196 + Person.members_of(self, with_entities).by_role(roles, with_entities)
124 end 197 end
125 198
126 acts_as_having_boxes 199 acts_as_having_boxes
@@ -151,6 +224,8 @@ class Profile &lt; ActiveRecord::Base @@ -151,6 +224,8 @@ class Profile &lt; ActiveRecord::Base
151 224
152 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments 225 has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments
153 226
  227 + has_many :email_templates, :foreign_key => :owner_id
  228 +
154 # Although this should be a has_one relation, there are no non-silly names for 229 # Although this should be a has_one relation, there are no non-silly names for
155 # a foreign key on article to reference the template to which it is 230 # a foreign key on article to reference the template to which it is
156 # welcome_page... =P 231 # welcome_page... =P
@@ -178,6 +253,7 @@ class Profile &lt; ActiveRecord::Base @@ -178,6 +253,7 @@ class Profile &lt; ActiveRecord::Base
178 settings_items :description 253 settings_items :description
179 settings_items :fields_privacy, :type => :hash, :default => {} 254 settings_items :fields_privacy, :type => :hash, :default => {}
180 settings_items :email_suggestions, :type => :boolean, :default => false 255 settings_items :email_suggestions, :type => :boolean, :default => false
  256 + settings_items :administrator_mail_notification, :type => :boolean, :default => true
181 257
182 validates_length_of :description, :maximum => 550, :allow_nil => true 258 validates_length_of :description, :maximum => 550, :allow_nil => true
183 259
@@ -471,6 +547,10 @@ class Profile &lt; ActiveRecord::Base @@ -471,6 +547,10 @@ class Profile &lt; ActiveRecord::Base
471 self.articles.find(:all, options) 547 self.articles.find(:all, options)
472 end 548 end
473 549
  550 + def to_liquid
  551 + HashWithIndifferentAccess.new :name => name, :identifier => identifier
  552 + end
  553 +
474 class << self 554 class << self
475 555
476 # finds a profile by its identifier. This method is a shortcut to 556 # finds a profile by its identifier. This method is a shortcut to
@@ -613,15 +693,15 @@ private :generate_url, :url_options @@ -613,15 +693,15 @@ private :generate_url, :url_options
613 after_create :insert_default_article_set 693 after_create :insert_default_article_set
614 def insert_default_article_set 694 def insert_default_article_set
615 if template 695 if template
616 - copy_articles_from template 696 + self.save! if copy_articles_from template
617 else 697 else
618 default_set_of_articles.each do |article| 698 default_set_of_articles.each do |article|
619 article.profile = self 699 article.profile = self
620 article.advertise = false 700 article.advertise = false
621 article.save! 701 article.save!
622 end 702 end
  703 + self.save!
623 end 704 end
624 - self.save!  
625 end 705 end
626 706
627 # Override this method in subclasses of Profile to create a default article 707 # Override this method in subclasses of Profile to create a default article
@@ -642,10 +722,12 @@ private :generate_url, :url_options @@ -642,10 +722,12 @@ private :generate_url, :url_options
642 end 722 end
643 723
644 def copy_articles_from other 724 def copy_articles_from other
  725 + return false if other.top_level_articles.empty?
645 other.top_level_articles.each do |a| 726 other.top_level_articles.each do |a|
646 copy_article_tree a 727 copy_article_tree a
647 end 728 end
648 self.articles.reload 729 self.articles.reload
  730 + true
649 end 731 end
650 732
651 def copy_article_tree(article, parent=nil) 733 def copy_article_tree(article, parent=nil)
@@ -690,6 +772,29 @@ private :generate_url, :url_options @@ -690,6 +772,29 @@ private :generate_url, :url_options
690 end 772 end
691 end 773 end
692 774
  775 + # Adds many people to profile by id's
  776 + def add_members_by_id(people_ids)
  777 +
  778 + unless people_ids.nil? && people_ids.empty?
  779 +
  780 + people = Person.where(id: people_ids)
  781 + people.each do |person|
  782 +
  783 + add_member(person) unless person.is_member_of?(self)
  784 + end
  785 + end
  786 + end
  787 +
  788 + # Adds many people to profile by email's
  789 + def add_members_by_email(people_emails)
  790 +
  791 + people = User.where(email: people_emails)
  792 + people.each do |user|
  793 +
  794 + add_member(user.person) unless user.person.is_member_of?(self)
  795 + end
  796 + end
  797 +
693 def remove_member(person) 798 def remove_member(person)
694 self.disaffiliate(person, Profile::Roles.all_roles(environment.id)) 799 self.disaffiliate(person, Profile::Roles.all_roles(environment.id))
695 end 800 end
@@ -821,11 +926,11 @@ private :generate_url, :url_options @@ -821,11 +926,11 @@ private :generate_url, :url_options
821 self.forums.count.nonzero? 926 self.forums.count.nonzero?
822 end 927 end
823 928
824 - def admins 929 + def admins(with_entities = nil)
825 return [] if environment.blank? 930 return [] if environment.blank?
826 admin_role = Profile::Roles.admin(environment.id) 931 admin_role = Profile::Roles.admin(environment.id)
827 return [] if admin_role.blank? 932 return [] if admin_role.blank?
828 - self.members_by_role(admin_role) 933 + self.members_by_role(admin_role, with_entities)
829 end 934 end
830 935
831 def enable_contact? 936 def enable_contact?
@@ -936,6 +1041,13 @@ private :generate_url, :url_options @@ -936,6 +1041,13 @@ private :generate_url, :url_options
936 image.public_filename(:icon) if image.present? 1041 image.public_filename(:icon) if image.present?
937 end 1042 end
938 1043
  1044 + #FIXME make this test
  1045 + def profile_custom_image(size = :icon)
  1046 + image_path = profile_custom_icon if size == :icon
  1047 + image_path ||= image.public_filename(size) if image.present?
  1048 + image_path
  1049 + end
  1050 +
939 def jid(options = {}) 1051 def jid(options = {})
940 domain = options[:domain] || environment.default_hostname 1052 domain = options[:domain] || environment.default_hostname
941 "#{identifier}@#{domain}" 1053 "#{identifier}@#{domain}"