123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- # frozen_string_literal: true
-
- # Redmine - project management software
- # Copyright (C) 2006-2020 Jean-Philippe Lang
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- class Principal < ActiveRecord::Base
- self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
-
- # Account statuses
- STATUS_ANONYMOUS = 0
- STATUS_ACTIVE = 1
- STATUS_REGISTERED = 2
- STATUS_LOCKED = 3
-
- class_attribute :valid_statuses
-
- has_many :members, :foreign_key => 'user_id', :dependent => :destroy
- has_many :memberships,
- lambda {joins(:project).where.not(:projects => {:status => Project::STATUS_ARCHIVED})},
- :class_name => 'Member',
- :foreign_key => 'user_id'
- has_many :projects, :through => :memberships
- has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
-
- validate :validate_status
-
- # Groups and active users
- scope :active, lambda { where(:status => STATUS_ACTIVE) }
-
- scope :visible, lambda {|*args|
- user = args.first || User.current
-
- if user.admin?
- all
- else
- view_all_active = false
- if user.memberships.to_a.any?
- view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
- else
- view_all_active = user.builtin_role.users_visibility == 'all'
- end
-
- if view_all_active
- active
- else
- # self and members of visible projects
- active.where(
- "#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
- user.id, user.visible_project_ids
- )
- end
- end
- }
-
- scope :like, lambda {|q|
- q = q.to_s
- if q.blank?
- where({})
- else
- pattern = "%#{q}%"
- sql = +"LOWER(#{table_name}.login) LIKE LOWER(:p)"
- sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
- params = {:p => pattern}
-
- tokens = q.split(/\s+/).reject(&:blank?).map { |token| "%#{token}%" }
- if tokens.present?
- sql << ' OR ('
- sql << tokens.map.with_index do |token, index|
- params[:"token_#{index}"] = token
- "(LOWER(#{table_name}.firstname) LIKE LOWER(:token_#{index}) OR LOWER(#{table_name}.lastname) LIKE LOWER(:token_#{index}))"
- end.join(' AND ')
- sql << ')'
- end
-
- where(sql, params)
- end
- }
-
- # Principals that are members of a collection of projects
- scope :member_of, lambda {|projects|
- projects = [projects] if projects.is_a?(Project)
- if projects.blank?
- where("1=0")
- else
- ids = projects.map(&:id)
- # include active and locked users
- where(:status => [STATUS_LOCKED, STATUS_ACTIVE]).
- where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
- end
- }
- # Principals that are not members of projects
- scope :not_member_of, lambda {|projects|
- projects = [projects] unless projects.is_a?(Array)
- if projects.empty?
- where("1=0")
- else
- ids = projects.map(&:id)
- where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
- end
- }
- scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
-
- # Principals that can be added as watchers
- scope :assignable_watchers, lambda { active.visible.where(:type => ['User', 'Group']) }
-
- before_create :set_default_empty_values
- before_destroy :nullify_projects_default_assigned_to
-
- def reload(*args)
- @project_ids = nil
- super
- end
-
- def name(formatter = nil)
- to_s
- end
-
- def mail=(*args)
- nil
- end
-
- def mail
- nil
- end
-
- def visible?(user=User.current)
- Principal.visible(user).find_by(:id => id) == self
- end
-
- # Returns true if the principal is a member of project
- def member_of?(project)
- project.is_a?(Project) && project_ids.include?(project.id)
- end
-
- # Returns an array of the project ids that the principal is a member of
- def project_ids
- @project_ids ||= super.freeze
- end
-
- def <=>(principal)
- if principal.nil?
- -1
- elsif self.class.name == principal.class.name
- self.to_s.casecmp(principal.to_s)
- else
- # groups after users
- principal.class.name <=> self.class.name
- end
- end
-
- # Returns an array of fields names than can be used to make an order statement for principals.
- # Users are sorted before Groups.
- # Examples:
- def self.fields_for_order_statement(table=nil)
- table ||= table_name
- columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
- columns.uniq.map {|field| "#{table}.#{field}"}
- end
-
- # Returns the principal that matches the keyword among principals
- def self.detect_by_keyword(principals, keyword)
- keyword = keyword.to_s
- return nil if keyword.blank?
-
- principal = nil
- principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
- principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
-
- if principal.nil? && / /.match?(keyword)
- firstname, lastname = *(keyword.split) # "First Last Throwaway"
- principal ||=
- principals.detect {|a|
- a.is_a?(User) &&
- firstname.casecmp(a.firstname.to_s) == 0 &&
- lastname.casecmp(a.lastname.to_s) == 0
- }
- end
- if principal.nil?
- principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
- end
- principal
- end
-
- def nullify_projects_default_assigned_to
- Project.where(default_assigned_to: self).update_all(default_assigned_to_id: nil)
- end
-
- protected
-
- # Make sure we don't try to insert NULL values (see #4632)
- def set_default_empty_values
- self.login ||= ''
- self.hashed_password ||= ''
- self.firstname ||= ''
- self.lastname ||= ''
- true
- end
-
- def validate_status
- if status_changed? && self.class.valid_statuses.present?
- unless self.class.valid_statuses.include?(status)
- errors.add :status, :invalid
- end
- end
- end
- end
-
- require_dependency "user"
- require_dependency "group"
|