2019-05-02 18:17:27 -04:00
# frozen_string_literal: true
2013-02-05 14:16:51 -05:00
class User < ActiveRecord :: Base
2017-08-15 11:46:57 -04:00
include Searchable
2013-06-06 18:07:59 -04:00
include Roleable
2014-04-28 04:31:51 -04:00
include HasCustomFields
2018-02-20 01:44:51 -05:00
include SecondFactorManager
2018-10-05 04:53:59 -04:00
include HasDestroyedWebHook
2023-10-27 05:27:04 -04:00
include HasDeprecatedColumns
2013-06-06 18:07:59 -04:00
2020-05-23 00:56:13 -04:00
DEFAULT_FEATURED_BADGE_COUNT = 3
2023-04-11 05:16:28 -04:00
PASSWORD_SALT_LENGTH = 16
TARGET_PASSWORD_ALGORITHM =
" $pbkdf2- #{ Rails . configuration . pbkdf2_algorithm } $i= #{ Rails . configuration . pbkdf2_iterations } ,l=32$ "
2023-10-27 05:27:04 -04:00
deprecate_column :flag_level , drop_from : " 3.2 "
2020-05-23 00:56:13 -04:00
# not deleted on user delete
2013-02-05 14:16:51 -05:00
has_many :posts
2020-05-23 00:56:13 -04:00
has_many :topics
has_many :uploads
2015-09-02 14:43:15 -04:00
has_many :category_users , dependent : :destroy
2016-05-04 14:02:47 -04:00
has_many :tag_users , dependent : :destroy
2016-08-16 03:06:33 -04:00
has_many :user_api_keys , dependent : :destroy
2020-05-22 23:25:56 -04:00
has_many :topic_allowed_users , dependent : :destroy
2020-05-23 00:56:13 -04:00
has_many :user_archived_messages , dependent : :destroy
has_many :email_change_requests , dependent : :destroy
2020-05-22 23:25:56 -04:00
has_many :email_tokens , dependent : :destroy
has_many :topic_links , dependent : :destroy
has_many :user_uploads , dependent : :destroy
2022-06-08 19:24:30 -04:00
has_many :upload_references , as : :target , dependent : :destroy
2021-02-22 06:42:37 -05:00
has_many :user_emails , dependent : :destroy , autosave : true
2020-05-23 00:56:13 -04:00
has_many :user_associated_accounts , dependent : :destroy
has_many :oauth2_user_infos , dependent : :destroy
has_many :user_second_factors , dependent : :destroy
has_many :user_badges , - > { for_enabled_badges } , dependent : :destroy
has_many :user_auth_tokens , dependent : :destroy
has_many :group_users , dependent : :destroy
has_many :user_warnings , dependent : :destroy
has_many :api_keys , dependent : :destroy
has_many :push_subscriptions , dependent : :destroy
has_many :acting_group_histories ,
dependent : :destroy ,
foreign_key : :acting_user_id ,
class_name : " GroupHistory "
has_many :targeted_group_histories ,
dependent : :destroy ,
foreign_key : :target_user_id ,
class_name : " GroupHistory "
has_many :reviewable_scores , dependent : :destroy
2020-06-09 11:19:32 -04:00
has_many :invites , foreign_key : :invited_by_id , dependent : :destroy
2021-04-27 01:52:45 -04:00
has_many :user_custom_fields , dependent : :destroy
2021-12-09 07:30:27 -05:00
has_many :user_associated_groups , dependent : :destroy
2021-08-26 12:16:00 -04:00
has_many :pending_posts ,
- > { merge ( Reviewable . pending ) } ,
class_name : " ReviewableQueuedPost " ,
2023-07-18 07:50:31 -04:00
foreign_key : :target_created_by_id
2020-05-22 23:25:56 -04:00
has_one :user_option , dependent : :destroy
has_one :user_avatar , dependent : :destroy
2021-03-10 07:49:13 -05:00
has_one :primary_email ,
- > { where ( primary : true ) } ,
class_name : " UserEmail " ,
dependent : :destroy ,
autosave : true ,
validate : false
2020-05-23 00:56:13 -04:00
has_one :user_stat , dependent : :destroy
has_one :user_profile , dependent : :destroy , inverse_of : :user
has_one :single_sign_on_record , dependent : :destroy
has_one :anonymous_user_master , class_name : " AnonymousUser " , dependent : :destroy
has_one :anonymous_user_shadow ,
- > ( record ) { where ( active : true ) } ,
foreign_key : :master_user_id ,
class_name : " AnonymousUser " ,
dependent : :destroy
2020-06-09 11:19:32 -04:00
has_one :invited_user , dependent : :destroy
2021-01-20 11:31:52 -05:00
has_one :user_notification_schedule , dependent : :destroy
2024-06-04 03:42:53 -04:00
has_many :passwords , class_name : " UserPassword " , dependent : :destroy
2020-05-22 23:25:56 -04:00
2020-05-23 00:56:13 -04:00
# delete all is faster but bypasses callbacks
has_many :bookmarks , dependent : :delete_all
has_many :notifications , dependent : :delete_all
has_many :topic_users , dependent : :delete_all
has_many :incoming_emails , dependent : :delete_all
has_many :user_visits , dependent : :delete_all
has_many :user_auth_token_logs , dependent : :delete_all
has_many :group_requests , dependent : :delete_all
has_many :muted_user_records , class_name : " MutedUser " , dependent : :delete_all
has_many :ignored_user_records , class_name : " IgnoredUser " , dependent : :delete_all
2020-12-18 10:03:51 -05:00
has_many :do_not_disturb_timings , dependent : :delete_all
2023-02-02 22:44:40 -05:00
has_many :sidebar_sections , dependent : :destroy
2022-05-27 05:15:14 -04:00
has_one :user_status , dependent : :destroy
2019-05-29 00:26:06 -04:00
2020-05-23 00:56:13 -04:00
# dependent deleting handled via before_destroy (special cases)
has_many :user_actions
has_many :post_actions
has_many :post_timings
has_many :directory_items
2021-02-22 08:07:47 -05:00
has_many :email_logs
2020-05-22 23:25:56 -04:00
has_many :security_keys , - > { where ( enabled : true ) } , class_name : " UserSecurityKey "
2023-08-24 02:27:38 -04:00
has_many :all_security_keys , class_name : " UserSecurityKey "
2020-05-22 23:25:56 -04:00
2020-05-23 00:56:13 -04:00
has_many :badges , through : :user_badges
2021-06-22 11:58:03 -04:00
has_many :default_featured_user_badges ,
2023-11-29 00:38:07 -05:00
- > do
2021-06-22 11:58:03 -04:00
max_featured_rank =
2023-01-09 07:20:10 -05:00
(
2021-06-22 11:58:03 -04:00
if SiteSetting . max_favorite_badges > 0
SiteSetting . max_favorite_badges + 1
2023-01-09 07:20:10 -05:00
else
2021-06-22 11:58:03 -04:00
DEFAULT_FEATURED_BADGE_COUNT
2023-01-09 07:20:10 -05:00
end
)
2021-06-22 11:58:03 -04:00
for_enabled_badges . grouped_with_count . where ( " featured_rank <= ? " , max_featured_rank )
2023-11-29 00:38:07 -05:00
end ,
2021-06-22 11:58:03 -04:00
class_name : " UserBadge "
2020-05-23 00:56:13 -04:00
has_many :topics_allowed , through : :topic_allowed_users , source : :topic
has_many :groups , through : :group_users
2022-12-05 13:39:10 -05:00
has_many :secure_categories , - > { distinct } , through : :groups , source : :categories
2021-12-09 07:30:27 -05:00
has_many :associated_groups , through : :user_associated_groups , dependent : :destroy
2020-05-23 00:56:13 -04:00
# deleted in user_second_factors relationship
has_many :totps ,
- > { where ( method : UserSecondFactor . methods [ :totp ] , enabled : true ) } ,
class_name : " UserSecondFactor "
2020-05-22 23:25:56 -04:00
2019-05-29 00:26:06 -04:00
has_one :master_user , through : :anonymous_user_master
has_one :shadow_user , through : :anonymous_user_shadow , source : :user
2019-04-28 23:58:52 -04:00
has_one :profile_background_upload , through : :user_profile
has_one :card_background_upload , through : :user_profile
2013-02-05 14:16:51 -05:00
belongs_to :approved_by , class_name : " User "
2014-04-23 22:42:04 -04:00
belongs_to :primary_group , class_name : " Group "
2021-07-08 03:46:21 -04:00
belongs_to :flair_group , class_name : " Group "
2013-02-05 14:16:51 -05:00
2015-03-23 20:55:22 -04:00
has_many :muted_users , through : :muted_user_records
2020-01-02 08:04:08 -05:00
has_many :ignored_users , through : :ignored_user_records
2014-05-22 03:37:02 -04:00
belongs_to :uploaded_avatar , class_name : " Upload "
2013-08-13 16:08:29 -04:00
2022-06-30 02:54:20 -04:00
has_many :sidebar_section_links , dependent : :delete_all
2024-05-16 15:47:01 -04:00
has_many :embeddable_hosts
2022-06-30 02:54:20 -04:00
2013-11-15 10:27:43 -05:00
delegate :last_sent_email_address , to : :email_logs
2013-02-05 14:16:51 -05:00
validates_presence_of :username
2017-08-31 00:06:56 -04:00
validate :username_validator , if : :will_save_change_to_username?
2013-02-05 14:16:51 -05:00
validate :password_validator
2019-05-13 16:43:19 -04:00
validate :name_validator , if : :will_save_change_to_name?
2017-08-31 00:06:56 -04:00
validates :name , user_full_name : true , if : :will_save_change_to_name? , length : { maximum : 255 }
2023-10-27 03:22:38 -04:00
validates :ip_address , allowed_ip_address : { on : :create }
2023-03-29 23:52:10 -04:00
validates :primary_email , presence : true , unless : :skip_email_validation
2024-01-29 12:44:32 -05:00
validates :validatable_user_fields_values ,
watched_words : true ,
unless : :should_skip_user_fields_validation?
2017-09-11 13:22:04 -04:00
validates_associated :primary_email ,
2024-06-20 05:42:17 -04:00
message : - > ( _ , user_email ) { user_email [ :value ] & . errors & . [] ( :email ) & . first }
2013-02-05 14:16:51 -05:00
after_initialize :add_trust_level
2015-08-21 14:39:21 -04:00
2017-10-25 01:02:18 -04:00
before_validation :set_skip_validate_email
2017-08-08 22:56:08 -04:00
2013-02-05 21:44:49 -05:00
after_create :create_email_token
2013-09-11 14:50:26 -04:00
after_create :create_user_stat
2016-02-16 23:46:19 -05:00
after_create :create_user_option
2014-05-27 13:54:04 -04:00
after_create :create_user_profile
2018-07-18 06:57:43 -04:00
after_create :set_random_avatar
2014-06-16 20:46:30 -04:00
after_create :ensure_in_trust_level_group
2015-08-21 14:39:21 -04:00
after_create :set_default_categories_preferences
2019-11-01 03:10:13 -04:00
after_create :set_default_tags_preferences
2023-07-26 22:52:33 -04:00
after_create :set_default_sidebar_section_links
2024-05-14 20:06:58 -04:00
after_create :refresh_user_directory , if : Proc . new { SiteSetting . bootstrap_mode_enabled }
2023-07-26 22:52:33 -04:00
after_update :set_default_sidebar_section_links , if : Proc . new { self . saved_change_to_staged? }
2014-08-13 16:17:16 -04:00
2020-05-07 21:27:26 -04:00
after_update :trigger_user_updated_event ,
if : Proc . new { self . human? && self . saved_change_to_uploaded_avatar_id? }
2019-06-17 01:10:47 -04:00
after_update :trigger_user_automatic_group_refresh , if : :saved_change_to_staged?
2023-06-25 23:01:59 -04:00
after_update :change_display_name , if : :saved_change_to_name?
2019-05-27 12:12:26 -04:00
2019-04-23 06:22:47 -04:00
before_save :update_usernames
2014-08-13 16:17:16 -04:00
before_save :ensure_password_is_hashed
2021-07-08 03:46:21 -04:00
before_save :match_primary_group_changes
2018-09-20 22:06:08 -04:00
before_save :check_if_title_is_badged_granted
2024-01-29 12:44:32 -05:00
before_save :apply_watched_words , unless : :should_skip_user_fields_validation?
2014-08-13 16:17:16 -04:00
2017-01-31 17:21:37 -05:00
after_save :expire_tokens_if_password_changed
2014-08-13 16:17:16 -04:00
after_save :clear_global_notice_if_needed
2014-05-22 03:37:02 -04:00
after_save :refresh_avatar
2014-07-22 21:42:24 -04:00
after_save :badge_grant
2015-06-05 13:50:06 -04:00
after_save :expire_old_email_tokens
2016-12-21 21:13:14 -05:00
after_save :index_search
2018-12-14 16:52:37 -05:00
after_save :check_site_contact_username
2019-06-17 01:10:47 -04:00
2022-06-08 19:24:30 -04:00
after_save do
if saved_change_to_uploaded_avatar_id?
UploadReference . ensure_exist! ( upload_ids : [ self . uploaded_avatar_id ] , target : self )
end
end
2017-03-16 04:02:34 -04:00
after_commit :trigger_user_created_event , on : :create
2018-07-23 03:49:49 -04:00
after_commit :trigger_user_destroyed_event , on : :destroy
2013-02-05 14:16:51 -05:00
2013-09-03 17:19:29 -04:00
before_destroy do
# These tables don't have primary keys, so destroying them with activerecord is tricky:
2017-08-31 00:06:56 -04:00
PostTiming . where ( user_id : self . id ) . delete_all
TopicViewItem . where ( user_id : self . id ) . delete_all
2019-01-16 20:40:30 -05:00
UserAction . where (
" user_id = :user_id OR target_user_id = :user_id OR acting_user_id = :user_id " ,
user_id : self . id ,
) . delete_all
# we need to bypass the default scope here, which appears not bypassed for :delete_all
# however :destroy it is bypassed
PostAction . with_deleted . where ( user_id : self . id ) . delete_all
2019-04-26 04:11:39 -04:00
# This is a perf optimisation to ensure we hit the index
# without this we need to scan a much larger number of rows
DirectoryItem
. where ( user_id : self . id )
. where ( " period_type in (?) " , DirectoryItem . period_types . values )
. delete_all
2020-05-23 00:56:13 -04:00
# our relationship filters on enabled, this makes sure everything is deleted
UserSecurityKey . where ( user_id : self . id ) . delete_all
2020-06-18 17:42:39 -04:00
Developer . where ( user_id : self . id ) . delete_all
DraftSequence . where ( user_id : self . id ) . delete_all
GivenDailyLike . where ( user_id : self . id ) . delete_all
MutedUser . where ( user_id : self . id ) . or ( MutedUser . where ( muted_user_id : self . id ) ) . delete_all
IgnoredUser . where ( user_id : self . id ) . or ( IgnoredUser . where ( ignored_user_id : self . id ) ) . delete_all
UserAvatar . where ( user_id : self . id ) . delete_all
2013-09-03 17:19:29 -04:00
end
2016-09-07 14:05:46 -04:00
# Skip validating email, for example from a particular auth provider plugin
attr_accessor :skip_email_validation
2013-02-05 14:16:51 -05:00
# Whether we need to be sending a system message after creation
attr_accessor :send_welcome_message
# This is just used to pass some information into the serializer
attr_accessor :notification_channel_position
2014-08-13 16:17:16 -04:00
# set to true to optimize creation and save for imports
attr_accessor :import_mode
2021-04-27 01:52:45 -04:00
# Cache for user custom fields. Currently it is used to display quick search results
attr_accessor :custom_data
2018-03-18 23:31:14 -04:00
scope :with_email ,
2018-03-19 00:34:21 -04:00
- > ( email ) { joins ( :user_emails ) . where ( " lower(user_emails.email) IN (?) " , email ) }
2017-04-26 14:47:36 -04:00
2021-07-05 00:56:32 -04:00
scope :with_primary_email ,
2023-11-29 00:38:07 -05:00
- > ( email ) do
2021-07-05 00:56:32 -04:00
joins ( :user_emails ) . where (
" lower(user_emails.email) IN (?) AND user_emails.primary " ,
email ,
)
2023-11-29 00:38:07 -05:00
end
2021-07-05 00:56:32 -04:00
2024-03-26 17:55:53 -04:00
scope :human_users ,
- > ( allowed_bot_user_ids : nil ) do
if allowed_bot_user_ids . present?
where ( " users.id > 0 OR users.id IN (?) " , allowed_bot_user_ids )
else
where ( " users.id > 0 " )
end
end
2017-03-11 01:25:09 -05:00
2015-05-10 19:10:10 -04:00
# excluding fake users like the system user or anonymous users
2017-03-11 01:25:09 -05:00
scope :real ,
2024-03-26 17:55:53 -04:00
- > ( allowed_bot_user_ids : nil ) do
human_users ( allowed_bot_user_ids : allowed_bot_user_ids ) . where (
2017-03-11 01:25:09 -05:00
" NOT EXISTS(
2015-05-10 19:10:10 -04:00
SELECT 1
2019-05-29 00:26:06 -04:00
FROM anonymous_users a
WHERE a . user_id = users . id
2023-01-09 07:20:10 -05:00
) " ,
2019-05-29 00:26:06 -04:00
)
2023-11-29 00:38:07 -05:00
end
2013-03-29 02:29:58 -04:00
2014-09-03 17:50:19 -04:00
# TODO-PERF: There is no indexes on any of these
# and NotifyMailingListSubscribers does a select-all-and-loop
2017-11-10 12:18:08 -05:00
# may want to create an index on (active, silence, suspended_till)?
2017-11-13 13:41:36 -05:00
scope :silenced , - > { where ( " silenced_till IS NOT NULL AND silenced_till > ? " , Time . zone . now ) }
scope :not_silenced , - > { where ( " silenced_till IS NULL OR silenced_till <= ? " , Time . zone . now ) }
2014-09-03 17:50:19 -04:00
scope :suspended , - > { where ( " suspended_till IS NOT NULL AND suspended_till > ? " , Time . zone . now ) }
scope :not_suspended , - > { where ( " suspended_till IS NULL OR suspended_till <= ? " , Time . zone . now ) }
scope :activated , - > { where ( active : true ) }
2022-06-07 14:58:58 -04:00
scope :not_staged , - > { where ( staged : false ) }
2014-09-03 17:50:19 -04:00
2018-03-22 01:42:46 -04:00
scope :filter_by_username ,
2023-11-29 00:38:07 -05:00
- > ( filter ) do
2018-03-26 02:30:37 -04:00
if filter . is_a? ( Array )
where ( " username_lower ~* ? " , " ( #{ filter . join ( " | " ) } ) " )
else
where ( " username_lower ILIKE ? " , " % #{ filter } % " )
2018-03-22 01:42:46 -04:00
end
2023-11-29 00:38:07 -05:00
end
2018-03-26 02:30:37 -04:00
2018-03-22 01:42:46 -04:00
scope :filter_by_username_or_email ,
2023-11-29 00:38:07 -05:00
- > ( filter ) do
2022-12-15 20:08:05 -05:00
if filter . is_a? ( String ) && filter =~ / .+@.+ /
2018-03-22 01:42:46 -04:00
# probably an email so try the bypass
2023-02-12 23:39:45 -05:00
if user_id = UserEmail . where ( " lower(email) = ? " , filter . downcase ) . pick ( :user_id )
2018-03-22 01:42:46 -04:00
return where ( " users.id = ? " , user_id )
2023-01-09 07:20:10 -05:00
end
end
2018-03-26 02:30:37 -04:00
users = joins ( :primary_email )
2023-01-09 07:20:10 -05:00
2018-03-26 02:30:37 -04:00
if filter . is_a? ( Array )
users . where (
" username_lower ~* :filter OR lower(user_emails.email) SIMILAR TO :filter " ,
filter : " ( #{ filter . join ( " | " ) } ) " ,
)
else
users . where (
2018-03-22 01:42:46 -04:00
" username_lower ILIKE :filter OR lower(user_emails.email) ILIKE :filter " ,
filter : " % #{ filter } % " ,
)
end
2023-11-29 00:38:07 -05:00
end
2018-03-22 01:42:46 -04:00
2022-10-05 20:10:43 -04:00
scope :watching_topic ,
2023-11-29 00:38:07 -05:00
- > ( topic ) do
2020-12-15 17:30:21 -05:00
joins (
DB . sql_fragment (
" LEFT JOIN category_users ON category_users.user_id = users.id AND category_users.category_id = :category_id " ,
category_id : topic . category_id ,
2023-01-09 07:20:10 -05:00
) ,
2020-12-15 17:30:21 -05:00
)
. joins (
DB . sql_fragment (
" LEFT JOIN topic_users ON topic_users.user_id = users.id AND topic_users.topic_id = :topic_id " ,
topic_id : topic . id ,
2023-01-09 07:20:10 -05:00
) ,
2020-12-15 17:30:21 -05:00
)
. joins (
" LEFT JOIN tag_users ON tag_users.user_id = users.id AND tag_users.tag_id IN ( #{ topic . tag_ids . join ( " , " ) . presence || " NULL " } ) " ,
)
. where (
" category_users.notification_level > 0 OR topic_users.notification_level > 0 OR tag_users.notification_level > 0 " ,
2023-01-09 07:20:10 -05:00
)
2023-11-29 00:38:07 -05:00
end
2020-12-15 17:30:21 -05:00
2013-02-14 01:32:58 -05:00
module NewTopicDuration
2013-02-25 11:42:20 -05:00
ALWAYS = - 1
2013-02-14 01:32:58 -05:00
LAST_VISIT = - 2
end
2014-03-07 12:58:53 -05:00
2019-08-10 06:02:12 -04:00
MAX_STAFF_DELETE_POST_COUNT || = 5
2022-11-09 13:20:34 -05:00
def self . user_tips
@user_tips || =
Enum . new (
first_notification : 1 ,
topic_timeline : 2 ,
2022-11-15 10:36:08 -05:00
post_menu : 3 ,
topic_notification_levels : 4 ,
suggested_topics : 5 ,
2022-11-09 13:20:34 -05:00
)
end
2024-01-29 12:44:32 -05:00
def should_skip_user_fields_validation?
custom_fields_clean? || SiteSetting . disable_watched_word_checking_in_user_fields
end
2024-05-06 12:32:18 -04:00
def all_sidebar_sections
sidebar_sections
. or ( SidebarSection . public_sections )
. includes ( :sidebar_urls )
. order ( " (section_type IS NOT NULL) DESC, (public IS TRUE) DESC " )
end
2023-07-26 22:52:33 -04:00
def secured_sidebar_category_ids ( user_guardian = nil )
user_guardian || = guardian
SidebarSectionLink . where ( user_id : self . id , linkable_type : " Category " ) . pluck ( :linkable_id ) &
user_guardian . allowed_category_ids
end
2022-10-26 18:38:50 -04:00
def visible_sidebar_tags ( user_guardian = nil )
user_guardian || = guardian
2023-07-26 22:52:33 -04:00
DiscourseTagging . filter_visible (
Tag . where (
id : SidebarSectionLink . where ( user_id : self . id , linkable_type : " Tag " ) . select ( :linkable_id ) ,
) ,
user_guardian ,
)
2022-10-26 18:38:50 -04:00
end
2014-09-11 15:22:11 -04:00
def self . max_password_length
200
end
2013-02-05 14:16:51 -05:00
def self . username_length
2014-07-16 12:25:24 -04:00
SiteSetting . min_username_length . to_i .. SiteSetting . max_username_length . to_i
2013-02-05 14:16:51 -05:00
end
2019-04-23 06:22:47 -04:00
def self . normalize_username ( username )
2022-04-04 17:15:32 -04:00
username . to_s . unicode_normalize . downcase if username . present?
2019-04-23 06:22:47 -04:00
end
2018-07-31 23:08:45 -04:00
def self . username_available? ( username , email = nil , allow_reserved_username : false )
2019-04-23 06:22:47 -04:00
lower = normalize_username ( username )
2018-07-31 23:08:45 -04:00
return false if ! allow_reserved_username && reserved_username? ( lower )
2019-02-19 16:31:03 -05:00
return true if ! username_exists? ( lower )
2018-05-22 15:25:52 -04:00
2017-12-12 05:26:00 -05:00
# staged users can use the same username since they will take over the account
email . present? &&
User . joins ( :user_emails ) . exists? (
staged : true ,
username_lower : lower ,
user_emails : {
primary : true ,
email : email ,
} ,
)
2017-04-12 22:44:26 -04:00
end
def self . reserved_username? ( username )
2019-04-23 06:22:47 -04:00
username = normalize_username ( username )
2016-08-31 09:49:45 -04:00
2021-11-23 15:25:54 -05:00
return true if SiteSetting . here_mention == username
2019-04-23 06:22:47 -04:00
SiteSetting
. reserved_usernames
. unicode_normalize
. split ( " | " )
2023-01-20 13:52:49 -05:00
. any? { | reserved | username . match? ( / \ A #{ Regexp . escape ( reserved ) . gsub ( '\*' , " .* " ) } \ z / ) }
2013-02-05 14:16:51 -05:00
end
2019-10-11 04:57:55 -04:00
def self . editable_user_custom_fields ( by_staff : false )
2018-09-04 06:45:36 -04:00
fields = [ ]
2020-08-30 18:52:01 -04:00
fields . push ( * DiscoursePluginRegistry . self_editable_user_custom_fields )
fields . push ( * DiscoursePluginRegistry . staff_editable_user_custom_fields ) if by_staff
2019-10-11 04:57:55 -04:00
2018-09-04 06:45:36 -04:00
fields . uniq
end
2020-07-26 20:23:54 -04:00
def self . allowed_user_custom_fields ( guardian )
2016-03-11 15:52:18 -05:00
fields = [ ]
2020-08-30 18:52:01 -04:00
fields . push ( * DiscoursePluginRegistry . public_user_custom_fields )
2018-10-17 05:33:27 -04:00
2016-03-11 15:52:18 -05:00
if SiteSetting . public_user_custom_fields . present?
2020-08-30 18:52:01 -04:00
fields . push ( * SiteSetting . public_user_custom_fields . split ( " | " ) )
2016-03-11 15:52:18 -05:00
end
if guardian . is_staff?
if SiteSetting . staff_user_custom_fields . present?
2020-08-30 18:52:01 -04:00
fields . push ( * SiteSetting . staff_user_custom_fields . split ( " | " ) )
2016-03-11 15:52:18 -05:00
end
2020-05-15 09:04:38 -04:00
2020-08-30 18:52:01 -04:00
fields . push ( * DiscoursePluginRegistry . staff_user_custom_fields )
2016-03-11 15:52:18 -05:00
end
fields . uniq
end
2020-05-25 20:07:00 -04:00
def self . human_user_id? ( user_id )
user_id > 0
end
2019-02-08 13:34:54 -05:00
def human?
2020-05-25 20:07:00 -04:00
User . human_user_id? ( self . id )
2019-02-08 13:34:54 -05:00
end
2019-03-11 19:58:14 -04:00
def bot?
! self . human?
end
2015-02-05 22:38:51 -05:00
def effective_locale
if SiteSetting . allow_user_locale && self . locale . present?
self . locale
else
SiteSetting . default_locale
end
end
2022-04-21 18:23:42 -04:00
def bookmarks_of_type ( type )
bookmarks . where ( bookmarkable_type : type )
end
2013-03-31 12:51:13 -04:00
EMAIL = / ([^@]+)@([^ \ .]+) /
2020-04-30 02:48:34 -04:00
FROM_STAGED = " from_staged "
2013-03-31 12:51:13 -04:00
2013-04-12 18:46:55 -04:00
def self . new_from_params ( params )
user = User . new
user . name = params [ :name ]
user . email = params [ :email ]
user . password = params [ :password ]
user . username = params [ :username ]
user
end
2020-03-17 11:48:24 -04:00
def unstage!
2018-05-14 06:03:15 -04:00
if self . staged
2020-03-17 11:48:24 -04:00
ActiveRecord :: Base . transaction do
self . staged = false
self . custom_fields [ FROM_STAGED ] = true
self . notifications . destroy_all
save!
end
2018-05-13 11:00:02 -04:00
2020-03-17 11:48:24 -04:00
DiscourseEvent . trigger ( :user_unstaged , self )
2018-01-19 09:29:15 -05:00
end
end
2018-05-17 02:51:48 -04:00
def self . suggest_name ( string )
return " " if string . blank?
2018-05-17 04:34:16 -04:00
( string [ / \ A[^@]+ / ] . presence || string [ / [^@]+ \ z / ] ) . tr ( " . " , " " ) . titleize
2013-02-05 14:16:51 -05:00
end
2013-04-29 02:33:24 -04:00
def self . find_by_username_or_email ( username_or_email )
2013-10-28 01:29:07 -04:00
if username_or_email . include? ( " @ " )
find_by_email ( username_or_email )
2013-06-18 20:31:19 -04:00
else
2013-10-28 01:29:07 -04:00
find_by_username ( username_or_email )
2013-06-18 20:31:19 -04:00
end
2013-04-29 02:33:24 -04:00
end
2021-07-05 00:56:32 -04:00
def self . find_by_email ( email , primary : false )
if primary
self . with_primary_email ( Email . downcase ( email ) ) . first
else
self . with_email ( Email . downcase ( email ) ) . first
end
2013-10-24 03:59:58 -04:00
end
def self . find_by_username ( username )
2019-04-23 06:22:47 -04:00
find_by ( username_lower : normalize_username ( username ) )
2013-10-24 03:59:58 -04:00
end
2022-09-25 23:58:40 -04:00
def in_any_groups? ( group_ids )
2024-01-21 23:40:29 -05:00
group_ids . include? ( Group :: AUTO_GROUPS [ :everyone ] ) ||
( is_system_user? && ( Group . auto_groups_between ( :admins , :trust_level_4 ) & group_ids ) . any? ) ||
( group_ids & belonging_to_group_ids ) . any?
2022-09-25 23:58:40 -04:00
end
def belonging_to_group_ids
@belonging_to_group_ids || = group_users . pluck ( :group_id )
end
2018-08-24 18:41:03 -04:00
def group_granted_trust_level
GroupUser . where ( user_id : id ) . includes ( :group ) . maximum ( " groups.grant_trust_level " )
end
2018-12-18 02:41:42 -05:00
def visible_groups
groups . visible_groups ( self )
end
2018-08-24 18:41:03 -04:00
2013-04-29 02:33:24 -04:00
def enqueue_welcome_message ( message_type )
return unless SiteSetting . send_welcome_message?
Jobs . enqueue ( :send_system_message , user_id : id , message_type : message_type )
end
2018-06-22 12:51:07 -04:00
def enqueue_member_welcome_message
return unless SiteSetting . send_tl1_welcome_message?
Jobs . enqueue ( :send_system_message , user_id : id , message_type : " welcome_tl1_user " )
end
2020-09-21 20:17:52 -04:00
def enqueue_tl2_promotion_message
return unless SiteSetting . send_tl2_promotion_message
Jobs . enqueue ( :send_system_message , user_id : id , message_type : " tl2_promotion_message " )
end
2019-11-05 07:45:55 -05:00
def enqueue_staff_welcome_message ( role )
return unless staff?
2024-04-23 14:50:14 -04:00
return if is_singular_admin?
2019-11-05 07:45:55 -05:00
Jobs . enqueue (
:send_system_message ,
user_id : id ,
message_type : " welcome_staff " ,
message_options : {
2022-03-24 21:07:21 -04:00
role : role . to_s ,
2019-11-05 07:45:55 -05:00
} ,
)
2019-10-28 09:58:45 -04:00
end
2015-01-16 17:30:46 -05:00
def change_username ( new_username , actor = nil )
2015-03-06 16:44:54 -05:00
UsernameChanger . change ( self , new_username , actor )
2013-02-05 14:16:51 -05:00
end
2013-09-12 17:46:43 -04:00
def created_topic_count
2014-07-28 13:17:37 -04:00
stat . topic_count
2013-09-12 17:46:43 -04:00
end
2013-02-26 11:27:59 -05:00
2014-07-28 13:17:37 -04:00
alias_method :topic_count , :created_topic_count
2013-02-05 14:16:51 -05:00
# tricky, we need our bus to be subscribed from the right spot
def sync_notification_channel_position
@unread_notifications_by_type = nil
2015-05-03 22:21:00 -04:00
self . notification_channel_position = MessageBus . last_id ( " /notification/ #{ id } " )
2013-02-05 14:16:51 -05:00
end
def invited_by
2021-03-06 06:29:35 -05:00
used_invite =
Invite . with_deleted . joins ( :invited_users ) . where ( " invited_users.user_id = ? " , self . id ) . first
2013-02-28 08:08:56 -05:00
used_invite . try ( :invited_by )
2013-02-05 14:16:51 -05:00
end
2017-08-08 22:56:08 -04:00
def should_validate_email_address?
2017-04-26 14:47:36 -04:00
! skip_email_validation && ! staged?
2016-09-07 14:05:46 -04:00
end
2013-02-05 14:16:51 -05:00
def self . email_hash ( email )
2013-02-05 21:44:49 -05:00
Digest :: MD5 . hexdigest ( email . strip . downcase )
2013-02-05 14:16:51 -05:00
end
def email_hash
2013-02-28 08:08:56 -05:00
User . email_hash ( email )
2013-02-05 14:16:51 -05:00
end
def reload
2015-04-17 02:01:20 -04:00
@unread_notifications = nil
2022-08-03 01:57:59 -04:00
@all_unread_notifications_count = nil
2014-10-13 06:26:30 -04:00
@unread_total_notifications = nil
2013-05-16 02:37:47 -04:00
@unread_pms = nil
2020-03-31 19:09:20 -04:00
@unread_bookmarks = nil
@unread_high_prios = nil
2020-01-02 08:04:08 -05:00
@ignored_user_ids = nil
@muted_user_ids = nil
2022-09-25 23:58:40 -04:00
@belonging_to_group_ids = nil
2013-02-05 14:16:51 -05:00
super
end
2020-01-02 08:04:08 -05:00
def ignored_user_ids
@ignored_user_ids || = ignored_users . pluck ( :id )
end
def muted_user_ids
@muted_user_ids || = muted_users . pluck ( :id )
end
2023-05-09 13:19:26 -04:00
def unread_notifications_of_type ( notification_type , since : nil )
2016-12-12 14:20:25 -05:00
# perf critical, much more efficient than AR
2018-05-25 20:09:48 -04:00
sql = << ~ SQL
SELECT COUNT ( * )
FROM notifications n
LEFT JOIN topics t ON t . id = n . topic_id
WHERE t . deleted_at IS NULL
2020-03-31 19:09:20 -04:00
AND n . notification_type = :notification_type
2018-05-25 20:09:48 -04:00
AND n . user_id = :user_id
AND NOT read
2023-05-09 13:19:26 -04:00
#{since ? "AND n.created_at > :since" : ""}
2018-05-25 20:09:48 -04:00
SQL
2016-12-12 14:20:25 -05:00
2018-06-19 02:13:14 -04:00
# to avoid coalesce we do to_i
2023-05-09 13:19:26 -04:00
DB . query_single ( sql , user_id : id , notification_type : notification_type , since : since ) [ 0 ] . to_i
2016-12-12 14:20:25 -05:00
end
2015-04-17 02:01:20 -04:00
2020-03-31 19:09:20 -04:00
def unread_notifications_of_priority ( high_priority : )
# perf critical, much more efficient than AR
sql = << ~ SQL
SELECT COUNT ( * )
FROM notifications n
LEFT JOIN topics t ON t . id = n . topic_id
WHERE t . deleted_at IS NULL
AND n . high_priority = :high_priority
AND n . user_id = :user_id
AND NOT read
SQL
# to avoid coalesce we do to_i
DB . query_single ( sql , user_id : id , high_priority : high_priority ) [ 0 ] . to_i
end
2022-08-30 21:16:28 -04:00
MAX_UNREAD_BACKLOG = 400
def grouped_unread_notifications
results = DB . query ( << ~ SQL , user_id : self . id , limit : MAX_UNREAD_BACKLOG )
2022-08-08 10:24:04 -04:00
SELECT X . notification_type AS type , COUNT ( * ) FROM (
SELECT n . notification_type
FROM notifications n
LEFT JOIN topics t ON t . id = n . topic_id
WHERE t . deleted_at IS NULL
AND n . user_id = :user_id
AND NOT n . read
LIMIT :limit
) AS X
GROUP BY X . notification_type
SQL
results . map! { | row | [ row . type , row . count ] }
results . to_h
end
2020-03-31 19:09:20 -04:00
def unread_high_priority_notifications
@unread_high_prios || = unread_notifications_of_priority ( high_priority : true )
2013-02-05 14:16:51 -05:00
end
2022-11-30 18:05:32 -05:00
def new_personal_messages_notifications_count
args = {
user_id : self . id ,
seen_notification_id : self . seen_notification_id ,
private_message : Notification . types [ :private_message ] ,
}
DB . query_single ( << ~ SQL , args ) . first
SELECT COUNT ( * )
FROM notifications
WHERE user_id = :user_id
AND id > :seen_notification_id
AND NOT read
AND notification_type = :private_message
SQL
end
2018-10-23 20:53:28 -04:00
# PERF: This safeguard is in place to avoid situations where
# a user with enormous amounts of unread data can issue extremely
# expensive queries
MAX_UNREAD_NOTIFICATIONS = 99
2018-10-23 21:10:27 -04:00
def self . max_unread_notifications
@max_unread_notifications || = MAX_UNREAD_NOTIFICATIONS
end
def self . max_unread_notifications = ( val )
@max_unread_notifications = val
end
2013-02-05 14:16:51 -05:00
def unread_notifications
2018-05-25 20:09:48 -04:00
@unread_notifications || =
begin
# perf critical, much more efficient than AR
sql = << ~ SQL
2018-10-23 21:10:27 -04:00
SELECT COUNT ( * ) FROM (
SELECT 1 FROM
notifications n
LEFT JOIN topics t ON t . id = n . topic_id
WHERE t . deleted_at IS NULL AND
2020-03-31 19:09:20 -04:00
n . high_priority = FALSE AND
2018-10-23 21:10:27 -04:00
n . user_id = :user_id AND
n . id > :seen_notification_id AND
NOT read
LIMIT :limit
) AS X
2018-05-25 20:09:48 -04:00
SQL
2018-06-19 02:13:14 -04:00
DB . query_single (
sql ,
2018-05-25 20:09:48 -04:00
user_id : id ,
seen_notification_id : seen_notification_id ,
2018-10-23 21:10:27 -04:00
limit : User . max_unread_notifications ,
2018-06-19 02:13:14 -04:00
) [
2023-01-09 07:20:10 -05:00
0
2018-06-19 02:13:14 -04:00
] . to_i
2018-05-25 20:09:48 -04:00
end
2013-02-05 14:16:51 -05:00
end
2013-02-05 21:44:49 -05:00
2022-08-03 01:57:59 -04:00
def all_unread_notifications_count
@all_unread_notifications_count || =
begin
sql = << ~ SQL
SELECT COUNT ( * ) FROM (
SELECT 1 FROM
notifications n
LEFT JOIN topics t ON t . id = n . topic_id
WHERE t . deleted_at IS NULL AND
n . user_id = :user_id AND
n . id > :seen_notification_id AND
NOT read
LIMIT :limit
) AS X
SQL
DB . query_single (
sql ,
user_id : id ,
seen_notification_id : seen_notification_id ,
limit : User . max_unread_notifications ,
) [
2023-01-09 07:20:10 -05:00
0
2022-08-03 01:57:59 -04:00
] . to_i
end
end
2014-10-13 06:26:30 -04:00
def total_unread_notifications
@unread_total_notifications || = notifications . where ( " read = false " ) . count
end
2022-07-28 04:16:33 -04:00
def reviewable_count
2023-05-17 12:16:42 -04:00
Reviewable . list_for ( self , include_claimed_by_others : false ) . count
2022-08-03 01:57:59 -04:00
end
2022-09-12 14:19:25 -04:00
def bump_last_seen_notification!
query = self . notifications . visible
query = query . where ( " notifications.id > ? " , seen_notification_id ) if seen_notification_id
if max_notification_id = query . maximum ( :id )
update! ( seen_notification_id : max_notification_id )
true
else
false
end
end
2022-08-03 01:57:59 -04:00
def bump_last_seen_reviewable!
query = Reviewable . unseen_list_for ( self , preload : false )
2022-11-30 18:09:57 -05:00
query = query . where ( " reviewables.id > ? " , last_seen_reviewable_id ) if last_seen_reviewable_id
2022-08-03 01:57:59 -04:00
max_reviewable_id = query . maximum ( :id )
if max_reviewable_id
update! ( last_seen_reviewable_id : max_reviewable_id )
2022-11-30 18:09:57 -05:00
publish_reviewable_counts
2022-08-03 01:57:59 -04:00
end
end
2022-11-30 18:09:57 -05:00
def publish_reviewable_counts ( extra_data = nil )
data = {
reviewable_count : self . reviewable_count ,
unseen_reviewable_count : Reviewable . unseen_reviewable_count ( self ) ,
}
data . merge! ( extra_data ) if extra_data . present?
2022-08-03 01:57:59 -04:00
MessageBus . publish ( " /reviewable_counts/ #{ self . id } " , data , user_ids : [ self . id ] )
end
2016-11-08 03:12:40 -05:00
def read_first_notification?
2023-06-26 11:39:29 -04:00
self . seen_notification_id != 0 || user_option . skip_new_user_tips
2016-11-08 03:12:40 -05:00
end
2013-02-05 14:16:51 -05:00
def publish_notifications_state
2021-11-21 22:38:49 -05:00
return if ! self . allow_live_notifications?
2018-05-25 20:09:48 -04:00
# publish last notification json with the message so we can apply an update
2018-06-28 11:04:40 -04:00
notification = notifications . visible . order ( " notifications.created_at desc " ) . first
2015-09-03 23:20:33 -04:00
json = NotificationSerializer . new ( notification ) . as_json if notification
2020-04-30 02:48:34 -04:00
sql = ( << ~ SQL )
2016-02-15 03:29:35 -05:00
SELECT * FROM (
SELECT n . id , n . read FROM notifications n
LEFT JOIN topics t ON n . topic_id = t . id
WHERE
t . deleted_at IS NULL AND
2020-03-31 19:09:20 -04:00
n . high_priority AND
2016-02-15 03:29:35 -05:00
n . user_id = :user_id AND
NOT read
ORDER BY n . id DESC
LIMIT 20
) AS x
UNION ALL
SELECT * FROM (
SELECT n . id , n . read FROM notifications n
LEFT JOIN topics t ON n . topic_id = t . id
WHERE
t . deleted_at IS NULL AND
2020-03-31 19:09:20 -04:00
( n . high_priority = FALSE OR read ) AND
2016-02-15 03:29:35 -05:00
n . user_id = :user_id
ORDER BY n . id DESC
LIMIT 20
) AS y
2018-06-19 02:13:14 -04:00
SQL
2016-02-15 03:29:35 -05:00
2020-03-31 19:09:20 -04:00
recent = DB . query ( sql , user_id : id ) . map! { | r | [ r . id , r . read ] }
2016-02-15 03:29:35 -05:00
2018-05-25 20:09:48 -04:00
payload = {
unread_notifications : unread_notifications ,
2020-03-31 19:09:20 -04:00
unread_high_priority_notifications : unread_high_priority_notifications ,
2018-05-25 20:09:48 -04:00
read_first_notification : read_first_notification? ,
last_notification : json ,
recent : recent ,
seen_notification_id : seen_notification_id ,
}
2023-05-17 12:16:42 -04:00
payload [ :all_unread_notifications_count ] = all_unread_notifications_count
payload [ :grouped_unread_notifications ] = grouped_unread_notifications
payload [ :new_personal_messages_notifications_count ] = new_personal_messages_notifications_count
2022-08-03 01:57:59 -04:00
2018-05-25 20:09:48 -04:00
MessageBus . publish ( " /notification/ #{ id } " , payload , user_ids : [ id ] )
2013-02-05 14:16:51 -05:00
end
2020-12-18 10:03:51 -05:00
def publish_do_not_disturb ( ends_at : nil )
2022-06-17 00:24:15 -04:00
MessageBus . publish ( " /do-not-disturb/ #{ id } " , { ends_at : ends_at & . httpdate } , user_ids : [ id ] )
2020-12-18 10:03:51 -05:00
end
2022-05-30 05:41:53 -04:00
def publish_user_status ( status )
2022-07-05 11:12:22 -04:00
if status
payload = {
description : status . description ,
emoji : status . emoji ,
ends_at : status . ends_at & . iso8601 ,
}
else
payload = nil
end
2022-06-17 00:24:15 -04:00
2022-07-07 09:37:05 -04:00
MessageBus . publish (
" /user-status " ,
{ id = > payload } ,
group_ids : [ Group :: AUTO_GROUPS [ :trust_level_0 ] ] ,
)
2022-05-30 05:41:53 -04:00
end
2013-02-05 14:16:51 -05:00
def password = ( password )
2013-02-05 21:44:49 -05:00
# special case for passwordless accounts
2024-05-27 06:27:13 -04:00
@raw_password = password if password . present?
2013-02-05 14:16:51 -05:00
end
2013-12-19 15:12:03 -05:00
def password
" " # so that validator doesn't complain that a password attribute doesn't exist
end
2013-02-12 15:42:04 -05:00
# Indicate that this is NOT a passwordless account for the purposes of validation
2013-02-28 08:08:56 -05:00
def password_required!
2013-02-12 15:42:04 -05:00
@password_required = true
end
2013-12-19 15:12:03 -05:00
def password_required?
! ! @password_required
end
2017-11-30 23:19:24 -05:00
def password_validation_required?
password_required? || @raw_password . present?
end
2014-01-21 12:42:20 -05:00
def has_password?
password_hash . present?
end
2013-12-19 15:12:03 -05:00
def password_validator
PasswordValidator . new ( attributes : :password ) . validate_each ( self , :password , @raw_password )
end
2024-06-04 03:42:53 -04:00
def password_expired? ( password )
passwords
. where ( " password_expired_at IS NOT NULL AND password_expired_at < ? " , Time . zone . now )
. any? do | user_password |
user_password . password_hash ==
hash_password ( password , user_password . password_salt , user_password . password_algorithm )
end
end
2013-02-05 14:16:51 -05:00
def confirm_password? ( password )
2023-04-11 05:16:28 -04:00
return false unless password_hash && salt && password_algorithm
confirmed = self . password_hash == hash_password ( password , salt , password_algorithm )
if confirmed && persisted? && password_algorithm != TARGET_PASSWORD_ALGORITHM
# Regenerate password_hash with new algorithm and persist
salt = SecureRandom . hex ( PASSWORD_SALT_LENGTH )
update_columns (
password_algorithm : TARGET_PASSWORD_ALGORITHM ,
salt : salt ,
password_hash : hash_password ( password , salt , TARGET_PASSWORD_ALGORITHM ) ,
)
end
confirmed
2013-02-05 14:16:51 -05:00
end
2013-10-11 13:33:23 -04:00
2016-06-20 16:38:15 -04:00
def new_user_posting_on_first_day?
2015-03-26 01:48:36 -04:00
! staff? && trust_level < TrustLevel [ 2 ] &&
2018-06-20 20:25:03 -04:00
(
trust_level == TrustLevel [ 0 ] || self . first_post_created_at . nil? ||
self . first_post_created_at > = 24 . hours . ago
)
2015-03-26 01:48:36 -04:00
end
2013-10-11 13:33:23 -04:00
def new_user?
2015-03-26 01:04:32 -04:00
( created_at > = 24 . hours . ago || trust_level == TrustLevel [ 0 ] ) && trust_level < TrustLevel [ 2 ] &&
! staff?
2013-10-11 13:33:23 -04:00
end
2013-02-05 14:16:51 -05:00
2013-02-12 00:41:04 -05:00
def seen_before?
last_seen_at . present?
end
2021-11-21 22:38:49 -05:00
def seen_since? ( datetime )
seen_before? && last_seen_at > = datetime
end
2015-07-07 12:31:07 -04:00
def create_visit_record! ( date , opts = { } )
user_stat . update_column ( :days_visited , user_stat . days_visited + 1 )
user_visits . create! (
visited_at : date ,
posts_read : opts [ :posts_read ] || 0 ,
mobile : opts [ :mobile ] || false ,
)
end
2014-01-24 15:19:20 -05:00
def visit_record_for ( date )
2014-05-06 09:41:59 -04:00
user_visits . find_by ( visited_at : date )
2013-02-12 00:41:04 -05:00
end
def update_visit_record! ( date )
2014-01-24 15:19:20 -05:00
create_visit_record! ( date ) unless visit_record_for ( date )
end
2019-11-24 19:49:27 -05:00
def update_timezone_if_missing ( timezone )
return if timezone . blank? || ! TimezoneValidator . valid? ( timezone )
# we only want to update the user's timezone if they have not set it themselves
UserOption . where ( user_id : self . id , timezone : nil ) . update_all ( timezone : timezone )
end
2014-01-24 15:19:20 -05:00
2015-07-07 12:31:07 -04:00
def update_posts_read! ( num_posts , opts = { } )
now = opts [ :at ] || Time . zone . now
_retry = opts [ :retry ] || false
2014-01-24 15:19:20 -05:00
if user_visit = visit_record_for ( now . to_date )
user_visit . posts_read += num_posts
2015-07-07 12:31:07 -04:00
user_visit . mobile = true if opts [ :mobile ]
2014-01-24 15:19:20 -05:00
user_visit . save
user_visit
else
2015-05-31 21:55:07 -04:00
begin
2015-07-07 12:31:07 -04:00
create_visit_record! ( now . to_date , posts_read : num_posts , mobile : opts . fetch ( :mobile , false ) )
2015-05-31 21:55:07 -04:00
rescue ActiveRecord :: RecordNotUnique
if ! _retry
2015-08-23 20:28:38 -04:00
update_posts_read! ( num_posts , opts . merge ( retry : true ) )
2015-05-31 21:55:07 -04:00
else
raise
end
end
2013-02-12 00:41:04 -05:00
end
end
2022-05-02 18:50:56 -04:00
def self . update_ip_address! ( user_id , new_ip : , old_ip : )
unless old_ip == new_ip || new_ip . blank?
DB . exec ( << ~ SQL , user_id : user_id , ip_address : new_ip )
UPDATE users
SET ip_address = :ip_address
WHERE id = :user_id
SQL
2020-09-16 22:55:29 -04:00
if SiteSetting . keep_old_ip_address_count > 0
2022-05-02 18:50:56 -04:00
DB . exec ( << ~ SQL , user_id : user_id , ip_address : new_ip , current_timestamp : Time . zone . now )
2020-09-16 22:55:29 -04:00
INSERT INTO user_ip_address_histories ( user_id , ip_address , created_at , updated_at )
VALUES ( :user_id , :ip_address , :current_timestamp , :current_timestamp )
ON CONFLICT ( user_id , ip_address )
DO
UPDATE SET updated_at = :current_timestamp
SQL
2022-05-02 18:50:56 -04:00
DB . exec ( << ~ SQL , user_id : user_id , offset : SiteSetting . keep_old_ip_address_count )
2020-09-16 22:55:29 -04:00
DELETE FROM user_ip_address_histories
WHERE id IN (
SELECT
id
FROM user_ip_address_histories
WHERE user_id = :user_id
ORDER BY updated_at DESC
OFFSET :offset
)
SQL
end
2013-02-24 05:42:04 -05:00
end
end
2022-05-02 18:50:56 -04:00
def update_ip_address! ( new_ip_address )
User . update_ip_address! ( id , new_ip : new_ip_address , old_ip : ip_address )
end
def self . last_seen_redis_key ( user_id , now )
2013-10-23 17:24:50 -04:00
now_date = now . to_date
2022-05-02 18:50:56 -04:00
" user: #{ user_id } : #{ now_date } "
end
def last_seen_redis_key ( now )
User . last_seen_redis_key ( id , now )
2020-08-30 18:54:42 -04:00
end
def clear_last_seen_cache! ( now = Time . zone . now )
Discourse . redis . del ( last_seen_redis_key ( now ) )
end
2022-05-02 18:50:56 -04:00
def self . should_update_last_seen? ( user_id , now = Time . zone . now )
return true if SiteSetting . active_user_rate_limit_secs < = 0
2020-08-30 18:54:42 -04:00
2022-05-02 18:50:56 -04:00
Discourse . redis . set (
last_seen_redis_key ( user_id , now ) ,
" 1 " ,
nx : true ,
ex : SiteSetting . active_user_rate_limit_secs ,
)
end
def update_last_seen! ( now = Time . zone . now , force : false )
if ! force
return if ! User . should_update_last_seen? ( self . id , now )
2020-08-30 18:54:42 -04:00
end
2013-02-05 14:16:51 -05:00
2013-10-23 17:24:50 -04:00
update_previous_visit ( now )
# using update_column to avoid the AR transaction
update_column ( :last_seen_at , now )
2016-05-21 09:17:54 -04:00
update_column ( :first_seen_at , now ) unless self . first_seen_at
2017-03-31 18:30:59 -04:00
DiscourseEvent . trigger ( :user_seen , self )
2013-02-05 14:16:51 -05:00
end
2013-08-13 16:08:29 -04:00
def self . gravatar_template ( email )
2020-03-12 11:23:55 -04:00
" // #{ SiteSetting . gravatar_base_url } /avatar/ #{ self . email_hash ( email ) } .png?s={size}&r=pg&d=identicon "
2013-02-05 14:16:51 -05:00
end
2013-03-08 15:58:37 -05:00
# Don't pass this up to the client - it's meant for server side use
2013-08-13 16:08:29 -04:00
# This is used in
# - self oneboxes in open graph data
# - emails
2013-03-08 15:58:37 -05:00
def small_avatar_url
2014-05-22 03:37:02 -04:00
avatar_template_url . gsub ( " {size} " , " 45 " )
2013-03-08 15:58:37 -05:00
end
2014-05-22 03:37:02 -04:00
def avatar_template_url
2015-06-12 06:02:36 -04:00
UrlHelper . schemaless UrlHelper . absolute avatar_template
2013-09-10 15:18:22 -04:00
end
2018-07-18 06:57:43 -04:00
def self . username_hash ( username )
username
. each_char
. reduce ( 0 ) do | result , char |
[ ( ( result << 5 ) - result ) + char . ord ] . pack ( " L " ) . unpack ( " l " ) . first
end
. abs
end
2015-06-26 13:37:50 -04:00
def self . default_template ( username )
if SiteSetting . default_avatars . present?
2018-07-18 06:57:43 -04:00
urls = SiteSetting . default_avatars . split ( " \n " )
return urls [ username_hash ( username ) % urls . size ] if urls . present?
2015-06-26 13:37:50 -04:00
end
2018-07-18 06:57:43 -04:00
system_avatar_template ( username )
2015-06-26 13:37:50 -04:00
end
2015-09-10 20:12:40 -04:00
def self . avatar_template ( username , uploaded_avatar_id )
2014-05-22 03:37:02 -04:00
username || = " "
2015-09-11 09:04:29 -04:00
return default_template ( username ) if ! uploaded_avatar_id
2015-05-29 12:51:17 -04:00
hostname = RailsMultisite :: ConnectionManagement . current_hostname
UserAvatar . local_avatar_template ( hostname , username . downcase , uploaded_avatar_id )
2014-05-22 03:37:02 -04:00
end
2015-09-11 04:14:34 -04:00
def self . system_avatar_template ( username )
2019-04-24 17:18:52 -04:00
normalized_username = normalize_username ( username )
2015-09-11 04:14:34 -04:00
# TODO it may be worth caching this in a distributed cache, should be benched
if SiteSetting . external_system_avatars_enabled
url = SiteSetting . external_system_avatars_url . dup
2023-01-20 13:52:49 -05:00
url = + " #{ Discourse . base_path } #{ url } " unless url =~ %r{ \ Ahttps?:// }
2019-04-24 17:18:52 -04:00
url . gsub! " {color} " , letter_avatar_color ( normalized_username )
2019-12-11 21:49:21 -05:00
url . gsub! " {username} " , UrlHelper . encode_component ( username )
url . gsub! " {first_letter} " ,
UrlHelper . encode_component ( normalized_username . grapheme_clusters . first )
2015-10-02 03:27:54 -04:00
url . gsub! " {hostname} " , Discourse . current_hostname
2015-09-11 04:14:34 -04:00
url
2015-09-10 20:12:40 -04:00
else
2020-10-09 07:51:24 -04:00
" #{ Discourse . base_path } /letter_avatar/ #{ normalized_username } /{size}/ #{ LetterAvatar . version } .png "
2015-09-10 20:12:40 -04:00
end
end
def self . letter_avatar_color ( username )
2015-09-11 09:04:29 -04:00
username || = " "
2019-03-18 11:24:21 -04:00
if SiteSetting . restrict_letter_avatar_colors . present?
hex_length = 6
colors = SiteSetting . restrict_letter_avatar_colors
length = colors . count ( " | " ) + 1
num = color_index ( username , length )
index = ( num * hex_length ) + num
colors [ index , hex_length ]
else
color = LetterAvatar :: COLORS [ color_index ( username , LetterAvatar :: COLORS . length ) ]
color . map { | c | c . to_s ( 16 ) . rjust ( 2 , " 0 " ) } . join
end
end
def self . color_index ( username , length )
Digest :: MD5 . hexdigest ( username ) [ 0 ... 15 ] . to_i ( 16 ) % length
2014-05-30 00:17:35 -04:00
end
2021-04-27 16:28:15 -04:00
def is_system_user?
id == Discourse :: SYSTEM_USER_ID
end
2013-02-05 14:16:51 -05:00
def avatar_template
2021-04-27 16:28:15 -04:00
use_small_logo =
2021-01-18 12:09:07 -05:00
is_system_user? && SiteSetting . logo_small && SiteSetting . use_site_small_logo_as_system_avatar
if use_small_logo
2021-01-31 20:35:41 -05:00
Discourse . store . cdn_url ( SiteSetting . logo_small . url )
2021-01-08 08:40:00 -05:00
else
self . class . avatar_template ( username , uploaded_avatar_id )
end
2013-02-05 14:16:51 -05:00
end
# The following count methods are somewhat slow - definitely don't use them in a loop.
2013-03-06 02:52:24 -05:00
# They might need to be denormalized
2013-02-05 14:16:51 -05:00
def like_count
2013-02-28 08:08:56 -05:00
UserAction . where ( user_id : id , action_type : UserAction :: WAS_LIKED ) . count
2013-02-05 14:16:51 -05:00
end
2014-08-22 15:23:10 -04:00
def like_given_count
UserAction . where ( user_id : id , action_type : UserAction :: LIKE ) . count
2014-08-22 14:37:00 -04:00
end
2013-02-05 14:16:51 -05:00
def post_count
2014-07-28 13:17:37 -04:00
stat . post_count
2014-02-20 12:29:40 -05:00
end
2021-08-02 10:15:53 -04:00
def post_edits_count
stat . post_edits_count
end
def increment_post_edits_count
stat . increment! ( :post_edits_count )
end
2013-02-05 14:16:51 -05:00
def flags_given_count
2017-10-17 13:31:45 -04:00
PostAction . where (
user_id : id ,
post_action_type_id : PostActionType . flag_types_without_custom . values ,
) . count
2013-02-05 14:16:51 -05:00
end
2014-09-08 11:11:56 -04:00
def warnings_received_count
2017-04-15 00:11:02 -04:00
user_warnings . count
2014-09-08 11:11:56 -04:00
end
2023-10-17 21:38:17 -04:00
def flags_received_count
posts
. includes ( :post_actions )
. where ( " post_actions.post_action_type_id " = > PostActionType . flag_types_without_custom . values )
. count
end
2013-02-05 14:16:51 -05:00
def private_topics_count
topics_allowed . where ( archetype : Archetype . private_message ) . count
end
2013-12-19 13:45:55 -05:00
def posted_too_much_in_topic? ( topic_id )
2016-04-18 16:08:42 -04:00
# Does not apply to staff and non-new members...
return false if staff? || ( trust_level != TrustLevel [ 0 ] )
# ... your own topics or in private messages
topic = Topic . where ( id : topic_id ) . first
return false if topic . try ( :private_message? ) || ( topic . try ( :user_id ) == self . id )
2014-01-02 12:57:40 -05:00
2014-04-29 12:59:14 -04:00
last_action_in_topic = UserAction . last_action_in_topic ( id , topic_id )
since_reply = Post . where ( user_id : id , topic_id : topic_id )
since_reply = since_reply . where ( " id > ? " , last_action_in_topic ) if last_action_in_topic
( since_reply . count > = SiteSetting . newuser_max_replies_per_topic )
2013-12-19 13:45:55 -05:00
end
2018-12-14 05:04:18 -05:00
def delete_posts_in_batches ( guardian , batch_size = 20 )
2013-02-07 02:11:56 -05:00
raise Discourse :: InvalidAccess unless guardian . can_delete_all_posts? self
2013-02-07 10:45:24 -05:00
2019-01-03 12:03:01 -05:00
Reviewable . where ( created_by_id : id ) . delete_all
2015-04-24 16:04:44 -04:00
2018-12-14 05:04:18 -05:00
posts
. order ( " post_number desc " )
. limit ( batch_size )
2013-06-05 16:00:45 -04:00
. each { | p | PostDestroyer . new ( guardian . user , p ) . destroy }
2013-02-07 02:11:56 -05:00
end
2013-11-07 13:53:32 -05:00
def suspended?
2017-11-28 13:44:24 -05:00
! ! ( suspended_till && suspended_till > Time . zone . now )
2013-02-05 14:16:51 -05:00
end
2017-11-13 13:41:36 -05:00
def silenced?
2017-11-28 13:44:24 -05:00
! ! ( silenced_till && silenced_till > Time . zone . now )
2017-11-13 13:41:36 -05:00
end
def silenced_record
UserHistory . for ( self , :silence_user ) . order ( " id DESC " ) . first
end
def silence_reason
silenced_record . try ( :details ) if silenced?
end
def silenced_at
silenced_record . try ( :created_at ) if silenced?
end
2021-07-20 06:42:08 -04:00
def silenced_forever?
silenced_till > 100 . years . from_now
end
2013-11-07 13:53:32 -05:00
def suspend_record
UserHistory . for ( self , :suspend_user ) . order ( " id DESC " ) . first
2013-11-01 10:47:03 -04:00
end
2017-12-07 13:20:42 -05:00
def full_suspend_reason
2023-08-09 20:03:38 -04:00
suspend_record . try ( :details ) if suspended?
2017-12-07 13:20:42 -05:00
end
2013-11-07 13:53:32 -05:00
def suspend_reason
2017-12-07 13:20:42 -05:00
if details = full_suspend_reason
return details . split ( " \n " ) [ 0 ]
end
nil
2013-11-01 10:47:03 -04:00
end
2021-07-20 06:42:08 -04:00
def suspended_message
return nil unless suspended?
message = " login.suspended "
if suspend_reason
if suspended_forever?
message = " login.suspended_with_reason_forever "
else
message = " login.suspended_with_reason "
end
end
I18n . t (
message ,
date : I18n . l ( suspended_till , format : :date_only ) ,
reason : Rack :: Utils . escape_html ( suspend_reason ) ,
)
end
def suspended_forever?
suspended_till > 100 . years . from_now
end
2013-02-05 14:16:51 -05:00
# Use this helper to determine if the user has a particular trust level.
# Takes into account admin, etc.
2013-02-05 21:44:49 -05:00
def has_trust_level? ( level )
2016-05-29 23:38:04 -04:00
raise InvalidTrustLevel . new ( " Invalid trust level #{ level } " ) unless TrustLevel . valid? ( level )
2015-12-07 11:01:08 -05:00
admin? || moderator? || staged? || TrustLevel . compare ( trust_level , level )
2013-02-05 14:16:51 -05:00
end
2021-11-22 13:18:53 -05:00
def has_trust_level_or_staff? ( level )
return admin? if level . to_s == " admin "
return staff? if level . to_s == " staff "
has_trust_level? ( level . to_i )
end
2013-03-19 19:51:39 -04:00
# a touch faster than automatic
2013-03-31 12:51:13 -04:00
def admin?
2013-03-19 19:51:39 -04:00
admin
end
2013-02-05 14:16:51 -05:00
def guardian
Guardian . new ( self )
end
2018-04-02 12:44:04 -04:00
def username_format_validator
UsernameValidator . perform_validation ( self , " username " )
2013-02-07 18:23:41 -05:00
end
2013-02-11 11:18:26 -05:00
def email_confirmed?
2018-05-14 19:48:30 -04:00
email_tokens . where ( email : email , confirmed : true ) . present? || email_tokens . empty? ||
2019-11-07 12:26:28 -05:00
single_sign_on_record & . external_email & . downcase == email
2013-02-11 11:18:26 -05:00
end
2013-05-07 21:58:34 -04:00
def activate
2021-11-25 02:34:39 -05:00
email_token = self . email_tokens . create! ( email : self . email , scope : EmailToken . scopes [ :signup ] )
EmailToken . confirm ( email_token . token , scope : EmailToken . scopes [ :signup ] )
reload
2013-05-07 21:58:34 -04:00
end
2019-04-03 12:04:05 -04:00
def deactivate ( performed_by )
2017-09-13 03:33:59 -04:00
self . update! ( active : false )
2019-04-03 12:04:05 -04:00
if reviewable = ReviewableUser . pending . find_by ( target : self )
2021-06-15 11:35:45 -04:00
reviewable . perform ( performed_by , :delete_user )
2019-04-03 12:04:05 -04:00
end
2013-05-07 21:58:34 -04:00
end
2014-06-16 20:46:30 -04:00
def change_trust_level! ( level , opts = nil )
Promotion . new ( self ) . change_trust_level! ( level , opts )
end
2013-02-21 13:20:00 -05:00
def readable_name
2018-05-14 19:48:30 -04:00
name . present? && name != username ? " #{ name } ( #{ username } ) " : username
2013-02-21 13:20:00 -05:00
end
2014-04-16 06:22:21 -04:00
def badge_count
2019-12-30 06:19:59 -05:00
user_stat & . distinct_badge_count
2014-04-16 06:22:21 -04:00
end
2021-06-22 11:58:03 -04:00
def featured_user_badges ( limit = nil )
if limit . nil?
2020-01-14 09:26:49 -05:00
default_featured_user_badges
else
user_badges . grouped_with_count . where ( " featured_rank <= ? " , limit )
end
2014-04-16 06:11:11 -04:00
end
2018-05-03 09:41:41 -04:00
def self . count_by_signup_date ( start_date = nil , end_date = nil , group_id = nil )
result = self
if start_date && end_date
2018-05-10 23:30:21 -04:00
result = result . group ( " date(users.created_at) " )
result = result . where ( " users.created_at >= ? AND users.created_at <= ? " , start_date , end_date )
2018-06-05 03:29:17 -04:00
result = result . order ( " date(users.created_at) " )
2018-05-03 09:41:41 -04:00
end
2016-02-02 21:29:51 -05:00
if group_id
result = result . joins ( " INNER JOIN group_users ON group_users.user_id = users.id " )
result = result . where ( " group_users.group_id = ? " , group_id )
end
2018-04-26 08:49:41 -04:00
result . count
end
2018-05-03 09:41:41 -04:00
def self . count_by_first_post ( start_date = nil , end_date = nil )
result = joins ( " INNER JOIN user_stats AS us ON us.user_id = users.id " )
if start_date && end_date
2018-05-10 23:30:21 -04:00
result = result . group ( " date(us.first_post_created_at) " )
result =
result . where (
" us.first_post_created_at > ? AND us.first_post_created_at < ? " ,
start_date ,
end_date ,
)
result = result . order ( " date(us.first_post_created_at) " )
2018-05-03 09:41:41 -04:00
end
result . count
2013-03-07 11:07:59 -05:00
end
2013-04-29 02:33:24 -04:00
def secure_category_ids
2022-11-17 22:37:36 -05:00
cats =
if self . admin? && ! SiteSetting . suppress_secured_categories_from_admin
Category . unscoped . where ( read_restricted : true )
else
secure_categories . references ( :categories )
end
2013-09-10 00:29:02 -04:00
cats . pluck ( " categories.id " ) . sort
2013-04-29 02:33:24 -04:00
end
2013-05-10 16:58:23 -04:00
# Flag all posts from a user as spam
def flag_linked_posts_as_spam
2019-01-03 12:03:01 -05:00
results = [ ]
2016-04-25 17:03:17 -04:00
disagreed_flag_post_ids =
PostAction
. where ( post_action_type_id : PostActionType . types [ :spam ] )
. where . not ( disagreed_at : nil )
. pluck ( :post_id )
2015-10-16 15:16:44 -04:00
2016-04-25 17:03:17 -04:00
topic_links
. includes ( :post )
. where . not ( post_id : disagreed_flag_post_ids )
. each do | tl |
2019-07-12 06:04:16 -04:00
message =
I18n . t (
" flag_reason.spam_hosts " ,
base_path : Discourse . base_path ,
locale : SiteSetting . default_locale ,
)
2019-01-03 12:03:01 -05:00
results << PostActionCreator . create ( Discourse . system_user , tl . post , :spam , message : message )
2013-05-10 16:58:23 -04:00
end
2019-01-03 12:03:01 -05:00
results
2013-05-10 16:58:23 -04:00
end
2013-05-13 04:04:03 -04:00
2013-08-13 16:08:29 -04:00
def has_uploaded_avatar
uploaded_avatar . present?
end
2013-05-24 06:58:26 -04:00
2013-11-15 10:27:43 -05:00
def find_email
2022-02-17 20:12:51 -05:00
if last_sent_email_address . present? &&
EmailAddressValidator . valid_value? ( last_sent_email_address )
last_sent_email_address
2023-01-09 07:20:10 -05:00
else
2022-02-17 20:12:51 -05:00
email
2023-01-09 07:20:10 -05:00
end
2013-11-15 10:27:43 -05:00
end
2014-09-24 20:19:26 -04:00
def tl3_requirements
2014-09-05 01:20:39 -04:00
@lq || = TrustLevel3Requirements . new ( self )
2014-01-22 17:09:56 -05:00
end
2014-09-24 20:19:26 -04:00
def on_tl3_grace_period?
2018-10-11 15:11:40 -04:00
return true if SiteSetting . tl3_promotion_min_duration . to_i . days . ago . year < 2013
2014-09-13 16:55:26 -04:00
UserHistory
. for ( self , :auto_trust_level_change )
. where ( " created_at >= ? " , SiteSetting . tl3_promotion_min_duration . to_i . days . ago )
. where ( previous_value : TrustLevel [ 2 ] . to_s )
. where ( new_value : TrustLevel [ 3 ] . to_s )
. exists?
end
2014-05-22 03:37:02 -04:00
def refresh_avatar
2014-08-13 16:17:16 -04:00
return if @import_mode
2014-05-28 02:54:21 -04:00
avatar = user_avatar || create_user_avatar
2014-05-22 03:37:02 -04:00
2020-09-02 22:12:24 -04:00
if self . primary_email . present? && SiteSetting . automatically_download_gravatars? &&
! avatar . last_gravatar_download_attempt
2016-04-18 06:44:09 -04:00
Jobs . cancel_scheduled_job ( :update_gravatar , user_id : self . id , avatar_id : avatar . id )
Jobs . enqueue_in ( 1 . second , :update_gravatar , user_id : self . id , avatar_id : avatar . id )
2014-07-03 03:29:44 -04:00
end
2015-03-30 06:31:10 -04:00
2015-04-24 05:14:10 -04:00
# mark all the user's quoted posts as "needing a rebake"
2024-05-27 03:57:48 -04:00
Post . rebake_all_quoted_posts ( self . id ) if saved_change_to_uploaded_avatar_id?
2014-07-03 03:29:44 -04:00
end
2014-07-28 13:17:37 -04:00
def first_post_created_at
user_stat . try ( :first_post_created_at )
end
2014-09-25 01:50:54 -04:00
def associated_accounts
result = [ ]
2018-07-23 11:51:57 -04:00
Discourse . authenticators . each do | authenticator |
account_description = authenticator . description_for_user ( self )
unless account_description . empty?
result << { name : authenticator . name , description : account_description }
end
2014-09-25 01:50:54 -04:00
end
2018-07-23 11:51:57 -04:00
result
2014-09-25 01:50:54 -04:00
end
2018-09-06 18:02:47 -04:00
USER_FIELD_PREFIX || = " user_field_ "
2022-07-18 11:35:47 -04:00
def user_fields ( field_ids = nil )
2020-03-02 14:22:49 -05:00
field_ids = ( @all_user_field_ids || = UserField . pluck ( :id ) ) if field_ids . nil?
2023-06-21 13:35:24 -04:00
field_ids . map { | fid | [ fid . to_s , custom_fields [ " #{ USER_FIELD_PREFIX } #{ fid } " ] ] } . to_h
2014-09-26 14:48:34 -04:00
end
2022-07-14 18:36:54 -04:00
def validatable_user_fields_values
validatable_user_fields . values . join ( " " )
2022-05-16 09:21:33 -04:00
end
2021-03-29 07:03:19 -04:00
def set_user_field ( field_id , value )
custom_fields [ " #{ USER_FIELD_PREFIX } #{ field_id } " ] = value
end
2022-06-14 12:27:01 -04:00
def apply_watched_words
2022-07-14 18:36:54 -04:00
validatable_user_fields . each do | id , value |
2023-02-28 21:43:34 -05:00
field = WordWatcher . censor_text ( value )
field = WordWatcher . replace_text ( field )
set_user_field ( id , field )
2022-06-14 12:27:01 -04:00
end
end
2022-07-14 18:36:54 -04:00
def validatable_user_fields
2022-07-18 11:35:47 -04:00
# ignore multiselect fields since they are admin-set and thus not user generated content
@public_user_field_ids || =
UserField . public_fields . where . not ( field_type : " multiselect " ) . pluck ( :id )
2022-07-14 18:36:54 -04:00
2022-06-14 12:27:01 -04:00
user_fields ( @public_user_field_ids )
end
2015-02-19 12:11:07 -05:00
def number_of_deleted_posts
Post . with_deleted . where ( user_id : self . id ) . where . not ( deleted_at : nil ) . count
end
def number_of_flagged_posts
2023-10-17 21:38:17 -04:00
ReviewableFlaggedPost . where ( target_created_by : self . id ) . count
2015-02-19 12:11:07 -05:00
end
2020-03-16 08:52:08 -04:00
def number_of_rejected_posts
2023-07-28 12:16:23 -04:00
ReviewableQueuedPost . rejected . where ( target_created_by_id : self . id ) . count
2020-03-16 08:52:08 -04:00
end
2015-02-19 12:11:07 -05:00
def number_of_flags_given
PostAction
. where ( user_id : self . id )
. where ( disagreed_at : nil )
. where ( post_action_type_id : PostActionType . notify_flag_type_ids )
. count
end
def number_of_suspensions
UserHistory . for ( self , :suspend_user ) . count
end
2015-03-06 16:44:54 -05:00
def create_user_profile
2019-05-24 00:06:58 -04:00
UserProfile . create! ( user_id : id )
2015-03-06 16:44:54 -05:00
end
2018-07-18 06:57:43 -04:00
def set_random_avatar
2022-02-24 15:57:39 -05:00
if SiteSetting . selectable_avatars_mode != " disabled "
2020-10-13 09:17:06 -04:00
if upload = SiteSetting . selectable_avatars . sample
update_column ( :uploaded_avatar_id , upload . id )
UserAvatar . create! ( user_id : id , custom_upload_id : upload . id )
2018-07-18 06:57:43 -04:00
end
end
end
2015-04-07 22:29:43 -04:00
def anonymous?
SiteSetting . allow_anonymous_posting && trust_level > = 1 && ! ! anonymous_user_master
end
2016-04-26 13:08:19 -04:00
def is_singular_admin?
2017-03-14 02:33:06 -04:00
User . where ( admin : true ) . where . not ( id : id ) . human_users . blank?
2016-04-26 13:08:19 -04:00
end
2016-07-04 05:20:30 -04:00
def logged_out
2023-01-04 14:55:52 -05:00
MessageBus . publish " /logout/ #{ self . id } " , self . id , user_ids : [ self . id ]
2016-07-04 05:20:30 -04:00
DiscourseEvent . trigger ( :user_logged_out , self )
end
2017-06-01 04:19:42 -04:00
def logged_in
DiscourseEvent . trigger ( :user_logged_in , self )
DiscourseEvent . trigger ( :user_first_logged_in , self ) if ! self . seen_before?
end
2017-06-14 13:20:18 -04:00
def set_automatic_groups
2018-05-14 19:48:30 -04:00
return if ! active || staged || ! email_confirmed?
2017-06-14 13:20:18 -04:00
Group
. where ( automatic : false )
. where ( " LENGTH(COALESCE(automatic_membership_email_domains, '')) > 0 " )
. each do | group |
domains = group . automatic_membership_email_domains . gsub ( " . " , '\.' )
if email =~ Regexp . new ( " @( #{ domains } )$ " , true ) && ! group . users . include? ( self )
group . add ( self )
GroupActionLogger . new ( Discourse . system_user , group ) . log_add_user_to_group ( self )
2023-01-09 07:20:10 -05:00
end
2017-06-14 13:20:18 -04:00
end
2022-09-25 23:58:40 -04:00
@belonging_to_group_ids = nil
2017-06-14 13:20:18 -04:00
end
2017-04-26 14:47:36 -04:00
def email
2020-12-09 12:14:45 -05:00
primary_email & . email
2017-04-26 14:47:36 -04:00
end
2021-02-22 06:42:37 -05:00
# Shortcut to set the primary email of the user.
# Automatically removes any identical secondary emails.
2018-03-02 03:41:02 -05:00
def email = ( new_email )
2017-04-26 14:47:36 -04:00
if primary_email
2021-02-22 06:42:37 -05:00
primary_email . email = new_email
2017-04-26 14:47:36 -04:00
else
2021-02-22 06:42:37 -05:00
build_primary_email email : new_email , skip_validate_email : ! should_validate_email_address?
2017-04-26 14:47:36 -04:00
end
2021-02-22 06:42:37 -05:00
if secondary_match =
user_emails . detect { | ue |
! ue . primary && Email . downcase ( ue . email ) == Email . downcase ( new_email )
}
secondary_match . mark_for_destruction
primary_email . skip_validate_unique_email = true
end
2017-04-26 14:47:36 -04:00
end
2018-07-03 07:51:22 -04:00
def emails
self . user_emails . order ( " user_emails.primary DESC NULLS LAST " ) . pluck ( :email )
end
def secondary_emails
self . user_emails . secondary . pluck ( :email )
end
2020-06-10 12:11:49 -04:00
def unconfirmed_emails
self
. email_change_requests
. where . not ( change_state : EmailChangeRequest . states [ :complete ] )
. pluck ( :new_email )
end
2020-03-03 08:57:46 -05:00
RECENT_TIME_READ_THRESHOLD || = 60 . days
def self . preload_recent_time_read ( users )
times =
UserVisit
. where ( user_id : users . map ( & :id ) )
. where ( " visited_at >= ? " , RECENT_TIME_READ_THRESHOLD . ago )
. group ( :user_id )
. sum ( :time_read )
users . each { | u | u . preload_recent_time_read ( times [ u . id ] || 0 ) }
end
def preload_recent_time_read ( time )
@recent_time_read = time
end
2017-11-14 16:39:07 -05:00
def recent_time_read
2020-03-03 08:57:46 -05:00
@recent_time_read || =
self . user_visits . where ( " visited_at >= ? " , RECENT_TIME_READ_THRESHOLD . ago ) . sum ( :time_read )
2017-11-14 16:39:07 -05:00
end
2018-01-19 09:29:15 -05:00
def from_staged?
custom_fields [ User :: FROM_STAGED ]
end
2018-06-18 20:05:04 -04:00
def mature_staged?
2018-06-19 12:41:10 -04:00
from_staged? && self . created_at && self . created_at < 1 . day . ago
2018-06-18 20:05:04 -04:00
end
2018-09-20 22:06:08 -04:00
def next_best_title
group_titles_query = groups . where ( " groups.title <> '' " )
group_titles_query =
group_titles_query . order ( " groups.id = #{ primary_group_id } DESC " ) if primary_group_id
group_titles_query = group_titles_query . order ( " groups.primary_group DESC " ) . limit ( 1 )
2023-02-12 23:39:45 -05:00
if next_best_group_title = group_titles_query . pick ( :title )
2018-09-20 22:06:08 -04:00
return next_best_group_title
end
2023-02-12 23:39:45 -05:00
next_best_badge_title = badges . where ( allow_title : true ) . pick ( :name )
2018-09-20 22:06:08 -04:00
next_best_badge_title ? Badge . display_name ( next_best_badge_title ) : nil
end
2019-04-03 16:10:36 -04:00
def create_reviewable
return unless SiteSetting . must_approve_users? || SiteSetting . invite_only?
return if approved?
Jobs . enqueue ( :create_user_reviewable , user_id : self . id )
end
2019-08-10 06:02:12 -04:00
def has_more_posts_than? ( max_post_count )
return true if user_stat && ( user_stat . topic_count + user_stat . post_count ) > max_post_count
2020-04-01 16:10:17 -04:00
return true if max_post_count < 0
2019-08-10 06:02:12 -04:00
DB . query_single ( << ~ SQL , user_id : self . id ) . first > max_post_count
SELECT COUNT ( 1 )
FROM (
SELECT 1
FROM posts p
JOIN topics t ON ( p . topic_id = t . id )
WHERE p . user_id = :user_id AND
p . deleted_at IS NULL AND
t . deleted_at IS NULL AND
(
t . archetype < > 'private_message' OR
EXISTS (
SELECT 1
FROM topic_allowed_users a
WHERE a . topic_id = t . id AND a . user_id > 0 AND a . user_id < > :user_id
) OR
EXISTS (
SELECT 1
FROM topic_allowed_groups g
WHERE g . topic_id = p . topic_id
)
)
LIMIT #{max_post_count + 1}
) x
SQL
end
2019-10-01 22:08:41 -04:00
def create_or_fetch_secure_identifier
return secure_identifier if secure_identifier . present?
new_secure_identifier = SecureRandom . hex ( 20 )
self . update ( secure_identifier : new_secure_identifier )
new_secure_identifier
end
2023-10-03 14:59:28 -04:00
def second_factor_security_keys
security_keys . where ( factor_type : UserSecurityKey . factor_types [ :second_factor ] )
end
2019-10-01 22:08:41 -04:00
def second_factor_security_key_credential_ids
2023-10-03 14:59:28 -04:00
second_factor_security_keys . pluck ( :credential_id )
end
def passkey_credential_ids
security_keys . where ( factor_type : UserSecurityKey . factor_types [ :first_factor ] ) . pluck (
:credential_id ,
)
2019-10-01 22:08:41 -04:00
end
2020-06-05 12:31:58 -04:00
def encoded_username ( lower : false )
UrlHelper . encode_component ( lower ? username_lower : username )
end
2020-12-18 10:03:51 -05:00
def do_not_disturb?
active_do_not_disturb_timings . exists?
end
def active_do_not_disturb_timings
now = Time . zone . now
do_not_disturb_timings . where ( " starts_at <= ? AND ends_at > ? " , now , now )
end
2021-01-07 11:49:49 -05:00
def do_not_disturb_until
active_do_not_disturb_timings . maximum ( :ends_at )
end
2021-01-27 11:29:24 -05:00
def shelved_notifications
ShelvedNotification . joins ( :notification ) . where ( " notifications.user_id = ? " , self . id )
end
2021-11-21 22:38:49 -05:00
def allow_live_notifications?
seen_since? ( 30 . days . ago )
end
2021-12-02 08:42:23 -05:00
def username_equals_to? ( another_username )
username_lower == User . normalize_username ( another_username )
end
2024-05-27 15:30:19 -04:00
def relative_url
" #{ Discourse . base_path } /u/ #{ encoded_username } "
end
2022-04-13 09:52:56 -04:00
def full_url
" #{ Discourse . base_url } /u/ #{ encoded_username } "
end
def display_name
if SiteSetting . prioritize_username_in_ux?
username
else
name . presence || username
end
end
2022-05-27 05:15:14 -04:00
def clear_status!
user_status . destroy! if user_status
2022-05-30 05:41:53 -04:00
publish_user_status ( nil )
2022-05-27 05:15:14 -04:00
end
2022-08-09 06:54:33 -04:00
def set_status! ( description , emoji , ends_at = nil )
2023-02-06 09:56:28 -05:00
status = {
description : description ,
emoji : emoji ,
set_at : Time . zone . now ,
ends_at : ends_at ,
user_id : id ,
}
validate_status! ( status )
UserStatus . upsert ( status )
2022-05-30 05:41:53 -04:00
2023-02-06 09:56:28 -05:00
reload_user_status
2022-05-30 05:41:53 -04:00
publish_user_status ( user_status )
2022-05-27 05:15:14 -04:00
end
2022-07-05 11:12:22 -04:00
def has_status?
user_status && ! user_status . expired?
end
2023-02-27 07:11:01 -05:00
def new_new_view_enabled?
in_any_groups? ( SiteSetting . experimental_new_new_view_groups_map )
end
2024-02-23 13:08:15 -05:00
2023-07-04 01:08:29 -04:00
def watched_precedence_over_muted
if user_option . watched_precedence_over_muted . nil?
SiteSetting . watched_precedence_over_muted
else
user_option . watched_precedence_over_muted
end
end
2013-02-05 14:16:51 -05:00
protected
2014-07-22 21:42:24 -04:00
def badge_grant
BadgeGranter . queue_badge_grant ( Badge :: Trigger :: UserChange , user : self )
end
2015-06-05 13:50:06 -04:00
def expire_old_email_tokens
2017-08-31 00:06:56 -04:00
if saved_change_to_password_hash? && ! saved_change_to_id?
2015-06-05 13:50:06 -04:00
email_tokens . where ( " not expired " ) . update_all ( expired : true )
end
end
2016-12-21 21:13:14 -05:00
def index_search
2021-04-27 01:52:45 -04:00
# force is needed as user custom fields are updated using SQL and after_save callback is not triggered
SearchIndexer . index ( self , force : true )
2016-12-21 21:13:14 -05:00
end
2014-03-24 03:03:39 -04:00
def clear_global_notice_if_needed
2017-03-14 02:33:06 -04:00
return if id < 0
2017-02-13 10:53:45 -05:00
2014-03-24 03:03:39 -04:00
if admin && SiteSetting . has_login_hint
SiteSetting . has_login_hint = false
SiteSetting . global_notice = " "
end
end
2014-06-16 20:46:30 -04:00
def ensure_in_trust_level_group
Group . user_trust_level_change! ( id , trust_level )
end
2013-09-11 14:50:26 -04:00
def create_user_stat
2021-08-02 10:15:53 -04:00
UserStat . create! ( new_since : Time . zone . now , user_id : id )
2013-09-11 14:50:26 -04:00
end
2016-02-16 23:46:19 -05:00
def create_user_option
2019-05-24 00:06:58 -04:00
UserOption . create! ( user_id : id )
2016-02-16 23:46:19 -05:00
end
2013-06-06 10:40:10 -04:00
def create_email_token
2021-11-25 02:34:39 -05:00
email_tokens . create! ( email : email , scope : EmailToken . scopes [ :signup ] )
2013-06-06 10:40:10 -04:00
end
2013-02-05 14:16:51 -05:00
2013-06-06 10:40:10 -04:00
def ensure_password_is_hashed
if @raw_password
2023-04-11 05:16:28 -04:00
self . salt = SecureRandom . hex ( PASSWORD_SALT_LENGTH )
self . password_algorithm = TARGET_PASSWORD_ALGORITHM
self . password_hash = hash_password ( @raw_password , salt , password_algorithm )
2013-02-05 14:16:51 -05:00
end
2013-06-06 10:40:10 -04:00
end
2013-02-05 14:16:51 -05:00
2017-01-31 17:21:37 -05:00
def expire_tokens_if_password_changed
# NOTE: setting raw password is the only valid way of changing a password
# the password field in the DB is actually hashed, nobody should be amending direct
if @raw_password
# Association in model may be out-of-sync
UserAuthToken . where ( user_id : id ) . destroy_all
# We should not carry this around after save
@raw_password = nil
2017-11-30 23:19:24 -05:00
@password_required = false
2017-01-31 17:21:37 -05:00
end
end
2023-04-11 05:16:28 -04:00
def hash_password ( password , salt , algorithm )
2016-05-29 23:38:04 -04:00
raise StandardError . new ( " password is too long " ) if password . size > User . max_password_length
2023-04-11 05:16:28 -04:00
PasswordHasher . hash_password ( password : password , salt : salt , algorithm : algorithm )
2013-06-06 10:40:10 -04:00
end
2013-02-05 14:16:51 -05:00
2013-06-06 10:40:10 -04:00
def add_trust_level
2013-12-21 02:19:22 -05:00
# there is a possibility we did not load trust level column, skip it
2013-06-06 10:40:10 -04:00
return unless has_attribute? :trust_level
self . trust_level || = SiteSetting . default_trust_level
end
2013-02-05 14:16:51 -05:00
2019-04-23 06:22:47 -04:00
def update_usernames
self . username . unicode_normalize!
2013-06-06 10:40:10 -04:00
self . username_lower = username . downcase
end
2013-02-05 21:44:49 -05:00
2018-04-02 12:44:04 -04:00
USERNAME_EXISTS_SQL = << ~ SQL
2019-04-23 06:22:47 -04:00
( SELECT users . id AS id , true as is_user FROM users
WHERE users . username_lower = :username )
2018-04-02 12:44:04 -04:00
2019-04-23 06:22:47 -04:00
UNION ALL
2018-04-02 12:44:04 -04:00
2019-04-23 06:22:47 -04:00
( SELECT groups . id , false as is_user FROM groups
WHERE lower ( groups . name ) = :username )
2018-04-02 12:44:04 -04:00
SQL
2019-04-23 06:22:47 -04:00
def self . username_exists? ( username )
username = normalize_username ( username )
DB . exec ( User :: USERNAME_EXISTS_SQL , username : username ) > 0
2019-02-19 16:31:03 -05:00
end
2018-04-02 12:44:04 -04:00
def username_validator
username_format_validator ||
begin
2019-05-13 16:43:19 -04:00
if will_save_change_to_username?
existing =
DB . query ( USERNAME_EXISTS_SQL , username : self . class . normalize_username ( username ) )
user_id = existing . select { | u | u . is_user } . first & . id
same_user = user_id && user_id == self . id
2018-06-19 02:13:14 -04:00
2019-05-13 16:43:19 -04:00
errors . add ( :username , I18n . t ( :" user.username.unique " ) ) if existing . present? && ! same_user
2018-04-02 12:44:04 -04:00
2019-05-13 16:43:19 -04:00
if confirm_password? ( username ) || confirm_password? ( username . downcase )
errors . add ( :username , :same_as_password )
2023-01-09 07:20:10 -05:00
end
2019-05-13 16:43:19 -04:00
end
2018-04-02 12:44:04 -04:00
end
end
2019-05-13 16:43:19 -04:00
def name_validator
2020-02-03 14:12:45 -05:00
if name . present?
name_pw = name [ 0 ... User . max_password_length ]
if confirm_password? ( name_pw ) || confirm_password? ( name_pw . downcase )
errors . add ( :name , :same_as_password )
end
2019-05-13 16:43:19 -04:00
end
end
2015-08-21 14:39:21 -04:00
def set_default_categories_preferences
2016-06-14 10:45:47 -04:00
return if self . staged?
2015-08-21 14:39:21 -04:00
values = [ ]
2021-05-05 19:14:07 -04:00
# The following site settings are used to pre-populate default category
# tracking settings for a user:
#
# * default_categories_watching
# * default_categories_tracking
# * default_categories_watching_first_post
2022-06-19 23:49:33 -04:00
# * default_categories_normal
2021-05-05 19:14:07 -04:00
# * default_categories_muted
2022-06-19 23:49:33 -04:00
%w[ watching watching_first_post tracking normal muted ] . each do | setting |
2021-05-05 19:14:07 -04:00
category_ids = SiteSetting . get ( " default_categories_ #{ setting } " ) . split ( " | " ) . map ( & :to_i )
2015-08-21 14:39:21 -04:00
category_ids . each do | category_id |
2019-07-11 13:41:51 -04:00
next if category_id == 0
2021-05-05 19:14:07 -04:00
values << {
user_id : self . id ,
category_id : category_id ,
notification_level : CategoryUser . notification_levels [ setting . to_sym ] ,
}
2013-08-23 17:35:01 -04:00
end
end
2023-03-28 10:13:01 -04:00
CategoryUser . insert_all ( values ) if values . present?
2014-01-02 15:27:26 -05:00
end
2019-11-01 03:10:13 -04:00
def set_default_tags_preferences
return if self . staged?
values = [ ]
2021-05-05 19:14:07 -04:00
# The following site settings are used to pre-populate default tag
# tracking settings for a user:
#
# * default_tags_watching
# * default_tags_tracking
# * default_tags_watching_first_post
# * default_tags_muted
%w[ watching watching_first_post tracking muted ] . each do | setting |
tag_names = SiteSetting . get ( " default_tags_ #{ setting } " ) . split ( " | " )
2019-11-01 03:10:13 -04:00
now = Time . zone . now
Tag
. where ( name : tag_names )
. pluck ( :id )
. each do | tag_id |
2021-05-05 19:14:07 -04:00
values << {
user_id : self . id ,
tag_id : tag_id ,
notification_level : TagUser . notification_levels [ setting . to_sym ] ,
created_at : now ,
updated_at : now ,
}
2019-11-01 03:10:13 -04:00
end
end
TagUser . insert_all! ( values ) if values . present?
end
2014-12-03 00:36:25 -05:00
def self . purge_unactivated
2017-08-25 15:20:06 -04:00
return [ ] if SiteSetting . purge_unactivated_users_grace_period_days < = 0
2018-05-16 12:24:11 -04:00
destroyer = UserDestroyer . new ( Discourse . system_user )
User
2024-04-02 23:06:31 -04:00
. joins (
2024-04-17 19:53:43 -04:00
" LEFT JOIN user_histories ON user_histories.target_user_id = users.id AND action = #{ UserHistory . actions [ :deactivate_user ] } AND acting_user_id IS NOT NULL " ,
2024-04-02 23:06:31 -04:00
)
2018-05-16 12:24:11 -04:00
. where ( active : false )
2024-04-02 23:06:31 -04:00
. where ( " users.created_at < ? " , SiteSetting . purge_unactivated_users_grace_period_days . days . ago )
2018-05-16 12:24:11 -04:00
. where ( " NOT admin AND NOT moderator " )
2018-06-28 23:33:54 -04:00
. where (
" NOT EXISTS
2018-07-12 22:28:27 -04:00
( SELECT 1 FROM topic_allowed_users tu JOIN topics t ON t . id = tu . topic_id AND t . user_id > 0 WHERE tu . user_id = users . id LIMIT 1 )
2018-06-28 23:33:54 -04:00
" ,
)
2021-03-01 00:46:28 -05:00
. where (
" NOT EXISTS
( SELECT 1 FROM posts p WHERE p . user_id = users . id LIMIT 1 )
" ,
)
2024-04-02 23:06:31 -04:00
. where ( " user_histories.id IS NULL " )
2016-07-27 06:28:56 -04:00
. limit ( 200 )
2018-05-16 12:24:11 -04:00
. find_each do | user |
2014-08-19 13:46:40 -04:00
begin
2018-05-16 12:24:11 -04:00
destroyer . destroy ( user , context : I18n . t ( :purge_reason ) )
2021-03-01 00:46:28 -05:00
rescue Discourse :: InvalidAccess
2018-05-16 12:24:11 -04:00
# keep going
2023-01-09 07:20:10 -05:00
end
2014-08-19 13:46:40 -04:00
end
2014-08-13 14:13:41 -04:00
end
2021-07-08 03:46:21 -04:00
def match_primary_group_changes
2018-09-17 01:08:39 -04:00
return unless primary_group_id_changed?
2023-02-12 23:39:45 -05:00
self . title = primary_group & . title if Group . exists? ( id : primary_group_id_was , title : title )
2021-07-08 03:46:21 -04:00
self . flair_group_id = primary_group & . id if flair_group_id == primary_group_id_was
2018-09-17 01:08:39 -04:00
end
2022-08-09 12:22:39 -04:00
def self . first_login_admin_id
User
. where ( admin : true )
. human_users
. joins ( :user_auth_tokens )
. order ( " user_auth_tokens.created_at " )
2023-02-12 23:39:45 -05:00
. pick ( :id )
2022-08-09 12:22:39 -04:00
end
2013-07-07 06:40:35 -04:00
private
2022-12-05 13:39:10 -05:00
def set_default_sidebar_section_links ( update : false )
2022-10-26 18:38:50 -04:00
return if staged? || bot?
2023-06-14 19:31:28 -04:00
if SiteSetting . default_navigation_menu_categories . present?
categories_to_update = SiteSetting . default_navigation_menu_categories . split ( " | " )
2022-12-05 13:39:10 -05:00
2022-11-30 20:32:35 -05:00
SidebarSectionLinksUpdater . update_category_section_links (
self ,
2022-12-05 13:39:10 -05:00
category_ids : categories_to_update ,
2022-11-30 20:32:35 -05:00
)
2022-10-26 18:38:50 -04:00
end
2023-06-14 19:31:28 -04:00
if SiteSetting . tagging_enabled && SiteSetting . default_navigation_menu_tags . present?
2023-07-26 22:52:33 -04:00
SidebarSectionLinksUpdater . update_tag_section_links (
self ,
tag_ids : Tag . where ( name : SiteSetting . default_navigation_menu_tags . split ( " | " ) ) . pluck ( :id ) ,
)
2022-10-26 18:38:50 -04:00
end
end
2021-08-02 10:15:53 -04:00
def stat
user_stat || create_user_stat
end
2019-06-17 01:10:47 -04:00
def trigger_user_automatic_group_refresh
Group . user_trust_level_change! ( id , trust_level ) if ! staged
true
end
2019-05-27 12:12:26 -04:00
def trigger_user_updated_event
DiscourseEvent . trigger ( :user_updated , self )
true
end
2018-09-20 22:06:08 -04:00
def check_if_title_is_badged_granted
if title_changed? && ! new_record? && user_profile
2019-11-08 00:34:24 -05:00
badge_matching_title =
title &&
badges . find do | badge |
badge . allow_title? && ( badge . display_name == title || badge . name == title )
end
2023-03-08 07:37:20 -05:00
user_profile . update! ( granted_title_badge_id : badge_matching_title & . id )
2018-09-20 22:06:08 -04:00
end
end
2013-10-23 17:24:50 -04:00
def previous_visit_at_update_required? ( timestamp )
2014-01-16 22:38:08 -05:00
seen_before? && ( last_seen_at < ( timestamp - SiteSetting . previous_visit_timeout_hours . hours ) )
2013-10-23 17:24:50 -04:00
end
def update_previous_visit ( timestamp )
update_visit_record! ( timestamp . to_date )
update_column ( :previous_visit_at , last_seen_at ) if previous_visit_at_update_required? ( timestamp )
end
2023-06-25 23:01:59 -04:00
def change_display_name
Jobs . enqueue ( :change_display_name , user_id : id , old_name : name_before_last_save , new_name : name )
end
2017-03-16 03:36:27 -04:00
def trigger_user_created_event
2017-03-16 04:02:34 -04:00
DiscourseEvent . trigger ( :user_created , self )
2017-03-16 03:36:27 -04:00
true
end
2018-07-23 03:49:49 -04:00
def trigger_user_destroyed_event
DiscourseEvent . trigger ( :user_destroyed , self )
true
end
2017-10-25 01:02:18 -04:00
def set_skip_validate_email
self . primary_email . skip_validate_email = ! should_validate_email_address? if self . primary_email
2017-08-08 22:56:08 -04:00
true
end
2018-12-14 16:52:37 -05:00
def check_site_contact_username
if ( saved_change_to_admin? || saved_change_to_moderator? ) &&
self . username == SiteSetting . site_contact_username && ! staff?
SiteSetting . set_and_log ( :site_contact_username , SiteSetting . defaults [ :site_contact_username ] )
end
end
2018-08-31 00:46:22 -04:00
def self . ensure_consistency!
DB . exec << ~ SQL
UPDATE users
SET uploaded_avatar_id = NULL
WHERE uploaded_avatar_id IN (
SELECT u1 . uploaded_avatar_id FROM users u1
LEFT JOIN uploads up
ON u1 . uploaded_avatar_id = up . id
WHERE u1 . uploaded_avatar_id IS NOT NULL AND
up . id IS NULL
)
SQL
end
2023-02-06 09:56:28 -05:00
def validate_status! ( status )
UserStatus . new ( status ) . validate!
end
2024-05-14 20:06:58 -04:00
def refresh_user_directory
DirectoryItem . refresh!
end
2013-02-05 14:16:51 -05:00
end
2013-05-23 22:48:32 -04:00
# == Schema Information
#
# Table name: users
#
2017-11-23 15:55:44 -05:00
# id :integer not null, primary key
# username :string(60) not null
# created_at :datetime not null
# updated_at :datetime not null
2019-01-11 14:29:56 -05:00
# name :string
2017-11-23 15:55:44 -05:00
# seen_notification_id :integer default(0), not null
# last_posted_at :datetime
# password_hash :string(64)
# salt :string(32)
# active :boolean default(FALSE), not null
# username_lower :string(60) not null
# last_seen_at :datetime
# admin :boolean default(FALSE), not null
# last_emailed_at :datetime
# trust_level :integer not null
# approved :boolean default(FALSE), not null
# approved_by_id :integer
# approved_at :datetime
# previous_visit_at :datetime
# suspended_at :datetime
# suspended_till :datetime
# date_of_birth :date
# views :integer default(0), not null
# flag_level :integer default(0), not null
# ip_address :inet
# moderator :boolean default(FALSE)
2019-01-11 14:29:56 -05:00
# title :string
2017-11-23 15:55:44 -05:00
# uploaded_avatar_id :integer
2019-01-11 12:19:23 -05:00
# locale :string(10)
2019-01-11 14:29:56 -05:00
# primary_group_id :integer
2017-11-23 15:55:44 -05:00
# registration_ip_address :inet
# staged :boolean default(FALSE), not null
# first_seen_at :datetime
# silenced_till :datetime
2019-01-03 12:03:01 -05:00
# group_locked_trust_level :integer
2017-11-23 15:55:44 -05:00
# manual_locked_trust_level :integer
2019-10-17 01:57:53 -04:00
# secure_identifier :string
2021-07-08 03:46:21 -04:00
# flair_group_id :integer
2022-05-06 03:11:16 -04:00
# last_seen_reviewable_id :integer
2023-04-11 05:16:28 -04:00
# password_algorithm :string(64)
2013-05-23 22:48:32 -04:00
#
# Indexes
#
2023-02-02 11:35:04 -05:00
# idx_users_admin (id) WHERE admin
# idx_users_moderator (id) WHERE moderator
# index_users_on_last_posted_at (last_posted_at)
# index_users_on_last_seen_at (last_seen_at)
# index_users_on_name_trgm (name) USING gist
# index_users_on_secure_identifier (secure_identifier) UNIQUE
# index_users_on_uploaded_avatar_id (uploaded_avatar_id)
# index_users_on_username (username) UNIQUE
# index_users_on_username_lower (username_lower) UNIQUE
# index_users_on_username_lower_trgm (username_lower) USING gist
2013-05-23 22:48:32 -04:00
#