2019-03-17 13:36:03 +00:00
|
|
|
# frozen_string_literal: true
|
2019-03-15 01:32:57 +00:00
|
|
|
|
2009-09-20 14:06:57 +00:00
|
|
|
# Redmine - project management software
|
2023-01-01 06:19:35 +00:00
|
|
|
# Copyright (C) 2006-2023 Jean-Philippe Lang
|
2009-09-20 14:06:57 +00:00
|
|
|
#
|
|
|
|
|
# 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.
|
2011-08-31 08:46:55 +00:00
|
|
|
#
|
2009-09-20 14:06:57 +00:00
|
|
|
# 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.
|
2011-08-31 08:46:55 +00:00
|
|
|
#
|
2009-09-20 14:06:57 +00:00
|
|
|
# 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.
|
|
|
|
|
|
2024-01-23 11:52:38 +00:00
|
|
|
class Principal < ApplicationRecord
|
2012-04-27 16:28:35 +00:00
|
|
|
self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
|
2009-09-20 14:06:57 +00:00
|
|
|
|
2012-12-08 10:44:42 +00:00
|
|
|
# Account statuses
|
|
|
|
|
STATUS_ANONYMOUS = 0
|
|
|
|
|
STATUS_ACTIVE = 1
|
|
|
|
|
STATUS_REGISTERED = 2
|
|
|
|
|
STATUS_LOCKED = 3
|
|
|
|
|
|
2016-04-11 17:51:16 +00:00
|
|
|
class_attribute :valid_statuses
|
|
|
|
|
|
2009-09-20 14:06:57 +00:00
|
|
|
has_many :members, :foreign_key => 'user_id', :dependent => :destroy
|
2014-10-22 17:37:16 +00:00
|
|
|
has_many :memberships,
|
2017-04-04 17:52:24 +00:00
|
|
|
lambda {joins(:project).where.not(:projects => {:status => Project::STATUS_ARCHIVED})},
|
2014-10-22 17:37:16 +00:00
|
|
|
:class_name => 'Member',
|
|
|
|
|
:foreign_key => 'user_id'
|
2009-09-20 14:06:57 +00:00
|
|
|
has_many :projects, :through => :memberships
|
2011-07-23 18:18:13 +00:00
|
|
|
has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
|
2009-09-20 14:06:57 +00:00
|
|
|
|
2016-04-11 17:51:16 +00:00
|
|
|
validate :validate_status
|
|
|
|
|
|
2009-09-20 14:06:57 +00:00
|
|
|
# Groups and active users
|
2020-11-18 16:16:15 +00:00
|
|
|
scope :active, lambda {where(:status => STATUS_ACTIVE)}
|
2011-08-31 08:46:55 +00:00
|
|
|
|
2020-11-10 11:10:24 +00:00
|
|
|
scope :visible, (lambda do |*args|
|
2014-11-11 13:08:52 +00:00
|
|
|
user = args.first || User.current
|
|
|
|
|
|
|
|
|
|
if user.admin?
|
|
|
|
|
all
|
|
|
|
|
else
|
|
|
|
|
view_all_active = false
|
2023-12-27 08:07:22 +00:00
|
|
|
if user.memberships.any?
|
|
|
|
|
view_all_active = User.where(id: user.id).joins(memberships: :roles).where("#{Role.table_name}.users_visibility = ?", 'all').any?
|
2014-11-11 13:08:52 +00:00
|
|
|
else
|
|
|
|
|
view_all_active = user.builtin_role.users_visibility == 'all'
|
|
|
|
|
end
|
2015-06-17 11:38:48 +00:00
|
|
|
|
2014-11-11 13:08:52 +00:00
|
|
|
if view_all_active
|
|
|
|
|
active
|
|
|
|
|
else
|
|
|
|
|
# self and members of visible projects
|
2019-11-06 12:04:10 +00:00
|
|
|
active.where(
|
|
|
|
|
"#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
|
2014-11-11 13:08:52 +00:00
|
|
|
user.id, user.visible_project_ids
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
end
|
2020-11-10 11:10:24 +00:00
|
|
|
end)
|
2014-11-11 13:08:52 +00:00
|
|
|
|
2020-11-10 11:10:24 +00:00
|
|
|
scope :like, (lambda do |q|
|
2012-11-13 22:03:42 +00:00
|
|
|
q = q.to_s
|
2012-02-14 16:29:48 +00:00
|
|
|
if q.blank?
|
2012-11-13 22:03:42 +00:00
|
|
|
where({})
|
2012-02-14 16:29:48 +00:00
|
|
|
else
|
2021-10-03 19:44:39 +00:00
|
|
|
pattern = "%#{sanitize_sql_like q}%"
|
2021-10-09 06:35:47 +00:00
|
|
|
sql = +"LOWER(#{table_name}.login) LIKE LOWER(:p) ESCAPE :s"
|
|
|
|
|
sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p) ESCAPE :s)"
|
|
|
|
|
params = {:p => pattern, :s => '\\'}
|
2018-04-07 06:39:57 +00:00
|
|
|
|
2021-10-03 19:44:39 +00:00
|
|
|
tokens = q.split(/\s+/).reject(&:blank?).map {|token| "%#{sanitize_sql_like token}%"}
|
2018-04-07 06:39:57 +00:00
|
|
|
if tokens.present?
|
|
|
|
|
sql << ' OR ('
|
|
|
|
|
sql << tokens.map.with_index do |token, index|
|
2020-10-24 05:33:23 +00:00
|
|
|
params[:"token_#{index}"] = token
|
2021-10-09 06:35:47 +00:00
|
|
|
"(LOWER(#{table_name}.firstname) LIKE LOWER(:token_#{index}) ESCAPE :s OR LOWER(#{table_name}.lastname) LIKE LOWER(:token_#{index}) ESCAPE :s)"
|
2018-04-07 06:39:57 +00:00
|
|
|
end.join(' AND ')
|
|
|
|
|
sql << ')'
|
2012-02-14 16:29:48 +00:00
|
|
|
end
|
2012-11-13 22:03:42 +00:00
|
|
|
where(sql, params)
|
2012-02-14 16:29:48 +00:00
|
|
|
end
|
2020-11-10 11:10:24 +00:00
|
|
|
end)
|
2011-08-31 08:46:55 +00:00
|
|
|
|
2012-02-01 20:07:01 +00:00
|
|
|
# Principals that are members of a collection of projects
|
2020-11-10 11:10:24 +00:00
|
|
|
scope :member_of, (lambda do |projects|
|
2014-10-22 17:37:16 +00:00
|
|
|
projects = [projects] if projects.is_a?(Project)
|
|
|
|
|
if projects.blank?
|
2012-11-13 22:15:47 +00:00
|
|
|
where("1=0")
|
2012-02-01 20:07:01 +00:00
|
|
|
else
|
|
|
|
|
ids = projects.map(&:id)
|
2018-06-09 08:07:08 +00:00
|
|
|
# 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)
|
2012-02-01 20:07:01 +00:00
|
|
|
end
|
2020-11-10 11:10:24 +00:00
|
|
|
end)
|
2012-03-06 18:37:30 +00:00
|
|
|
# Principals that are not members of projects
|
2020-11-10 11:10:24 +00:00
|
|
|
scope :not_member_of, (lambda do |projects|
|
2024-02-08 00:57:07 +00:00
|
|
|
projects = [projects] if projects.is_a?(Project)
|
|
|
|
|
if projects.blank?
|
2012-11-13 22:15:47 +00:00
|
|
|
where("1=0")
|
2012-03-06 18:37:30 +00:00
|
|
|
else
|
|
|
|
|
ids = projects.map(&:id)
|
2012-11-13 22:15:47 +00:00
|
|
|
where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
|
2012-03-06 18:37:30 +00:00
|
|
|
end
|
2020-11-10 11:10:24 +00:00
|
|
|
end)
|
2020-11-18 16:16:15 +00:00
|
|
|
scope :sorted, lambda {order(*Principal.fields_for_order_statement)}
|
2012-02-01 20:07:01 +00:00
|
|
|
|
2020-04-25 08:01:59 +00:00
|
|
|
# Principals that can be added as watchers
|
2020-11-18 16:16:15 +00:00
|
|
|
scope :assignable_watchers, lambda {active.visible.where(:type => ['User', 'Group'])}
|
2020-04-25 08:01:59 +00:00
|
|
|
|
2010-01-30 10:24:23 +00:00
|
|
|
before_create :set_default_empty_values
|
2017-04-08 08:02:06 +00:00
|
|
|
before_destroy :nullify_projects_default_assigned_to
|
2010-09-10 18:46:23 +00:00
|
|
|
|
2017-01-03 18:28:45 +00:00
|
|
|
def reload(*args)
|
|
|
|
|
@project_ids = nil
|
|
|
|
|
super
|
|
|
|
|
end
|
|
|
|
|
|
2010-09-10 18:46:23 +00:00
|
|
|
def name(formatter = nil)
|
|
|
|
|
to_s
|
|
|
|
|
end
|
|
|
|
|
|
2015-01-17 14:14:12 +00:00
|
|
|
def mail=(*args)
|
|
|
|
|
nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def mail
|
|
|
|
|
nil
|
|
|
|
|
end
|
|
|
|
|
|
2014-11-11 13:08:52 +00:00
|
|
|
def visible?(user=User.current)
|
2018-10-06 16:03:26 +00:00
|
|
|
Principal.visible(user).find_by(:id => id) == self
|
2014-11-11 13:08:52 +00:00
|
|
|
end
|
|
|
|
|
|
2017-01-03 18:28:45 +00:00
|
|
|
# Returns true if the principal is a member of project
|
2014-10-23 21:46:40 +00:00
|
|
|
def member_of?(project)
|
2017-01-03 18:28:45 +00:00
|
|
|
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
|
2014-10-23 21:46:40 +00:00
|
|
|
end
|
|
|
|
|
|
2009-09-20 14:06:57 +00:00
|
|
|
def <=>(principal)
|
2023-06-29 14:42:54 +00:00
|
|
|
# avoid an error when sorting members without roles (#10053)
|
|
|
|
|
return -1 if principal.nil?
|
|
|
|
|
return nil unless principal.is_a?(Principal)
|
|
|
|
|
|
|
|
|
|
if self.class.name == principal.class.name
|
2015-08-12 12:54:06 +00:00
|
|
|
self.to_s.casecmp(principal.to_s)
|
2009-12-12 11:20:26 +00:00
|
|
|
else
|
|
|
|
|
# groups after users
|
|
|
|
|
principal.class.name <=> self.class.name
|
|
|
|
|
end
|
2009-09-20 14:06:57 +00:00
|
|
|
end
|
2011-08-31 08:46:55 +00:00
|
|
|
|
2013-01-23 17:44:28 +00:00
|
|
|
# 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
|
|
|
|
|
|
2015-08-15 06:57:55 +00:00
|
|
|
# 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
|
2015-08-15 09:28:34 +00:00
|
|
|
principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
|
|
|
|
|
principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
|
|
|
|
|
|
2023-02-06 13:50:33 +00:00
|
|
|
if principal.nil? && keyword.include?(' ')
|
2015-08-15 06:57:55 +00:00
|
|
|
firstname, lastname = *(keyword.split) # "First Last Throwaway"
|
2019-11-09 09:18:51 +00:00
|
|
|
principal ||=
|
2020-11-07 12:31:10 +00:00
|
|
|
principals.detect do |a|
|
2019-11-09 09:18:51 +00:00
|
|
|
a.is_a?(User) &&
|
|
|
|
|
firstname.casecmp(a.firstname.to_s) == 0 &&
|
|
|
|
|
lastname.casecmp(a.lastname.to_s) == 0
|
2020-11-07 12:31:10 +00:00
|
|
|
end
|
2015-08-15 06:57:55 +00:00
|
|
|
end
|
|
|
|
|
if principal.nil?
|
|
|
|
|
principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
|
|
|
|
|
end
|
|
|
|
|
principal
|
|
|
|
|
end
|
|
|
|
|
|
2017-04-08 08:02:06 +00:00
|
|
|
def nullify_projects_default_assigned_to
|
|
|
|
|
Project.where(default_assigned_to: self).update_all(default_assigned_to_id: nil)
|
|
|
|
|
end
|
|
|
|
|
|
2010-01-30 10:24:23 +00:00
|
|
|
protected
|
2011-08-31 08:46:55 +00:00
|
|
|
|
2010-01-30 10:24:23 +00:00
|
|
|
# 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
|
2016-04-11 17:51:16 +00:00
|
|
|
|
|
|
|
|
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
|
2009-09-20 14:06:57 +00:00
|
|
|
end
|