socialforge/app/models/user.rb

1245 lines
42 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Redmine - project management software
# Copyright (C) 2006-2013 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.
require "digest/sha1"
require 'elasticsearch/model'
class User < Principal
TEACHER = 0
STUDENT = 1
ENTERPRISE = 2
DEVELOPER = 3
include Redmine::SafeAttributes
seems_rateable_rater
#elasticsearch
include Elasticsearch::Model
#elasticsearch kaminari init
Kaminari::Hooks.init
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari
settings index: { number_of_shards: 5 } do
mappings dynamic: 'false' do
indexes :login, analyzer: 'smartcn',index_options: 'offsets'
indexes :firstname, analyzer: 'smartcn',index_options: 'offsets'
indexes :lastname, analyzer: 'smartcn',index_options: 'offsets'
indexes :last_login_on, index:"not_analyzed",type: 'date'
end
end
# Different ways of displaying/sorting users
USER_FORMATS = {
:firstname_lastname => {
:string => '#{firstname} #{lastname}',
:order => %w(firstname lastname id),
:setting_order => 1
},
:firstname_lastinitial => {
:string => '#{firstname} #{lastname.to_s.chars.first}.',
:order => %w(firstname lastname id),
:setting_order => 2
},
:firstname => {
:string => '#{firstname}',
:order => %w(firstname id),
:setting_order => 3
},
:lastname_firstname => {
:string => '#{lastname} #{firstname}',
:order => %w(lastname firstname id),
:setting_order => 4
},
:lastname_coma_firstname => {
:string => '#{lastname}, #{firstname}',
:order => %w(lastname firstname id),
:setting_order => 5
},
:lastname => {
:string => '#{lastname}',
:order => %w(lastname id),
:setting_order => 6
},
:username => {
:string => '#{login}',
:order => %w(login id),
:setting_order => 7
},
}
#每日一报、一事一报、不报
MAIL_NOTIFICATION_OPTIONS = [
#['week', :label_user_mail_option_week],
['day', :label_user_mail_option_day],
['all', :label_user_mail_option_all],
['none', :label_user_mail_option_none]
]
has_many :homework_users
has_many :homework_attaches, :through => :homework_users
has_many :homework_evaluations
#问卷相关关关系
has_many :poll_users, :dependent => :destroy
has_many :poll_votes, :dependent => :destroy
has_many :poll, :dependent => :destroy #用户创建的问卷
has_many :answers, :source => :poll, :through => :poll_users, :dependent => :destroy #用户已经完成问答的问卷
# end
#在线测验相关关系
has_many :exercise_user, :dependent => :destroy #答卷中间表
has_many :exercise_answer, :dependent => :destroy #针对每个题目学生的答案
has_many :exercises, :dependent => :destroy #创建的试卷
has_many :exercises_answers, :source => :exercise, :through => :exercise_user, :dependent => :destroy #用户已经完成问答的试卷
#end
#作业相关关系
has_many :homework_commons, :dependent => :destroy
has_many :student_works, :dependent => :destroy
has_many :student_works_evaluation_distributions, :dependent => :destroy
has_many :student_works_scores, :dependent => :destroy
has_many :student_work_projects, :dependent => :destroy
#end
has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
:after_remove => Proc.new {|user, group| group.user_removed(user)}
has_many :changesets, :dependent => :nullify
has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'"
has_one :blog, :class_name => 'Blog', :foreign_key => "author_id"
has_many :org_document_comments, :dependent =>:destroy, :foreign_key => "creator_id"
has_one :api_token, :class_name => 'Token', :conditions => "action='api'"
belongs_to :auth_source
has_many :org_members
has_many :organizations, :through => :org_members
belongs_to :ucourse, :class_name => 'Course', :foreign_key => :id #huang
## added by xianbo for delete
# has_many :biding_projects, :dependent => :destroy
has_many :contesting_projects, :dependent => :destroy
belongs_to :softapplication, :foreign_key => 'id', :dependent => :destroy
##ended by xianbo
#####fq
has_many :jours, :class_name => 'JournalsForMessage', :dependent => :destroy
has_many :journals_messages, :class_name => 'JournalsForMessage', :foreign_key => "user_id", :dependent => :destroy
# has_many :bids, :foreign_key => 'author_id', :dependent => :destroy
has_many :contests, :foreign_key => 'author_id', :dependent => :destroy
has_many :softapplications, :foreign_key => 'user_id', :dependent => :destroy
has_many :journals_for_messages, :as => :jour, :dependent => :destroy
has_many :journal_replies, :dependent => :destroy
has_many :activities, :dependent => :destroy
has_many :students_for_courses
#has_many :courses, :through => :students_for_courses, :source => :project
has_many :acts, :class_name => 'Activity', :as => :act, :dependent => :destroy
has_many :principal_acts, :class_name => 'PrincipalActivity',:as =>:principal_act ,:dependent => :destroy
has_many :file_commit, :class_name => 'Attachment', :foreign_key => 'author_id', :conditions => "container_type = 'Project' or container_type = 'Version'"
####
# added by bai
has_many :join_in_contests, :dependent => :destroy
has_many :news, :foreign_key => 'author_id'
has_many :contestnotification, :foreign_key => 'author_id'
has_many :comments, :foreign_key => 'author_id'
has_many :notificationcomments, :foreign_key => 'author_id'
has_many :wiki_contents, :foreign_key => 'author_id'
has_many :journals
has_many :messages, :foreign_key => 'author_id'
has_one :user_score, :dependent => :destroy
has_many :documents # 项目中关联的文档再次与人关联
# 关联消息表
has_many :forge_messages
has_many :course_messages
has_many :memo_messages
has_many :user_feedback_messages
has_one :onclick_time
has_many :system_messages
has_many :at_messages
# 虚拟转换
has_many :new_jours, :as => :jour, :class_name => 'JournalsForMessage', :conditions => "status=1"
has_many :issue_assigns, :class_name => 'ForgeMessage', :conditions => 'viewed=0 and forge_message_type="Issue"'
has_many :status_updates, :class_name => 'ForgeMessage', :conditions => 'viewed=0 and forge_message_type="Journal"'
# 邮件邀请状态
has_many :invite_lists, :dependent => :destroy
# end
# 课程贡献榜
has_many :course_contributor_scores, :dependent => :destroy
######added by nie
has_many :project_infos, :dependent => :destroy
has_one :user_status, :dependent => :destroy
#####
has_many :shares ,:dependent => :destroy
has_one :user_wechat
# add by zjc
has_one :level, :class_name => 'UserLevels', :dependent => :destroy
has_many :memos , :foreign_key => 'author_id'
#####
scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
scope :visible, lambda {|*args|
nil
}
acts_as_attachable :view_permission => :view_files,
:delete_permission => :manage_files
acts_as_customizable
############################added by william
acts_as_taggable
scope :by_join_date, order("created_on DESC")
############################# added by liuping 关注
acts_as_watchable
has_one :user_extensions,:dependent => :destroy
## end
# default_scope -> { includes(:user_extensions, :user_score) }
scope :teacher, -> {
joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::TEACHER)
}
scope :student, -> {
joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::STUDENT)
}
scope :developer, -> {
joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::DEVELOPER)
}
scope :enterprise, -> {
joins(:user_extensions).where('user_extensions.identity = ?', UserExtensions::ENTERPRISE)
}
attr_accessor :password, :password_confirmation
attr_accessor :last_before_login_on
# Prevents unauthorized assignments
attr_protected :login, :admin, :password, :password_confirmation, :hashed_password
LOGIN_LENGTH_LIMIT = 25
MAIL_LENGTH_LIMIT = 60
validates_presence_of :login, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
validates_uniqueness_of :mail, :if => Proc.new { |user| user.mail_changed? && user.mail.present? }, :case_sensitive => false
# Login must contain letters, numbers, underscores only
validates_format_of :login, :with => /\A[a-z0-9_\-\.]*\z/i
validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
validates_length_of :firstname, :maximum => 30
validates_length_of :lastname, :maximum => 30
validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true
validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true
validates_confirmation_of :password, :allow_nil => true
validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
validate :validate_password_length
# validates_email_realness_of :mail
before_create :set_mail_notification
before_save :update_hashed_password
before_destroy :remove_references_before_destroy,:delete_user_ealasticsearch_index
# added by fq
after_create :act_as_activity, :add_onclick_time, :act_as_principal_activity,:create_user_ealasticsearch_index
# end
# 更新邮箱用户或用户名的同事,同步更新邀请信息
after_update :update_invite_list,:update_user_ealasticsearch_index
include Trustie::Gitlab::ManageUser
scope :in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
}
scope :not_in_group, lambda {|group|
group_id = group.is_a?(Group) ? group.id : group.to_i
where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
}
scope :sorted, lambda { order(*User.fields_for_order_statement)}
scope :indexable,lambda { where('id not in (2,4)')} #用于elastic建索引的scope,id为2是匿名用户4是管理员不能被索引
scope :like, lambda {|arg, type|
if arg.blank?
where(nil)
else
pattern = "%#{arg.to_s.strip.downcase}%"
#where(" LOWER(concat(lastname, firstname)) LIKE :p ", :p => pattern)
if type == "0"
where(" LOWER(login) LIKE '#{pattern}' ")
elsif type == "1"
where(" LOWER(concat(lastname, firstname)) LIKE '#{pattern}' ")
elsif type == "3"
where(" LOWER(concat(lastname, firstname,login)) LIKE '#{pattern}' ")
else
where(" LOWER(mail) LIKE '#{pattern}' ")
end
end
}
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
type:"most_fields",
operator: "or",
fields: ['login', 'firstname','lastname']
}
},
sort:{
_score:{order:"desc"},
last_login_on: {order:"desc"}
},
highlight: {
pre_tags: ['<span class="c_red">'],
post_tags: ['</span>'],
fields: {
login: {},
firstname: {},
lastname: {}
}
}
}
)
end
# ======================================================================
# 查询用户未读过的记录
# 用户留言记录
def count_new_jour
count = self.new_jours.count
# count = self.journals_for_messages(:conditions => ["status=? and is_readed = ? " ,1, 0]).count
end
def blog
@blog = Blog.where("author_id = #{self.id}").all[0]
if @blog.nil?
#如果某个user的blog不存在那么就创建一条并且跳转
@blog = Blog.create(:name=>(User.find(self.id).realname.blank? ? User.find(self.id).login : User.find(self.id).realname ),
:description=>'',
:author_id=>self.id)
@blog.save
end
@blog
end
# 查询指派给我的缺陷记录
def count_new_issue_assign_to
self.issue_assigns
end
# 新消息统计
def count_new_message
if OnclickTime.where("user_id =?", User.current).first.nil?
message_new_time = OnclickTime.new
message_new_time.user_id = User.current.id
# 第一次初始化点击铃铛时间
message_new_time.onclick_time = User.current.last_login_on.nil? ? Time.now : User.current.last_login_on
message_new_time.save
end
course_count = CourseMessage.where("user_id =? and viewed =? and created_at >?", User.current.id, 0, User.current.onclick_time.onclick_time).count
forge_count = ForgeMessage.where("user_id =? and viewed =? and created_at >?", User.current.id, 0, User.current.onclick_time.onclick_time).count
user_feedback_count = UserFeedbackMessage.where("user_id =? and viewed =? and created_at >?", User.current.id, 0, User.current.onclick_time.onclick_time).count
user_memo_count = MemoMessage.where("user_id =? and viewed =? and created_at >?", User.current.id, 0, User.current.onclick_time.onclick_time).count
system_messages_count = SystemMessage.where("created_at >?", User.current.onclick_time.onclick_time).count
at_count = AtMessage.where("user_id =? and viewed =? and created_at >?", User.current.id, 0, User.current.onclick_time.onclick_time).count
org_count = OrgMessage.where("user_id=? and viewed =? and created_at >?", User.current.id,0, User.current.onclick_time.onclick_time).count
messages_count = course_count + forge_count + user_feedback_count + user_memo_count + system_messages_count + at_count + org_count
end
# 查询指派给我的缺陷记录
def issue_status_update
self.status_updates
end
# end
def extensions
self.user_extensions ||= UserExtensions.new
end
# User现在可以作为一个Container_type,而Attachment的Container方法会有一个Container.try(:project),
# 所以这里定义一个空方法,保证不报错
def project
end
def user_score_attr
self.user_score ||= UserScore.new
end
# ======================================================================
#选择项目成员时显示的用户信息文字
def userInfo
if self.realname.gsub(' ','') == "" || self.realname.nil?
info = self.nickname;
else
info=self.nickname + ' (' + self.realname + ')';
end
info
end
###添加留言 fq
def add_jour(user, notes, reference_user_id = 0, options = {})
if options.count == 0
self.journals_for_messages << JournalsForMessage.new(:user_id => user.id, :notes => notes, :reply_id => reference_user_id, :status => true, :is_readed => false)
else
jfm = self.journals_for_messages.build(options)
jfm.save
jfm
end
end
### fq
def join_in?(course)
joined = StudentsForCourse.where('student_id = ? and course_id = ?', self.id, course.id)
if joined.size > 0
true
else
false
end
end
def show_name
name = lastname + firstname
name.empty? || name.nil? ? login : name
end
## end
def get_at_show_name
name = show_name
(name != self.login) ? "#{name} #{self.login}" : name
end
#added by nie
def count_new_journal_reply
count = self.journal_reply.count
end
def set_mail_notification
##add byxianbo
thread=Thread.new do
self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
true
end
end
def update_hashed_password
# update hashed_password if password was set
if self.password && self.auth_source_id.blank?
salt_password(password)
end
end
alias :base_reload :reload
def reload(*args)
@name = nil
@projects_by_role = nil
@courses_by_role = nil
@membership_by_project_id = nil
base_reload(*args)
end
def mail=(arg)
write_attribute(:mail, arg.to_s.strip)
end
def identity_url=(url)
if url.blank?
write_attribute(:identity_url, '')
else
begin
write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
rescue OpenIdAuthentication::InvalidOpenId
# Invalid url, don't save
end
end
self.read_attribute(:identity_url)
end
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
# VALID_EMAIL_REGEX = /^[0-9a-zA-Z_-]+@[0-9a-zA-Z_-]+(\.[0-9a-zA-Z_-]+)+$/
# Returns the user that matches provided login and password, or nil
#登录,返回用户名与密码匹配的用户
def self.try_to_login(login, password)
login = login.to_s.lstrip.rstrip
password = password.to_s
# Make sure no one can sign in with an empty login or password
return nil if login.empty? || password.empty?
if (login =~ VALID_EMAIL_REGEX)
user = find_by_mail(login)
else
user = find_by_login(login)
end
if user
# user is already in local database
return nil if user.locked?
return nil unless user.check_password?(password)
else
# user is not yet registered, try to authenticate with available sources
attrs = AuthSource.authenticate(login, password)
if attrs
user = new(attrs)
user.login = login
user.language = Setting.default_language
if user.save
user.reload
logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
end
end
end
if user && !user.new_record?
last_login_on = user.last_login_on.nil? ? '' : user.last_login_on.to_s
user.update_column(:last_login_on, Time.now)
end
[user, last_login_on]
rescue => text
raise text
end
def self.try_to_autologin(key)
user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
if user
user.update_column(:last_login_on, Time.now)
user
end
end
def self.name_formatter(formatter = nil)
USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
end
# Returns an array of fields names than can be used to make an order statement for users
# according to how user names are displayed
# Examples:
#
# User.fields_for_order_statement => ['users.login', 'users.id']
# User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
def self.fields_for_order_statement(table=nil)
table ||= table_name
name_formatter[:order].map {|field| "#{table}.#{field}"}
end
# Return user's full name for display
def realname(formatter = nil)
f = self.class.name_formatter(formatter)
if formatter
eval('"' + f[:string] + '"')
else
@name ||= eval('"' + f[:string] + '"')
end
end
def nickname(formatter = nil)
login.nil? || (login && login.empty?) ? "AnonymousUser" : login
end
def name(formatter = nil)
login
end
def active?
self.status == STATUS_ACTIVE
end
def registered?
self.status == STATUS_REGISTERED
end
def locked?
self.status == STATUS_LOCKED
end
def activate
self.status = STATUS_ACTIVE
end
def register
self.status = STATUS_REGISTERED
end
def lock
self.status = STATUS_LOCKED
end
def activate!
update_attribute(:status, STATUS_ACTIVE)
end
def register!
update_attribute(:status, STATUS_REGISTERED)
end
def lock!
update_attribute(:status, STATUS_LOCKED)
end
# Returns true if +clear_password+ is the correct user's password, otherwise false
def check_password?(clear_password)
if auth_source_id.present?
auth_source.authenticate(self.login, clear_password)
else
User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
end
end
def check_password1?(clear_password)
clear_password == hashed_password
end
# Generates a random salt and computes hashed_password for +clear_password+
# The hashed password is stored in the following form: SHA1(salt + SHA1(password))
def salt_password(clear_password)
self.salt = User.generate_salt
self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
end
# Does the backend storage allow this user to change their password?
def change_password_allowed?
return true if auth_source.nil?
return auth_source.allow_password_changes?
end
# Generate and set a random password. Useful for automated user creation
# Based on Token#generate_token_value
#
def random_password
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
password = ''
40.times { |i| password << chars[rand(chars.size-1)] }
self.password = password
self.password_confirmation = password
self
end
def pref
self.preference ||= UserPreference.new(:user => self)
end
def time_zone
@time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
end
def wants_comments_in_reverse_order?
self.pref[:comments_sorting] == 'desc'
end
def wants_notificationcomments_in_reverse_order?
self.pref[:notificationcomments_sorting] == 'desc'
end
# Return user's RSS key (a 40 chars long string), used to access feeds
def rss_key
if rss_token.nil?
create_rss_token(:action => 'feeds')
end
rss_token.value
end
# Return user's API key (a 40 chars long string), used to access the API
def api_key
if api_token.nil?
create_api_token(:action => 'api')
end
api_token.value
end
# Return an array of project ids for which the user has explicitly turned mail notifications on
def notified_projects_ids
@notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
end
def notified_project_ids=(ids)
Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
@notified_projects_ids = nil
notified_projects_ids
end
def valid_notification_options
self.class.valid_notification_options(self)
end
# Only users that belong to more than 1 project can select projects for which they are notified
def self.valid_notification_options(user=nil)
# Note that @user.membership.size would fail since AR ignores
# :include association option when doing a count
if user.nil? || user.memberships.length < 1
MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
else
MAIL_NOTIFICATION_OPTIONS
end
end
# Find a user account by matching the exact login and then a case-insensitive
# version. Exact matches will be given priority.
#通过用户名查找相应的用户,若没有匹配到,则不区分大小写进行查询
#修改:不再匹配不区分大小写情况 -zjc
def self.find_by_login(login)
if login.present?
login = login.to_s
# First look for an exact match
user = where(:login => login).all.detect {|u| u.login == login}
#unless user
# # Fail over to case-insensitive if none was found
# user = where("LOWER(login) = ?", login.downcase).first
#end
user
end
end
def self.find_by_rss_key(key)
Token.find_active_user('feeds', key)
end
def self.find_by_api_key(key)
Token.find_active_user('api', key)
end
# Makes find_by_mail case-insensitive
def self.find_by_mail(mail)
where("LOWER(mail) = ?", mail.to_s.downcase).first
end
# Returns true if the default admin account can no longer be used
def self.default_admin_account_changed?
!User.active.find_by_login("admin").try(:check_password?, "admin")
end
def to_s
name
end
CSS_CLASS_BY_STATUS = {
STATUS_ANONYMOUS => 'anon',
STATUS_ACTIVE => 'active',
STATUS_REGISTERED => 'registered',
STATUS_LOCKED => 'locked'
}
def css_classes
"user #{CSS_CLASS_BY_STATUS[status]}"
end
# Returns the current day according to user's time zone
def today
if time_zone.nil?
Date.today
else
Time.now.in_time_zone(time_zone).to_date
end
end
# Returns the day of +time+ according to user's time zone
def time_to_date(time)
if time_zone.nil?
time.to_date
else
time.in_time_zone(time_zone).to_date
end
end
def logged?
true
end
def anonymous?
!logged?
end
# Returns user's membership for the given project
# or nil if the user is not a member of project
def membership(project)
project_id = project.is_a?(Project) ? project.id : project
@membership_by_project_id ||= Hash.new {|h, project_id|
h[project_id] = memberships.where(:project_id => project_id).first
}
@membership_by_project_id[project_id]
end
def coursemembership(course)
course_id = course.is_a?(Course) ? course.id : course
@membership_by_course_id ||= Hash.new {|h, course_id|
h[course_id] = coursememberships.where(:course_id => course_id).first
}
@membership_by_course_id[course_id]
end
# Return user's roles for project
def roles_for_project(project)
roles = []
# No role on archived projects
return roles if project.nil? || project.archived?
if logged?
# Find project membership
membership = membership(project)
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# 用户课程权限判断
def roles_for_course(course)
roles = []
# No role on archived courses
return roles if course.nil? || course.archived?
if logged?
# Find course membership
membership = coursemembership(course)
if membership
roles = membership.roles
else
@role_non_member ||= Role.non_member
roles << @role_non_member
end
else
@role_anonymous ||= Role.anonymous
roles << @role_anonymous
end
roles
end
# Return true if the user is a member of project
def member_of?(project)
projects.to_a.include?(project)
end
def member_of_course?(course)
courses.to_a.include?(course)
end
def member_of_org?(org)
if !self.logged?
return false
end
OrgMember.where("user_id =? and organization_id =?", self.id, org.id).count > 0
end
def admin_of_org?(org)
if self.admin?
return true
end
if OrgMember.where("user_id =? and organization_id =?", self.id, org.id).count == 0
return false
end
role = OrgMember.where("user_id =? and organization_id =?", self.id, org.id)[0].roles[0]
unless role.nil?
role.name == 'orgManager' ? true : false
else
false
end
end
def member_of_course_group?(course_group)
course_groups.to_a.include?(course_group)
end
# Returns a hash of user's projects grouped by roles
def projects_by_role
return @projects_by_role if @projects_by_role
@projects_by_role = Hash.new([])
memberships.each do |membership|
if membership.project
membership.roles.each do |role|
@projects_by_role[role] = [] unless @projects_by_role.key?(role)
@projects_by_role[role] << membership.project
end
end
end
@projects_by_role.each do |role, projects|
projects.uniq!
end
@projects_by_role
end
# 课程的角色权限
def courses_by_role
return @courses_by_role if @courses_by_role
@courses_by_role = Hash.new([])
coursememberships.each do |membership|
if membership.course
membership.roles.each do |role|
@courses_by_role[role] = [] unless @courses_by_role.key?(role)
@courses_by_role[role] << membership.course
end
end
end
@courses_by_role.each do |role, courses|
courses.uniq!
end
@courses_by_role
end
# Returns true if user is arg or belongs to arg
def is_or_belongs_to?(arg)
if arg.is_a?(User)
self == arg
elsif arg.is_a?(Group)
arg.users.include?(self)
else
false
end
end
# Return true if the user is allowed to do the specified action on a specific context
# Action can be:
# * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
# * a permission Symbol (eg. :edit_project)
# Context can be:
# * a project : returns true if user is allowed to do the specified action on this project
# * an array of projects : returns true if user is allowed on every project
# * nil with options[:global] set : check if user has at least one role allowed for this action,
# or falls back to Non Member / Anonymous permissions depending if the user is logged
def allowed_to?(action, context, options={}, &block)
if Project === context
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_project(context)
return false unless roles
roles.any? {|role|
(context.is_public? || role.member?) &&
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
#添加课程相关的权限判断
elsif Course === context
return false unless context.allows_to?(action)
# Admin users are authorized for anything else
return true if admin?
roles = roles_for_course(context)
return false unless roles
roles.any? {|role|
(context.is_public? || role.member?) &&
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
elsif context && context.is_a?(Array)
if context.empty?
false
else
# Authorize if user is authorized on every element of the array
context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
end
elsif options[:global]
# Admin users are always authorized
return true if admin?
# authorize if user has at least one role that has this permission
roles = memberships.collect {|m| m.roles}.flatten.uniq
if roles.count == 0
roles = coursememberships.collect {|m| m.roles}.flatten.uniq
end
roles << (self.logged? ? Role.non_member : Role.anonymous)
roles.any? {|role|
role.allowed_to?(action) &&
(block_given? ? yield(role, self) : true)
}
else
if admin?
return true
end
#无项目时 查看Non memberid为1角色是否有权限执行action
Role.find('1').allowed_to?(action)
# false
end
end
# Is the user allowed to do the specified action on any project?
# See allowed_to? for the actions and valid options.
def allowed_to_globally?(action, options, &block)
allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
end
# Returns true if the user is allowed to delete his own account
def own_account_deletable?
Setting.unsubscribe? &&
(!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
end
safe_attributes 'login',
'firstname',
'lastname',
'mail',
'mail_notification',
'language',
'custom_field_values',
'custom_fields',
'identity_url'
safe_attributes 'status',
'auth_source_id',
:if => lambda {|user, current_user| current_user.admin?}
safe_attributes 'group_ids',
:if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
# Utility method to help check if a user should be notified about an
# event.
#
# TODO: only supports Issue events currently
def notify_about?(object)
if mail_notification == 'all'
true
elsif mail_notification.blank? || mail_notification == 'none'
false
else
case object
when Issue
case mail_notification
when 'selected', 'only_my_events'
# user receives notifications for created/assigned issues on unselected projects
object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
when 'only_assigned'
is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
when 'only_owner'
object.author == self
end
when News
# always send to project members except when mail_notification is set to 'none'
true
#判定用户是否接受留言提醒邮件
when JournalsForMessage
##如果是直接留言并且留言对象是Project并且Project类型是课程课程留言
if !object.at_user && object.jour.class.to_s.to_sym == :Project && object.jour.project_type == 1
#根据用户设置邮件接收模式判定当前用户是否接受邮件提醒
is_notified_project object.jour
end
end
end
end
#用户是否接收project的消息提醒
def is_notified_project arg
if arg.is_a?(Project)
case mail_notification
when 'selected'
notified_projects_ids.include?(arg.id)
when 'only_my_events'
projects.include?(arg)
when 'only_assigned'
false
when 'only_owner'
course = Course.find_by_extra(arg.identifier)
course.teacher == self
end
#勾选的项目或用户的项目 TODO需改
#notified_projects_ids.include?(arg) || projects.include?(arg)
else
false
end
end
def self.current=(user)
Thread.current[:current_user] = user
end
def self.current
Thread.current[:current_user] ||= User.anonymous
end
# Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
# one anonymous user per database.
def self.anonymous
anonymous_user = AnonymousUser.first
if anonymous_user.nil?
anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
end
anonymous_user
end
# refactor User model find function,
# return anonymous user when can not find user id = user_id
def self.find (*args, &block)
begin
super
rescue
self.anonymous
end
# super
end
# Salts all existing unsalted passwords
# It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
# This method is used in the SaltPasswords migration and is to be kept as is
def self.salt_unsalted_passwords!
transaction do
User.where("salt IS NULL OR salt = ''").find_each do |user|
next if user.hashed_password.blank?
salt = User.generate_salt
hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
end
end
end
protected
def validate_password_length
# Password length validation based on setting
if !password.nil? && password.size < Setting.password_min_length.to_i
errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
end
end
private
def act_as_activity
self.acts << Activity.new(:user_id => self.id)
end
#用户动态公共表记录
def act_as_principal_activity
self.principal_acts << PrincipalActivity.new(:user_id => self.id,:principal_id => self.id)
end
# 注册用户的时候消息默认点击时间为用户创建时间
def add_onclick_time
if OnclickTime.where("user_id =?" , self.id).first.nil?
OnclickTime.create(:user_id => self.id, :onclick_time => self.created_on)
end
end
# 更新邮箱的同事更新invite_lists表中的邮箱信息
def update_invite_list
invite_lists = InviteList.where("user_id =?",self.id).all
unless invite_lists.blank?
invite_lists.each do |invite_list|
invite_list.update_attribute(:mail, self.mail)
end
end
end
# Removes references that are not handled by associations
# Things that are not deleted are reassociated with the anonymous user
def remove_references_before_destroy
return if self.id.nil?
substitute = User.anonymous
Attachment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Comment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Notificationcomment.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
Issue.update_all 'assigned_to_id = NULL', ['assigned_to_id = ?', id]
Journal.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
JournalDetail.update_all ['old_value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]
JournalDetail.update_all ['value = ?', substitute.id.to_s], ["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]
Message.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
News.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
# Remove private queries and keep public ones
::Query.delete_all ['user_id = ? AND is_public = ?', id, false]
::Query.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
TimeEntry.update_all ['user_id = ?', substitute.id], ['user_id = ?', id]
Token.delete_all ['user_id = ?', id]
Watcher.delete_all ['user_id = ?', id]
WikiContent.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
WikiContent::Version.update_all ['author_id = ?', substitute.id], ['author_id = ?', id]
end
# Return password digest
def self.hash_password(clear_password)
Digest::SHA1.hexdigest(clear_password || "")
end
# Returns a 128bits random salt as a hex string (32 chars long)
def self.generate_salt
Redmine::Utils.random_hex(16)
end
private
def sync_gitlab_user
user = self
g = Gitlab.client
u = g.get("/users?search=#{user.mail}").first
unless u
u = g.create_user(user.mail, user.password, name: user.show_name, username: user.login, confirm: "true")
self.gid = u.id
puts "create user #{user.login}"
end
end
def create_user_ealasticsearch_index
if self.id != 2 && self.id != 4
self.__elasticsearch__.index_document if Rails.env.production?
end
end
def update_user_ealasticsearch_index
if self.id != 2 && self.id != 4
self.__elasticsearch__.update_document if Rails.env.production?
end
end
def delete_user_ealasticsearch_index
if self.id != 2 && self.id != 4
self.__elasticsearch__.delete_document if Rails.env.production?
end
end
end
class AnonymousUser < User
validate :validate_anonymous_uniqueness, :on => :create
def validate_anonymous_uniqueness
# There should be only one AnonymousUser in the database
errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
end
def available_custom_fields
[]
end
# Overrides a few properties
def logged?; false end
def admin; false end
def name(*args); I18n.t(:label_user_anonymous) end
def mail; nil end
def time_zone; nil end
def rss_key; nil end
def pref
UserPreference.new(:user => self)
end
# def member_of?(project)
# false
# end
# Anonymous user can not be destroyed
def destroy
false
end
end
# Delete the previous articles index in Elasticsearch
# User.__elasticsearch__.client.indices.delete index: User.index_name rescue nil
#
# # Create the new index with the new mapping
# User.__elasticsearch__.client.indices.create \
# index: User.index_name,
# body: { settings: User.settings.to_hash, mappings: User.mappings.to_hash }
# Index all article records from the DB to Elasticsearch
# 匿名用户 角色 和 管理员角色不能被索引
#User.where('id not in (2,4)').import :force=>true