Commit 3926c76a757800dd1433a8f9cb7dab9d5250954d
1 parent
8c31af4c
Exists in
theme-brasil-digital-from-staging
and in
9 other branches
Remove admins.map that generate a SQL query for each user causing performance problems.
Showing
3 changed files
with
181 additions
and
15 deletions
Show diff stats
app/models/organization.rb
... | ... | @@ -8,6 +8,30 @@ class Organization < Profile |
8 | 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 | 36 | settings_items :closed, :type => :boolean, :default => false |
13 | 37 | def closed? |
... | ... | @@ -154,7 +178,8 @@ class Organization < Profile |
154 | 178 | end |
155 | 179 | |
156 | 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 | 183 | if emails.empty? |
159 | 184 | emails << environment.contact_email |
160 | 185 | end | ... | ... |
app/models/person.rb
1 | 1 | # A person is the profile of an user holding all relationships with the rest of the system |
2 | 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 | 9 | SEARCH_FILTERS = { |
7 | 10 | :order => %w[more_recent more_popular more_active], |
... | ... | @@ -16,10 +19,12 @@ class Person < Profile |
16 | 19 | acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)} |
17 | 20 | acts_as_accessor |
18 | 21 | |
19 | - scope :members_of, lambda { |resources| | |
22 | + scope :members_of, lambda { |resources, extra_joins = nil| | |
20 | 23 | resources = [resources] if !resources.kind_of?(Array) |
21 | 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 | 30 | scope :not_members_of, lambda { |resources| |
... | ... | @@ -28,10 +33,11 @@ class Person < Profile |
28 | 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 | 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 | 43 | scope :not_friends_of, lambda { |resources| |
... | ... | @@ -39,6 +45,18 @@ roles] } |
39 | 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 | 60 | def has_permission_with_admin?(permission, resource) |
43 | 61 | return true if resource.blank? || resource.admins.include?(self) |
44 | 62 | return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self) |
... | ... | @@ -68,6 +86,10 @@ roles] } |
68 | 86 | memberships.where('role_assignments.role_id = ?', role.id) |
69 | 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 | 93 | has_many :friendships, :dependent => :destroy |
72 | 94 | has_many :friends, :class_name => 'Person', :through => :friendships |
73 | 95 | |
... | ... | @@ -125,6 +147,11 @@ roles] } |
125 | 147 | self.tracked_notifications.exists?(activity) |
126 | 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 | 155 | # Sets the identifier for this person. Raises an exception when called on a |
129 | 156 | # existing person (since peoples' identifiers cannot be changed) |
130 | 157 | def identifier=(value) |
... | ... | @@ -187,9 +214,11 @@ roles] } |
187 | 214 | organization |
188 | 215 | organization_website |
189 | 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 | 222 | validates_multiparameter_assignments |
194 | 223 | |
195 | 224 | def self.fields | ... | ... |
app/models/profile.rb
... | ... | @@ -3,7 +3,7 @@ |
3 | 3 | # which by default is the one returned by Environment:default. |
4 | 4 | class Profile < ActiveRecord::Base |
5 | 5 | |
6 | - attr_accessible :name, :identifier, :public_profile, :nickname, :custom_footer, :custom_header, :address, :zip_code, :contact_phone, :image_builder, :description, :closed, :template_id, :environment, :lat, :lng, :is_template, :fields_privacy, :preferred_domain_id, :category_ids, :country, :city, :state, :national_region_code, :email, :contact_email, :redirect_l10n, :notification_time, :redirection_after_login, :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 | 8 | # use for internationalizable human type names in search facets |
9 | 9 | # reimplement on subclasses |
... | ... | @@ -22,6 +22,34 @@ class Profile < ActiveRecord::Base |
22 | 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 | 53 | NUMBER_OF_BOXES = 4 |
26 | 54 | |
27 | 55 | def self.default_search_display |
... | ... | @@ -79,6 +107,7 @@ class Profile < ActiveRecord::Base |
79 | 107 | 'invite_members' => N_('Invite members'), |
80 | 108 | 'send_mail_to_members' => N_('Send e-Mail to members'), |
81 | 109 | 'manage_custom_roles' => N_('Manage custom roles'), |
110 | + 'manage_email_templates' => N_('Manage Email Templates'), | |
82 | 111 | } |
83 | 112 | |
84 | 113 | acts_as_accessible |
... | ... | @@ -100,6 +129,50 @@ class Profile < ActiveRecord::Base |
100 | 129 | } |
101 | 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 | 176 | def members |
104 | 177 | scopes = plugins.dispatch_scopes(:organization_members, self) |
105 | 178 | scopes << Person.members_of(self) |
... | ... | @@ -119,8 +192,8 @@ class Profile < ActiveRecord::Base |
119 | 192 | alias_method_chain :count, :distinct |
120 | 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 | 197 | end |
125 | 198 | |
126 | 199 | acts_as_having_boxes |
... | ... | @@ -151,6 +224,8 @@ class Profile < ActiveRecord::Base |
151 | 224 | |
152 | 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 | 229 | # Although this should be a has_one relation, there are no non-silly names for |
155 | 230 | # a foreign key on article to reference the template to which it is |
156 | 231 | # welcome_page... =P |
... | ... | @@ -178,6 +253,7 @@ class Profile < ActiveRecord::Base |
178 | 253 | settings_items :description |
179 | 254 | settings_items :fields_privacy, :type => :hash, :default => {} |
180 | 255 | settings_items :email_suggestions, :type => :boolean, :default => false |
256 | + settings_items :administrator_mail_notification, :type => :boolean, :default => true | |
181 | 257 | |
182 | 258 | validates_length_of :description, :maximum => 550, :allow_nil => true |
183 | 259 | |
... | ... | @@ -471,6 +547,10 @@ class Profile < ActiveRecord::Base |
471 | 547 | self.articles.find(:all, options) |
472 | 548 | end |
473 | 549 | |
550 | + def to_liquid | |
551 | + HashWithIndifferentAccess.new :name => name, :identifier => identifier | |
552 | + end | |
553 | + | |
474 | 554 | class << self |
475 | 555 | |
476 | 556 | # finds a profile by its identifier. This method is a shortcut to |
... | ... | @@ -613,15 +693,15 @@ private :generate_url, :url_options |
613 | 693 | after_create :insert_default_article_set |
614 | 694 | def insert_default_article_set |
615 | 695 | if template |
616 | - copy_articles_from template | |
696 | + self.save! if copy_articles_from template | |
617 | 697 | else |
618 | 698 | default_set_of_articles.each do |article| |
619 | 699 | article.profile = self |
620 | 700 | article.advertise = false |
621 | 701 | article.save! |
622 | 702 | end |
703 | + self.save! | |
623 | 704 | end |
624 | - self.save! | |
625 | 705 | end |
626 | 706 | |
627 | 707 | # Override this method in subclasses of Profile to create a default article |
... | ... | @@ -642,10 +722,12 @@ private :generate_url, :url_options |
642 | 722 | end |
643 | 723 | |
644 | 724 | def copy_articles_from other |
725 | + return false if other.top_level_articles.empty? | |
645 | 726 | other.top_level_articles.each do |a| |
646 | 727 | copy_article_tree a |
647 | 728 | end |
648 | 729 | self.articles.reload |
730 | + true | |
649 | 731 | end |
650 | 732 | |
651 | 733 | def copy_article_tree(article, parent=nil) |
... | ... | @@ -690,6 +772,29 @@ private :generate_url, :url_options |
690 | 772 | end |
691 | 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 | 798 | def remove_member(person) |
694 | 799 | self.disaffiliate(person, Profile::Roles.all_roles(environment.id)) |
695 | 800 | end |
... | ... | @@ -821,11 +926,11 @@ private :generate_url, :url_options |
821 | 926 | self.forums.count.nonzero? |
822 | 927 | end |
823 | 928 | |
824 | - def admins | |
929 | + def admins(with_entities = nil) | |
825 | 930 | return [] if environment.blank? |
826 | 931 | admin_role = Profile::Roles.admin(environment.id) |
827 | 932 | return [] if admin_role.blank? |
828 | - self.members_by_role(admin_role) | |
933 | + self.members_by_role(admin_role, with_entities) | |
829 | 934 | end |
830 | 935 | |
831 | 936 | def enable_contact? |
... | ... | @@ -936,6 +1041,13 @@ private :generate_url, :url_options |
936 | 1041 | image.public_filename(:icon) if image.present? |
937 | 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 | 1051 | def jid(options = {}) |
940 | 1052 | domain = options[:domain] || environment.default_hostname |
941 | 1053 | "#{identifier}@#{domain}" | ... | ... |