2013-04-17 17:08:21 +10:00
class Group < ActiveRecord :: Base
2014-04-28 10:31:51 +02:00
include HasCustomFields
2014-04-25 15:14:05 +02:00
2014-08-31 22:10:38 +02:00
has_many :category_groups , dependent : :destroy
2013-05-09 11:33:56 +10:00
has_many :group_users , dependent : :destroy
2015-12-01 16:52:43 +11:00
has_many :group_mentions , dependent : :destroy
2013-04-29 16:33:24 +10:00
2015-12-23 11:09:17 +11:00
has_many :group_archived_messages , dependent : :destroy
2013-04-29 16:33:24 +10:00
has_many :categories , through : :category_groups
has_many :users , through : :group_users
2015-12-07 12:39:28 +01:00
before_save :downcase_incoming_email
2013-05-09 11:33:56 +10:00
after_save :destroy_deletions
2015-01-23 18:25:43 +01:00
after_save :automatic_group_membership
2015-04-10 12:17:28 +10:00
after_save :update_primary_group
after_save :update_title
2013-05-09 11:33:56 +10:00
2015-09-28 16:43:38 +10:00
after_save :expire_cache
after_destroy :expire_cache
def expire_cache
ApplicationSerializer . expire_cache_fragment! ( " group_names " )
end
2013-05-09 17:37:34 +10:00
validate :name_format_validator
2014-12-17 01:07:15 +13:00
validates_uniqueness_of :name , case_sensitive : false
2015-11-27 11:05:16 +05:30
validate :automatic_membership_email_domains_format_validator
2015-12-07 12:39:28 +01:00
validate :incoming_email_validator
2013-05-09 17:37:34 +10:00
2013-05-06 14:49:56 +10:00
AUTO_GROUPS = {
2013-07-14 11:24:16 +10:00
:everyone = > 0 ,
2013-05-06 14:49:56 +10:00
:admins = > 1 ,
:moderators = > 2 ,
:staff = > 3 ,
2014-06-17 18:13:07 +10:00
:trust_level_0 = > 10 ,
2013-05-06 14:49:56 +10:00
:trust_level_1 = > 11 ,
:trust_level_2 = > 12 ,
:trust_level_3 = > 13 ,
2014-07-10 12:17:13 +10:00
:trust_level_4 = > 14
2013-05-06 14:49:56 +10:00
}
2014-07-10 12:17:13 +10:00
AUTO_GROUP_IDS = Hash [ * AUTO_GROUPS . to_a . flatten . reverse ]
2016-03-23 16:20:24 -04:00
STAFF_GROUPS = [ :admins , :moderators , :staff ]
2014-06-17 10:46:30 +10:00
2014-01-08 03:47:01 +11:00
ALIAS_LEVELS = {
:nobody = > 0 ,
:only_admins = > 1 ,
:mods_and_admins = > 2 ,
:members_mods_and_admins = > 3 ,
:everyone = > 99
}
2014-08-18 14:40:54 +10:00
validates :alias_level , inclusion : { in : ALIAS_LEVELS . values }
2014-01-08 03:47:01 +11:00
2015-11-30 17:03:47 +11:00
scope :mentionable , lambda { | user |
levels = [ ALIAS_LEVELS [ :everyone ] ]
if user && user . admin?
levels = [ ALIAS_LEVELS [ :everyone ] ,
ALIAS_LEVELS [ :only_admins ] ,
ALIAS_LEVELS [ :mods_and_admins ] ,
ALIAS_LEVELS [ :members_mods_and_admins ] ]
elsif user && user . moderator?
levels = [ ALIAS_LEVELS [ :everyone ] ,
ALIAS_LEVELS [ :mods_and_admins ] ,
ALIAS_LEVELS [ :members_mods_and_admins ] ]
end
where ( " alias_level in (:levels) OR
(
alias_level = #{ALIAS_LEVELS[:members_mods_and_admins]} AND id in (
SELECT group_id FROM group_users WHERE user_id = :user_id )
) " , levels: levels, user_id: user && user.id )
}
2015-12-07 12:39:28 +01:00
def downcase_incoming_email
self . incoming_email = ( incoming_email || " " ) . strip . downcase . presence
end
def incoming_email_validator
return if self . automatic || self . incoming_email . blank?
2016-02-24 19:47:58 +01:00
incoming_email . split ( " | " ) . each do | email |
2016-03-08 20:52:04 +01:00
if ! Email . is_valid? ( email )
self . errors . add ( :base , I18n . t ( 'groups.errors.invalid_incoming_email' , email : email ) )
elsif group = Group . where . not ( id : self . id ) . find_by_email ( email )
self . errors . add ( :base , I18n . t ( 'groups.errors.email_already_used_in_group' , email : email , group_name : group . name ) )
elsif category = Category . find_by_email ( email )
self . errors . add ( :base , I18n . t ( 'groups.errors.email_already_used_in_category' , email : email , category_name : category . name ) )
2016-02-24 19:47:58 +01:00
end
2015-12-07 12:39:28 +01:00
end
end
2014-02-12 14:00:45 -05:00
def posts_for ( guardian , before_post_id = nil )
2015-12-07 23:19:33 +01:00
user_ids = group_users . map { | gu | gu . user_id }
result = Post . includes ( :user , :topic , topic : :category )
. references ( :posts , :topics , :category )
. where ( user_id : user_ids )
2014-03-13 14:06:22 -04:00
. where ( 'topics.archetype <> ?' , Archetype . private_message )
. where ( post_type : Post . types [ :regular ] )
2014-02-07 10:44:03 -05:00
2015-02-12 11:52:59 -05:00
result = guardian . filter_allowed_categories ( result )
2014-02-07 10:44:03 -05:00
result = result . where ( 'posts.id < ?' , before_post_id ) if before_post_id
2015-12-01 16:52:43 +11:00
result . order ( 'posts.created_at desc' )
end
2015-12-07 23:19:33 +01:00
def messages_for ( guardian , before_post_id = nil )
result = Post . includes ( :user , :topic , topic : :category )
. references ( :posts , :topics , :category )
. where ( 'topics.archetype = ?' , Archetype . private_message )
. where ( post_type : Post . types [ :regular ] )
. where ( 'topics.id IN (SELECT topic_id FROM topic_allowed_groups WHERE group_id = ?)' , self . id )
result = guardian . filter_allowed_categories ( result )
result = result . where ( 'posts.id < ?' , before_post_id ) if before_post_id
result . order ( 'posts.created_at desc' )
end
2015-12-01 16:52:43 +11:00
def mentioned_posts_for ( guardian , before_post_id = nil )
result = Post . joins ( :group_mentions )
2015-12-07 23:19:33 +01:00
. includes ( :user , :topic , topic : :category )
2015-12-01 16:52:43 +11:00
. references ( :posts , :topics , :category )
. where ( 'topics.archetype <> ?' , Archetype . private_message )
. where ( post_type : Post . types [ :regular ] )
2015-12-02 16:16:19 +11:00
. where ( 'group_mentions.group_id = ?' , self . id )
2015-12-01 16:52:43 +11:00
result = guardian . filter_allowed_categories ( result )
result = result . where ( 'posts.id < ?' , before_post_id ) if before_post_id
2014-02-12 14:00:45 -05:00
result . order ( 'posts.created_at desc' )
2014-02-07 10:44:03 -05:00
end
2013-05-06 14:49:56 +10:00
def self . trust_group_ids
( 10 .. 19 ) . to_a
end
def self . refresh_automatic_group! ( name )
2015-12-07 23:19:33 +01:00
return unless id = AUTO_GROUPS [ name ]
2013-05-06 14:49:56 +10:00
2013-05-09 17:37:34 +10:00
unless group = self . lookup_group ( name )
group = Group . new ( name : name . to_s , automatic : true )
2013-05-06 14:49:56 +10:00
group . id = id
group . save!
end
2013-05-08 15:20:38 +10:00
group . name = I18n . t ( " groups.default_names. #{ name } " )
2013-05-06 14:49:56 +10:00
2013-06-23 14:44:16 +10:00
# don't allow shoddy localization to break this
validator = UsernameValidator . new ( group . name )
unless validator . valid_format?
group . name = name
end
2014-09-05 00:15:37 +02:00
# the everyone group is special, it can include non-users so there is no
# way to have the membership in a table
if name == :everyone
group . save!
return group
end
2014-08-11 13:30:51 -04:00
# Remove people from groups they don't belong in.
#
# BEWARE: any of these subqueries could match ALL the user records,
# so they can't be used in IN clauses.
remove_user_subquery = case name
when :admins
" SELECT u.id FROM users u WHERE NOT u.admin "
when :moderators
" SELECT u.id FROM users u WHERE NOT u.moderator "
when :staff
" SELECT u.id FROM users u WHERE NOT u.admin AND NOT u.moderator "
when :trust_level_0 , :trust_level_1 , :trust_level_2 , :trust_level_3 , :trust_level_4
" SELECT u.id FROM users u WHERE u.trust_level < #{ id - 10 } "
end
remove_ids = exec_sql ( " SELECT gu.id id
FROM group_users gu ,
( #{remove_user_subquery}) u
WHERE gu . group_id = #{group.id}
AND gu . user_id = u . id " ).map {|x| x['id']}
if remove_ids . length > 0
remove_ids . each_slice ( 100 ) do | ids |
GroupUser . where ( id : ids ) . delete_all
end
end
# Add people to groups
2013-05-06 14:49:56 +10:00
real_ids = case name
when :admins
2013-05-29 21:46:03 +02:00
" SELECT u.id FROM users u WHERE u.admin "
2013-05-06 14:49:56 +10:00
when :moderators
2013-05-29 21:46:03 +02:00
" SELECT u.id FROM users u WHERE u.moderator "
2013-05-06 14:49:56 +10:00
when :staff
2013-05-29 21:46:03 +02:00
" SELECT u.id FROM users u WHERE u.moderator OR u.admin "
2014-07-10 12:17:13 +10:00
when :trust_level_1 , :trust_level_2 , :trust_level_3 , :trust_level_4
2014-06-17 10:46:30 +10:00
" SELECT u.id FROM users u WHERE u.trust_level >= #{ id - 10 } "
2014-06-17 18:13:07 +10:00
when :trust_level_0
2014-06-17 10:46:30 +10:00
" SELECT u.id FROM users u "
2013-05-06 14:49:56 +10:00
end
2013-05-17 09:03:30 +10:00
missing_users = GroupUser
. joins ( " RIGHT JOIN ( #{ real_ids } ) X ON X.id = user_id AND group_id = #{ group . id } " )
2013-05-06 14:49:56 +10:00
. where ( " user_id IS NULL " )
. select ( " X.id " )
missing_users . each do | u |
group . group_users . build ( user_id : u . id )
end
group . save!
2013-05-08 15:20:38 +10:00
# we want to ensure consistency
Group . reset_counters ( group . id , :group_users )
2013-05-09 17:37:34 +10:00
group
2013-05-06 14:49:56 +10:00
end
2016-04-04 17:03:18 +02:00
def self . ensure_consistency!
2016-04-04 23:41:49 +02:00
reset_all_counters!
refresh_automatic_groups!
2016-04-04 17:03:18 +02:00
end
2016-04-04 23:41:49 +02:00
def self . reset_all_counters!
2016-04-04 17:03:18 +02:00
Group . pluck ( :id ) . each do | group_id |
Group . reset_counters ( group_id , :group_users )
end
end
2013-05-06 14:49:56 +10:00
def self . refresh_automatic_groups! ( * args )
2013-05-08 15:20:38 +10:00
if args . length == 0
2013-06-10 10:38:10 +05:30
args = AUTO_GROUPS . keys
2013-05-08 15:20:38 +10:00
end
2013-05-06 14:49:56 +10:00
args . each do | group |
refresh_automatic_group! ( group )
end
end
2013-11-18 12:53:14 +11:00
def self . ensure_automatic_groups!
2015-04-18 21:53:53 +10:00
AUTO_GROUPS . each_key do | name |
2013-11-18 12:53:14 +11:00
refresh_automatic_group! ( name ) unless lookup_group ( name )
end
end
2013-05-06 14:49:56 +10:00
def self . [] ( name )
2013-05-17 09:03:30 +10:00
lookup_group ( name ) || refresh_automatic_group! ( name )
2013-05-09 17:37:34 +10:00
end
2013-05-06 14:49:56 +10:00
2015-11-30 17:03:47 +11:00
def self . search_group ( name )
2015-12-02 15:49:43 +11:00
Group . where ( visible : true ) . where ( " name ILIKE :term_like " , term_like : " #{ name } % " )
2013-12-23 15:46:00 +01:00
end
2013-05-09 17:37:34 +10:00
def self . lookup_group ( name )
2013-11-22 19:18:45 +01:00
if id = AUTO_GROUPS [ name ]
2014-05-06 14:41:59 +01:00
Group . find_by ( id : id )
2013-07-23 10:10:36 +10:00
else
2014-05-06 14:41:59 +01:00
unless group = Group . find_by ( name : name )
2013-11-22 19:18:45 +01:00
raise ArgumentError , " unknown group "
2013-07-23 10:10:36 +10:00
end
group
end
2013-05-06 14:49:56 +10:00
end
2014-05-09 18:22:15 +10:00
def self . lookup_group_ids ( opts )
if group_ids = opts [ :group_ids ]
group_ids = group_ids . split ( " , " ) . map ( & :to_i )
group_ids = Group . where ( id : group_ids ) . pluck ( :id )
end
group_ids || = [ ]
if group_names = opts [ :group_names ]
group_names = group_names . split ( " , " )
if group_names . present?
group_ids += Group . where ( name : group_names ) . pluck ( :id )
end
end
group_ids
end
2014-06-17 10:46:30 +10:00
def self . desired_trust_level_groups ( trust_level )
trust_group_ids . keep_if do | id |
2014-06-17 18:13:07 +10:00
id == AUTO_GROUPS [ :trust_level_0 ] || ( trust_level + 10 ) > = id
2014-06-17 10:46:30 +10:00
end
end
2013-05-06 14:49:56 +10:00
def self . user_trust_level_change! ( user_id , trust_level )
2014-06-17 10:46:30 +10:00
desired = desired_trust_level_groups ( trust_level )
undesired = trust_group_ids - desired
2013-05-06 14:49:56 +10:00
2014-06-17 10:46:30 +10:00
GroupUser . where ( group_id : undesired , user_id : user_id ) . delete_all
2013-05-06 14:49:56 +10:00
2014-06-17 10:46:30 +10:00
desired . each do | id |
if group = find_by ( id : id )
unless GroupUser . where ( group_id : id , user_id : user_id ) . exists?
group . group_users . create! ( user_id : user_id )
end
else
name = AUTO_GROUP_IDS [ trust_level ]
refresh_automatic_group! ( name )
end
2013-05-06 14:49:56 +10:00
end
end
2013-04-17 17:08:21 +10:00
def self . builtin
Enum . new ( :moderators , :admins , :trust_level_1 , :trust_level_2 )
end
2013-04-29 16:33:24 +10:00
2013-05-09 11:33:56 +10:00
def usernames = ( val )
current = usernames . split ( " , " )
expected = val . split ( " , " )
additions = expected - current
deletions = current - expected
map = Hash [ * User . where ( username : additions + deletions )
. select ( 'id,username' )
. map { | u | [ u . username , u . id ] } . flatten ]
deletions = Set . new ( deletions . map { | d | map [ d ] } )
@deletions = [ ]
2014-03-27 00:00:23 -07:00
group_users . each do | gu |
2013-05-09 11:33:56 +10:00
@deletions << gu if deletions . include? ( gu . user_id )
end
additions . each do | a |
group_users . build ( user_id : map [ a ] )
end
end
def usernames
2013-05-17 15:11:37 -04:00
users . pluck ( :username ) . join ( " , " )
2013-05-09 11:33:56 +10:00
end
2013-04-29 16:33:24 +10:00
def add ( user )
self . users . push ( user )
end
2014-08-18 13:04:08 +02:00
2014-11-20 09:29:56 -08:00
def remove ( user )
self . group_users . where ( user : user ) . each ( & :destroy )
2015-02-09 16:03:09 +11:00
user . update_columns ( primary_group_id : nil ) if user . primary_group_id == self . id
2014-11-20 09:29:56 -08:00
end
2015-11-10 00:52:04 +11:00
def add_owner ( user )
self . group_users . create ( user_id : user . id , owner : true )
2015-01-08 15:35:52 -08:00
end
2015-12-07 17:01:08 +01:00
def self . find_by_email ( email )
2016-03-08 20:52:04 +01:00
self . where ( " string_to_array(incoming_email, '|') @> ARRAY[?] " , Email . downcase ( email ) ) . first
2015-12-07 17:01:08 +01:00
end
2016-02-18 14:03:00 -05:00
def bulk_add ( user_ids )
if user_ids . present?
Group . exec_sql ( " INSERT INTO group_users
( group_id , user_id , created_at , updated_at )
SELECT #{self.id},
u . id ,
CURRENT_TIMESTAMP ,
CURRENT_TIMESTAMP
FROM users AS u
WHERE u . id IN ( #{user_ids.join(', ')})
AND NOT EXISTS ( SELECT 1 FROM group_users AS gu
WHERE gu . user_id = u . id AND
gu . group_id = #{self.id})")
if self . primary_group?
User . where ( id : user_ids ) . update_all ( primary_group_id : self . id )
end
if self . title . present?
User . where ( id : user_ids ) . update_all ( title : self . title )
end
end
true
end
2016-03-02 23:48:17 +05:30
def mentionable? ( user , group_id )
Group . mentionable ( user ) . where ( id : group_id ) . exists?
end
2016-03-23 16:20:24 -04:00
def staff?
STAFF_GROUPS . include? ( self . name . to_sym )
end
2013-05-09 11:33:56 +10:00
protected
2014-08-18 13:04:08 +02:00
def name_format_validator
UsernameValidator . perform_validation ( self , 'name' )
end
2013-05-09 17:37:34 +10:00
2015-11-27 11:05:16 +05:30
def automatic_membership_email_domains_format_validator
return if self . automatic_membership_email_domains . blank?
domains = self . automatic_membership_email_domains . split ( " | " )
domains . each do | domain |
domain . sub! ( / ^https?: \/ \/ / , '' )
domain . sub! ( / \/ .*$ / , '' )
2016-04-08 12:11:58 -04:00
self . errors . add :base , ( I18n . t ( 'groups.errors.invalid_domain' , domain : domain ) ) unless domain =~ / \ A[a-z0-9]+([ \ - \ .]{1}[a-z0-9]+)* \ .[a-z]{2,24}(:[0-9]{1,5})?( \/ .*)? \ Z /i
2015-11-27 11:05:16 +05:30
end
self . automatic_membership_email_domains = domains . join ( " | " )
end
2014-08-18 13:04:08 +02:00
# hack around AR
def destroy_deletions
if @deletions
@deletions . each do | gu |
gu . destroy
User . where ( 'id = ? AND primary_group_id = ?' , gu . user_id , gu . group_id ) . update_all 'primary_group_id = NULL'
end
2013-05-09 11:33:56 +10:00
end
2014-08-18 13:04:08 +02:00
@deletions = nil
2013-05-09 11:33:56 +10:00
end
2015-01-23 18:25:43 +01:00
def automatic_group_membership
if self . automatic_membership_retroactive
Jobs . enqueue ( :automatic_group_membership , group_id : self . id )
end
end
2015-04-10 12:17:28 +10:00
def update_title
return if new_record? && ! self . title . present?
if self . title_changed?
sql = <<SQL
UPDATE users SET title = :title
WHERE ( title = :title_was OR
title = '' OR
title IS NULL ) AND
COALESCE ( title , '' ) < > COALESCE ( :title , '' ) AND
id IN (
SELECT user_id
FROM group_users
WHERE group_id = :id
)
SQL
self . class . exec_sql ( sql ,
title : title ,
title_was : title_was ,
id : id
)
end
end
def update_primary_group
return if new_record? && ! self . primary_group?
if self . primary_group_changed?
sql = <<SQL
UPDATE users
/ *set* /
/ *where* /
SQL
builder = SqlBuilder . new ( sql )
builder . where ( "
id IN (
SELECT user_id
FROM group_users
WHERE group_id = :id
) " , id: id)
if primary_group
builder . set ( " primary_group_id = :id " )
else
builder . set ( " primary_group_id = NULL " )
builder . where ( " primary_group_id = :id " )
end
builder . exec
end
end
2013-04-17 17:08:21 +10:00
end
2013-05-24 12:48:32 +10:00
# == Schema Information
#
# Table name: groups
#
2015-02-04 15:09:00 +11:00
# id :integer not null, primary key
2016-02-23 10:33:53 +11:00
# name :string not null
2015-02-04 15:09:00 +11:00
# created_at :datetime not null
# updated_at :datetime not null
# automatic :boolean default(FALSE), not null
# user_count :integer default(0), not null
# alias_level :integer default(0)
# visible :boolean default(TRUE), not null
# automatic_membership_email_domains :text
# automatic_membership_retroactive :boolean default(FALSE)
2015-09-18 10:41:10 +10:00
# primary_group :boolean default(FALSE), not null
2016-02-23 10:33:53 +11:00
# title :string
2015-09-01 16:52:05 -04:00
# grant_trust_level :integer
2016-01-11 17:30:56 +11:00
# incoming_email :string
# has_messages :boolean default(FALSE), not null
2013-05-24 12:48:32 +10:00
#
# Indexes
#
2016-01-11 17:30:56 +11:00
# index_groups_on_incoming_email (incoming_email) UNIQUE
# index_groups_on_name (name) UNIQUE
2013-05-24 12:48:32 +10:00
#