diff --git a/app/models/organization.rb b/app/models/organization.rb index 17fb7b8..1d4c638 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -8,6 +8,30 @@ class Organization < Profile :display => %w[compact] } + # An Organization is considered visible to a given person if one of the +# following conditions are met: +# 1) The user is an environment administrator. +# 2) The user is an administrator of the organization. +# 3) The user is a member of the organization and the organization is +# visible. +# 4) The user is not a member of the organization but the organization is +# visible, public and enabled. + def self.visible_for_person(person) + joins('LEFT JOIN "role_assignments" ON ("role_assignments"."resource_id" = "profiles"."id" + AND "role_assignments"."resource_type" = \'Profile\') OR ( + "role_assignments"."resource_id" = "profiles"."environment_id" AND + "role_assignments"."resource_type" = \'Environment\' )') + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"') + .where( + ['( (roles.key = ? OR roles.key = ?) AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) + OR + ( ( ( role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR + ( profiles.public_profile = ? AND profiles.enabled = ? ) ) AND + ( profiles.visible = ? ) )', + 'profile_admin', 'environment_administrator', Profile.name, person.id, + Profile.name, person.id, true, true, true] + ).uniq + end settings_items :closed, :type => :boolean, :default => false def closed? @@ -154,7 +178,8 @@ class Organization < Profile end def notification_emails - emails = [contact_email].select(&:present?) + admins.map(&:email) + # TODO: Add performance improvement here! + emails = [contact_email].select(&:present?) + admins([:user]).pluck(:email) if emails.empty? emails << environment.contact_email end diff --git a/app/models/person.rb b/app/models/person.rb index e6e37b8..342a3ed 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -1,7 +1,10 @@ # A person is the profile of an user holding all relationships with the rest of the system class Person < Profile - 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 + 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, :following_articiles SEARCH_FILTERS = { :order => %w[more_recent more_popular more_active], @@ -16,10 +19,12 @@ class Person < Profile acts_as_trackable :after_add => Proc.new {|p,t| notify_activity(t)} acts_as_accessor - scope :members_of, lambda { |resources| + scope :members_of, lambda { |resources, extra_joins = nil| resources = [resources] if !resources.kind_of?(Array) conditions = resources.map {|resource| "role_assignments.resource_type = '#{resource.class.base_class.name}' AND role_assignments.resource_id = #{resource.id || -1}"}.join(' OR ') - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => [conditions] } + joins = [:role_assignments] + joins += extra_joins if extra_joins.is_a? Array + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => [conditions] } } scope :not_members_of, lambda { |resources| @@ -28,10 +33,11 @@ class Person < Profile { :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] } } - scope :by_role, lambda { |roles| + scope :by_role, lambda { |roles, extra_joins = nil| roles = [roles] unless roles.kind_of?(Array) - { :select => 'DISTINCT profiles.*', :joins => :role_assignments, :conditions => ['role_assignments.role_id IN (?)', -roles] } + joins = [:role_assignments] + joins += extra_joins if extra_joins.is_a? Array + { :select => 'DISTINCT profiles.*', :joins => joins, :conditions => ['role_assignments.role_id IN (?)',roles] } } scope :not_friends_of, lambda { |resources| @@ -39,6 +45,18 @@ roles] } { :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)] } } + scope :visible_for_person, lambda { |person| + joins('LEFT JOIN "role_assignments" ON + "role_assignments"."resource_id" = "profiles"."environment_id" AND + "role_assignments"."resource_type" = \'Environment\'') + .joins('LEFT JOIN "roles" ON "role_assignments"."role_id" = "roles"."id"') + .joins('LEFT JOIN "friendships" ON "friendships"."friend_id" = "profiles"."id"') + .where( + ['( roles.key = ? AND role_assignments.accessor_type = ? AND role_assignments.accessor_id = ? ) OR ( + ( ( friendships.person_id = ? ) OR (profiles.public_profile = ?)) AND (profiles.visible = ?) )', 'environment_administrator', Profile.name, person.id, person.id, true, true] + ).uniq + } + def has_permission_with_admin?(permission, resource) return true if resource.blank? || resource.admins.include?(self) return true if resource.kind_of?(Profile) && resource.environment.admins.include?(self) @@ -68,6 +86,10 @@ roles] } memberships.where('role_assignments.role_id = ?', role.id) end + #Article followers relation + has_many :article_followers, :dependent => :destroy + has_many :following_articiles, :class_name => 'Article', :through => :article_followers, :source => :article + has_many :friendships, :dependent => :destroy has_many :friends, :class_name => 'Person', :through => :friendships @@ -125,6 +147,11 @@ roles] } self.tracked_notifications.exists?(activity) end + def can_post_content?(profile, parent=nil) + (!parent.nil? && (parent.allow_create?(self))) || + (self.has_permission?('post_content', profile) || self.has_permission?('publish_content', profile)) + end + # Sets the identifier for this person. Raises an exception when called on a # existing person (since peoples' identifiers cannot be changed) def identifier=(value) @@ -187,9 +214,11 @@ roles] } organization organization_website contact_phone - contact_information + contact_informatioin ] + 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' + validates_multiparameter_assignments def self.fields diff --git a/app/models/profile.rb b/app/models/profile.rb index 7c5a0e9..d0c8fa7 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -3,7 +3,7 @@ # which by default is the one returned by Environment:default. class Profile < ActiveRecord::Base - 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 + 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 # use for internationalizable human type names in search facets # reimplement on subclasses @@ -22,6 +22,34 @@ class Profile < ActiveRecord::Base :display => %w[compact] } + settings_items :custom_fields, :default => {} + + def custom_field_value(field) + if !self.custom_fields.blank? + self.custom_fields[field][:value] + else + '' + end + end + + def custom_field_title(field) + if !self.custom_fields.blank? + self.custom_fields[field][:title] + else + '' + end + end + + def custom_fields_template + fields = {} + fields = self.template.custom_fields unless self.template.blank? + fields + end + + def custom_fields_template_title(field) + self.template.custom_fields[field][:title] + end + NUMBER_OF_BOXES = 4 def self.default_search_display @@ -79,6 +107,7 @@ class Profile < ActiveRecord::Base 'invite_members' => N_('Invite members'), 'send_mail_to_members' => N_('Send e-Mail to members'), 'manage_custom_roles' => N_('Manage custom roles'), + 'manage_email_templates' => N_('Manage Email Templates'), } acts_as_accessible @@ -100,6 +129,50 @@ class Profile < ActiveRecord::Base } scope :no_templates, {:conditions => {:is_template => false}} + # Returns a scoped object to select profiles in a given location or in a radius + # distance from the given location center. + # The parameter can be the `request.params` with the keys: + # * `country`: Country code string. + # * `state`: Second-level administrative country subdivisions. + # * `city`: City full name for center definition, or as set by users. + # * `lat`: The latitude to define the center of georef search. + # * `lng`: The longitude to define the center of georef search. + # * `distance`: Define the search radius in kilometers. + # NOTE: This method may return an exception object, to inform filter error. + # When chaining scopes, is hardly recommended you to add this as the last one, + # if you can't be sure about the provided parameters. + def self.by_location(params) + params = params.with_indifferent_access + if params[:distance].blank? + where_code = [] + [ :city, :state, :country ].each do |place| + unless params[place].blank? + # ... So we must to find on this named location + # TODO: convert location attrs to a table collumn + where_code << "(profiles.data like '%#{place}: #{params[place]}%')" + end + end + self.where where_code.join(' AND ') + else # Filter in a georef circle + unless params[:lat].blank? && params[:lng].blank? + lat, lng = [ params[:lat].to_f, params[:lng].to_f ] + end + if !lat + location = [ params[:city], params[:state], params[:country] ].compact.join(', ') + if location.blank? + return Exception.new ( + _('You must to provide `lat` and `lng`, or `city` and `country` to define the center of the search circle, defined by `distance`.') + ) + end + lat, lng = Noosfero::GeoRef.location_to_georef location + end + dist = params[:distance].to_f + self.where "#{Noosfero::GeoRef.sql_dist lat, lng} <= #{dist}" + end + end + + include TimeScopes + def members scopes = plugins.dispatch_scopes(:organization_members, self) scopes << Person.members_of(self) @@ -119,8 +192,8 @@ class Profile < ActiveRecord::Base alias_method_chain :count, :distinct end - def members_by_role(roles) - Person.members_of(self).by_role(roles) + def members_by_role(roles, with_entities = nil) + Person.members_of(self, with_entities).by_role(roles, with_entities) end acts_as_having_boxes @@ -151,6 +224,8 @@ class Profile < ActiveRecord::Base has_many :comments_received, :class_name => 'Comment', :through => :articles, :source => :comments + has_many :email_templates, :foreign_key => :owner_id + # Although this should be a has_one relation, there are no non-silly names for # a foreign key on article to reference the template to which it is # welcome_page... =P @@ -178,6 +253,7 @@ class Profile < ActiveRecord::Base settings_items :description settings_items :fields_privacy, :type => :hash, :default => {} settings_items :email_suggestions, :type => :boolean, :default => false + settings_items :administrator_mail_notification, :type => :boolean, :default => true validates_length_of :description, :maximum => 550, :allow_nil => true @@ -471,6 +547,10 @@ class Profile < ActiveRecord::Base self.articles.find(:all, options) end + def to_liquid + HashWithIndifferentAccess.new :name => name, :identifier => identifier + end + class << self # finds a profile by its identifier. This method is a shortcut to @@ -613,15 +693,15 @@ private :generate_url, :url_options after_create :insert_default_article_set def insert_default_article_set if template - copy_articles_from template + self.save! if copy_articles_from template else default_set_of_articles.each do |article| article.profile = self article.advertise = false article.save! end + self.save! end - self.save! end # Override this method in subclasses of Profile to create a default article @@ -642,10 +722,12 @@ private :generate_url, :url_options end def copy_articles_from other + return false if other.top_level_articles.empty? other.top_level_articles.each do |a| copy_article_tree a end self.articles.reload + true end def copy_article_tree(article, parent=nil) @@ -690,6 +772,29 @@ private :generate_url, :url_options end end + # Adds many people to profile by id's + def add_members_by_id(people_ids) + + unless people_ids.nil? && people_ids.empty? + + people = Person.where(id: people_ids) + people.each do |person| + + add_member(person) unless person.is_member_of?(self) + end + end + end + + # Adds many people to profile by email's + def add_members_by_email(people_emails) + + people = User.where(email: people_emails) + people.each do |user| + + add_member(user.person) unless user.person.is_member_of?(self) + end + end + def remove_member(person) self.disaffiliate(person, Profile::Roles.all_roles(environment.id)) end @@ -821,11 +926,11 @@ private :generate_url, :url_options self.forums.count.nonzero? end - def admins + def admins(with_entities = nil) return [] if environment.blank? admin_role = Profile::Roles.admin(environment.id) return [] if admin_role.blank? - self.members_by_role(admin_role) + self.members_by_role(admin_role, with_entities) end def enable_contact? @@ -936,6 +1041,13 @@ private :generate_url, :url_options image.public_filename(:icon) if image.present? end + #FIXME make this test + def profile_custom_image(size = :icon) + image_path = profile_custom_icon if size == :icon + image_path ||= image.public_filename(size) if image.present? + image_path + end + def jid(options = {}) domain = options[:domain] || environment.default_hostname "#{identifier}@#{domain}" -- libgit2 0.21.2