Class: User
Defined Under Namespace
Modules: NewTopicDuration
Constant Summary
collapse
- DEFAULT_FEATURED_BADGE_COUNT =
3
- PASSWORD_SALT_LENGTH =
16
- TARGET_PASSWORD_ALGORITHM =
"$pbkdf2-#{Rails.configuration.pbkdf2_algorithm}$i=#{Rails.configuration.pbkdf2_iterations},l=32$"
- EMAIL =
/([^@]+)@([^\.]+)/
- FROM_STAGED =
"from_staged"
- MAX_UNREAD_BACKLOG =
400
- MAX_UNREAD_NOTIFICATIONS =
PERF: This safeguard is in place to avoid situations where a user with enormous amounts of unread data can issue extremely expensive queries
99
SecondFactorManager::TOTP_ALLOWED_DRIFT_SECONDS
HasCustomFields::CUSTOM_FIELDS_MAX_ITEMS, HasCustomFields::CUSTOM_FIELDS_MAX_VALUE_LENGTH
Constants included
from Searchable
Searchable::PRIORITIES
Instance Attribute Summary collapse
Class Method Summary
collapse
-
.allowed_user_custom_fields(guardian) ⇒ Object
-
.avatar_template(username, uploaded_avatar_id) ⇒ Object
-
.color_index(username, length) ⇒ Object
-
.count_by_first_post(start_date = nil, end_date = nil) ⇒ Object
-
.count_by_signup_date(start_date = nil, end_date = nil, group_id = nil) ⇒ Object
-
.default_template(username) ⇒ Object
-
.editable_user_custom_fields(by_staff: false) ⇒ Object
-
.email_hash(email) ⇒ Object
-
.find_by_email(email, primary: false) ⇒ Object
-
.find_by_username(username) ⇒ Object
-
.find_by_username_or_email(username_or_email) ⇒ Object
-
.gravatar_template(email) ⇒ Object
-
.human_user_id?(user_id) ⇒ Boolean
-
.last_seen_redis_key(user_id, now) ⇒ Object
-
.letter_avatar_color(username) ⇒ Object
-
.max_password_length ⇒ Object
-
.max_unread_notifications ⇒ Object
-
.max_unread_notifications=(val) ⇒ Object
-
.new_from_params(params) ⇒ Object
-
.normalize_username(username) ⇒ Object
-
.preload_recent_time_read(users) ⇒ Object
-
.reserved_username?(username) ⇒ Boolean
-
.should_update_last_seen?(user_id, now = Time.zone.now) ⇒ Boolean
-
.suggest_name(string) ⇒ Object
-
.system_avatar_template(username) ⇒ Object
-
.update_ip_address!(user_id, new_ip:, old_ip:) ⇒ Object
-
.user_tips ⇒ Object
-
.username_available?(username, email = nil, allow_reserved_username: false) ⇒ Boolean
-
.username_hash(username) ⇒ Object
-
.username_length ⇒ Object
Instance Method Summary
collapse
-
#activate ⇒ Object
-
#active_do_not_disturb_timings ⇒ Object
-
#admin? ⇒ Boolean
a touch faster than automatic.
-
#all_unread_notifications_count ⇒ Object
-
#allow_live_notifications? ⇒ Boolean
-
#anonymous? ⇒ Boolean
-
#apply_watched_words ⇒ Object
-
#associated_accounts ⇒ Object
-
#avatar_template ⇒ Object
-
#avatar_template_url ⇒ Object
-
#badge_count ⇒ Object
-
#belonging_to_group_ids ⇒ Object
-
#bookmarks_of_type(type) ⇒ Object
-
#bot? ⇒ Boolean
-
#bump_last_seen_notification! ⇒ Object
-
#bump_last_seen_reviewable! ⇒ Object
-
#change_trust_level!(level, opts = nil) ⇒ Object
-
#change_username(new_username, actor = nil) ⇒ Object
-
#clear_last_seen_cache!(now = Time.zone.now) ⇒ Object
-
#clear_status! ⇒ Object
-
#confirm_password?(password) ⇒ Boolean
-
#create_or_fetch_secure_identifier ⇒ Object
-
#create_reviewable ⇒ Object
-
#create_user_profile ⇒ Object
-
#create_visit_record!(date, opts = {}) ⇒ Object
-
#created_topic_count ⇒ Object
(also: #topic_count)
-
#deactivate(performed_by) ⇒ Object
-
#delete_posts_in_batches(guardian, batch_size = 20) ⇒ Object
-
#display_name ⇒ Object
-
#do_not_disturb? ⇒ Boolean
-
#do_not_disturb_until ⇒ Object
-
#effective_locale ⇒ Object
-
#email ⇒ Object
-
#email=(new_email) ⇒ Object
Shortcut to set the primary email of the user.
-
#email_confirmed? ⇒ Boolean
-
#email_hash ⇒ Object
-
#emails ⇒ Object
-
#encoded_username(lower: false) ⇒ Object
-
#enqueue_member_welcome_message ⇒ Object
-
#enqueue_staff_welcome_message(role) ⇒ Object
-
#enqueue_tl2_promotion_message ⇒ Object
-
#enqueue_welcome_message(message_type) ⇒ Object
-
#experimental_search_menu_groups_enabled? ⇒ Boolean
-
#featured_user_badges(limit = nil) ⇒ Object
-
#find_email ⇒ Object
-
#first_post_created_at ⇒ Object
-
#flag_linked_posts_as_spam ⇒ Object
Flag all posts from a user as spam.
-
#flags_given_count ⇒ Object
-
#from_staged? ⇒ Boolean
-
#full_suspend_reason ⇒ Object
-
#full_url ⇒ Object
-
#group_granted_trust_level ⇒ Object
-
#grouped_unread_notifications ⇒ Object
-
#guardian ⇒ Object
-
#has_more_posts_than?(max_post_count) ⇒ Boolean
-
#has_password? ⇒ Boolean
-
#has_status? ⇒ Boolean
-
#has_trust_level?(level) ⇒ Boolean
Use this helper to determine if the user has a particular trust level.
-
#has_trust_level_or_staff?(level) ⇒ Boolean
-
#has_uploaded_avatar ⇒ Object
-
#human? ⇒ Boolean
-
#ignored_user_ids ⇒ Object
-
#in_any_groups?(group_ids) ⇒ Boolean
-
#increment_post_edits_count ⇒ Object
-
#invited_by ⇒ Object
-
#is_singular_admin? ⇒ Boolean
-
#is_system_user? ⇒ Boolean
-
#last_seen_redis_key(now) ⇒ Object
-
#like_count ⇒ Object
The following count methods are somewhat slow - definitely don’t use them in a loop.
-
#like_given_count ⇒ Object
-
#logged_in ⇒ Object
-
#logged_out ⇒ Object
-
#mature_staged? ⇒ Boolean
-
#muted_user_ids ⇒ Object
-
#new_new_view_enabled? ⇒ Boolean
-
#new_personal_messages_notifications_count ⇒ Object
-
#new_user? ⇒ Boolean
-
#new_user_posting_on_first_day? ⇒ Boolean
-
#next_best_title ⇒ Object
-
#number_of_deleted_posts ⇒ Object
-
#number_of_flagged_posts ⇒ Object
(also: #flags_received_count)
-
#number_of_flags_given ⇒ Object
-
#number_of_rejected_posts ⇒ Object
-
#number_of_suspensions ⇒ Object
-
#on_tl3_grace_period? ⇒ Boolean
-
#password ⇒ Object
-
#password=(password) ⇒ Object
-
#password_required! ⇒ Object
Indicate that this is NOT a passwordless account for the purposes of validation.
-
#password_required? ⇒ Boolean
-
#password_validation_required? ⇒ Boolean
-
#password_validator ⇒ Object
-
#post_count ⇒ Object
-
#post_edits_count ⇒ Object
-
#posted_too_much_in_topic?(topic_id) ⇒ Boolean
-
#preload_recent_time_read(time) ⇒ Object
-
#private_topics_count ⇒ Object
-
#publish_do_not_disturb(ends_at: nil) ⇒ Object
-
#publish_notifications_state ⇒ Object
-
#publish_reviewable_counts(extra_data = nil) ⇒ Object
-
#publish_user_status(status) ⇒ Object
-
#read_first_notification? ⇒ Boolean
-
#readable_name ⇒ Object
-
#recent_time_read ⇒ Object
-
#refresh_avatar ⇒ Object
-
#reload ⇒ Object
-
#reviewable_count ⇒ Object
-
#saw_notification_id(notification_id) ⇒ Object
-
#second_factor_security_key_credential_ids ⇒ Object
-
#secondary_emails ⇒ Object
-
#secure_category_ids ⇒ Object
-
#secured_sidebar_category_ids(user_guardian = nil) ⇒ Object
-
#seen_before? ⇒ Boolean
-
#seen_since?(datetime) ⇒ Boolean
-
#set_automatic_groups ⇒ Object
-
#set_random_avatar ⇒ Object
-
#set_status!(description, emoji, ends_at = nil) ⇒ Object
-
#set_user_field(field_id, value) ⇒ Object
-
#shelved_notifications ⇒ Object
-
#should_validate_email_address? ⇒ Boolean
-
#silence_reason ⇒ Object
-
#silenced? ⇒ Boolean
-
#silenced_at ⇒ Object
-
#silenced_forever? ⇒ Boolean
-
#silenced_record ⇒ Object
-
#small_avatar_url ⇒ Object
Don’t pass this up to the client - it’s meant for server side use This is used in - self oneboxes in open graph data - emails.
-
#suspend_reason ⇒ Object
-
#suspend_record ⇒ Object
-
#suspended? ⇒ Boolean
-
#suspended_forever? ⇒ Boolean
-
#suspended_message ⇒ Object
-
#sync_notification_channel_position ⇒ Object
tricky, we need our bus to be subscribed from the right spot.
-
#tl3_requirements ⇒ Object
-
#total_unread_notifications ⇒ Object
-
#unconfirmed_emails ⇒ Object
-
#unread_high_priority_notifications ⇒ Object
-
#unread_notifications ⇒ Object
-
#unread_notifications_of_priority(high_priority:) ⇒ Object
-
#unread_notifications_of_type(notification_type, since: nil) ⇒ Object
-
#unstage! ⇒ Object
-
#update_ip_address!(new_ip_address) ⇒ Object
-
#update_last_seen!(now = Time.zone.now, force: false) ⇒ Object
-
#update_posts_read!(num_posts, opts = {}) ⇒ Object
-
#update_timezone_if_missing(timezone) ⇒ Object
-
#update_visit_record!(date) ⇒ Object
-
#user_fields(field_ids = nil) ⇒ Object
-
#username_equals_to?(another_username) ⇒ Boolean
-
#username_format_validator ⇒ Object
-
#validatable_user_fields ⇒ Object
-
#validatable_user_fields_values ⇒ Object
-
#visible_groups ⇒ Object
-
#visible_sidebar_tags(user_guardian = nil) ⇒ Object
-
#visit_record_for(date) ⇒ Object
-
#warnings_received_count ⇒ Object
-
#watched_precedence_over_muted ⇒ Object
#enqueue_destroyed_web_hook
#authenticate_backup_code, #authenticate_second_factor, #authenticate_security_key, #authenticate_totp, #backup_codes_enabled?, #create_backup_codes, #create_totp, #generate_backup_codes, #get_totp_object, #has_any_second_factor_methods_enabled?, #has_multiple_second_factor_methods?, #hash_backup_code, #invalid_second_factor_authentication_result, #invalid_second_factor_method_result, #invalid_security_key_result, #invalid_totp_or_backup_code_result, #not_enabled_second_factor_method_result, #only_security_keys_enabled?, #only_totp_or_backup_codes_enabled?, #remaining_backup_codes, #require_rotp, #security_keys_enabled?, #totp_enabled?, #totp_or_backup_codes_enabled?, #totp_provisioning_uri, #valid_second_factor_method_for_user?
#clear_custom_fields, #create_singular, #custom_field_preloaded?, #custom_fields, #custom_fields=, #custom_fields_clean?, #custom_fields_preloaded?, #on_custom_fields_change, #save_custom_fields, #set_preloaded_custom_fields, #upsert_custom_fields
Methods included from Roleable
#grant_admin!, #grant_moderation!, #regular?, #revoke_admin!, #revoke_moderation!, #save_and_refresh_staff_groups!, #set_default_notification_levels, #set_permission, #staff?, #whisperer?
Instance Attribute Details
#custom_data ⇒ Object
Cache for user custom fields. Currently it is used to display quick search results
243
244
245
|
# File 'app/models/user.rb', line 243
def custom_data
@custom_data
end
|
#import_mode ⇒ Object
set to true to optimize creation and save for imports
240
241
242
|
# File 'app/models/user.rb', line 240
def import_mode
@import_mode
end
|
#notification_channel_position ⇒ Object
This is just used to pass some information into the serializer
237
238
239
|
# File 'app/models/user.rb', line 237
def notification_channel_position
@notification_channel_position
end
|
#send_welcome_message ⇒ Object
Whether we need to be sending a system message after creation
234
235
236
|
# File 'app/models/user.rb', line 234
def send_welcome_message
@send_welcome_message
end
|
#skip_email_validation ⇒ Object
Skip validating email, for example from a particular auth provider plugin
231
232
233
|
# File 'app/models/user.rb', line 231
def skip_email_validation
@skip_email_validation
end
|
Class Method Details
.allowed_user_custom_fields(guardian) ⇒ Object
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
|
# File 'app/models/user.rb', line 421
def self.allowed_user_custom_fields(guardian)
fields = []
fields.push(*DiscoursePluginRegistry.public_user_custom_fields)
if SiteSetting.public_user_custom_fields.present?
fields.push(*SiteSetting.public_user_custom_fields.split("|"))
end
if guardian.is_staff?
if SiteSetting.staff_user_custom_fields.present?
fields.push(*SiteSetting.staff_user_custom_fields.split("|"))
end
fields.push(*DiscoursePluginRegistry.staff_user_custom_fields)
end
fields.uniq
end
|
.avatar_template(username, uploaded_avatar_id) ⇒ Object
1105
1106
1107
1108
1109
1110
|
# File 'app/models/user.rb', line 1105
def self.avatar_template(username, uploaded_avatar_id)
username ||= ""
return default_template(username) if !uploaded_avatar_id
hostname = RailsMultisite::ConnectionManagement.current_hostname
UserAvatar.local_avatar_template(hostname, username.downcase, uploaded_avatar_id)
end
|
.color_index(username, length) ⇒ Object
1145
1146
1147
|
# File 'app/models/user.rb', line 1145
def self.color_index(username, length)
Digest::MD5.hexdigest(username)[0...15].to_i(16) % length
end
|
.count_by_first_post(start_date = nil, end_date = nil) ⇒ Object
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
|
# File 'app/models/user.rb', line 1372
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
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)")
end
result.count
end
|
.count_by_signup_date(start_date = nil, end_date = nil, group_id = nil) ⇒ Object
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
|
# File 'app/models/user.rb', line 1355
def self.count_by_signup_date(start_date = nil, end_date = nil, group_id = nil)
result = self
if start_date && end_date
result = result.group("date(users.created_at)")
result = result.where("users.created_at >= ? AND users.created_at <= ?", start_date, end_date)
result = result.order("date(users.created_at)")
end
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
result.count
end
|
.default_template(username) ⇒ Object
1096
1097
1098
1099
1100
1101
1102
1103
|
# File 'app/models/user.rb', line 1096
def self.default_template(username)
if SiteSetting.default_avatars.present?
urls = SiteSetting.default_avatars.split("\n")
return urls[username_hash(username) % urls.size] if urls.present?
end
system_avatar_template(username)
end
|
.editable_user_custom_fields(by_staff: false) ⇒ Object
413
414
415
416
417
418
419
|
# File 'app/models/user.rb', line 413
def self.editable_user_custom_fields(by_staff: false)
fields = []
fields.push(*DiscoursePluginRegistry.self_editable_user_custom_fields)
fields.push(*DiscoursePluginRegistry.staff_editable_user_custom_fields) if by_staff
fields.uniq
end
|
.email_hash(email) ⇒ Object
586
587
588
|
# File 'app/models/user.rb', line 586
def self.email_hash(email)
Digest::MD5.hexdigest(email.strip.downcase)
end
|
.find_by_email(email, primary: false) ⇒ Object
503
504
505
506
507
508
509
|
# File 'app/models/user.rb', line 503
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
end
|
.find_by_username(username) ⇒ Object
511
512
513
|
# File 'app/models/user.rb', line 511
def self.find_by_username(username)
find_by(username_lower: normalize_username(username))
end
|
.find_by_username_or_email(username_or_email) ⇒ Object
495
496
497
498
499
500
501
|
# File 'app/models/user.rb', line 495
def self.find_by_username_or_email(username_or_email)
if username_or_email.include?("@")
find_by_email(username_or_email)
else
find_by_username(username_or_email)
end
end
|
.gravatar_template(email) ⇒ Object
1071
1072
1073
|
# File 'app/models/user.rb', line 1071
def self.gravatar_template(email)
"//#{SiteSetting.gravatar_base_url}/avatar/#{self.email_hash(email)}.png?s={size}&r=pg&d=identicon"
end
|
.human_user_id?(user_id) ⇒ Boolean
441
442
443
|
# File 'app/models/user.rb', line 441
def self.human_user_id?(user_id)
user_id > 0
end
|
.last_seen_redis_key(user_id, now) ⇒ Object
1034
1035
1036
1037
|
# File 'app/models/user.rb', line 1034
def self.last_seen_redis_key(user_id, now)
now_date = now.to_date
"user:#{user_id}:#{now_date}"
end
|
.letter_avatar_color(username) ⇒ Object
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
|
# File 'app/models/user.rb', line 1130
def self.letter_avatar_color(username)
username ||= ""
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
|
.max_password_length ⇒ Object
372
373
374
|
# File 'app/models/user.rb', line 372
def self.max_password_length
200
end
|
.max_unread_notifications ⇒ Object
692
693
694
|
# File 'app/models/user.rb', line 692
def self.max_unread_notifications
@max_unread_notifications ||= MAX_UNREAD_NOTIFICATIONS
end
|
.max_unread_notifications=(val) ⇒ Object
696
697
698
|
# File 'app/models/user.rb', line 696
def self.max_unread_notifications=(val)
@max_unread_notifications = val
end
|
.new_from_params(params) ⇒ Object
468
469
470
471
472
473
474
475
|
# File 'app/models/user.rb', line 468
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
|
.normalize_username(username) ⇒ Object
380
381
382
|
# File 'app/models/user.rb', line 380
def self.normalize_username(username)
username.to_s.unicode_normalize.downcase if username.present?
end
|
.preload_recent_time_read(users) ⇒ Object
1639
1640
1641
1642
1643
1644
1645
1646
1647
|
# File 'app/models/user.rb', line 1639
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
|
.reserved_username?(username) ⇒ Boolean
401
402
403
404
405
406
407
408
409
410
411
|
# File 'app/models/user.rb', line 401
def self.reserved_username?(username)
username = normalize_username(username)
return true if SiteSetting.here_mention == username
SiteSetting
.reserved_usernames
.unicode_normalize
.split("|")
.any? { |reserved| username.match?(/\A#{Regexp.escape(reserved).gsub('\*', ".*")}\z/) }
end
|
.should_update_last_seen?(user_id, now = Time.zone.now) ⇒ Boolean
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
|
# File 'app/models/user.rb', line 1047
def self.should_update_last_seen?(user_id, now = Time.zone.now)
return true if SiteSetting.active_user_rate_limit_secs <= 0
Discourse.redis.set(
last_seen_redis_key(user_id, now),
"1",
nx: true,
ex: SiteSetting.active_user_rate_limit_secs,
)
end
|
.suggest_name(string) ⇒ Object
490
491
492
493
|
# File 'app/models/user.rb', line 490
def self.suggest_name(string)
return "" if string.blank?
(string[/\A[^@]+/].presence || string[/[^@]+\z/]).tr(".", " ").titleize
end
|
.system_avatar_template(username) ⇒ Object
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
|
# File 'app/models/user.rb', line 1112
def self.system_avatar_template(username)
normalized_username = normalize_username(username)
if SiteSetting.external_system_avatars_enabled
url = SiteSetting.external_system_avatars_url.dup
url = +"#{Discourse.base_path}#{url}" unless url =~ %r{\Ahttps?://}
url.gsub! "{color}", letter_avatar_color(normalized_username)
url.gsub! "{username}", UrlHelper.encode_component(username)
url.gsub! "{first_letter}",
UrlHelper.encode_component(normalized_username.grapheme_clusters.first)
url.gsub! "{hostname}", Discourse.current_hostname
url
else
"#{Discourse.base_path}/letter_avatar/#{normalized_username}/{size}/#{LetterAvatar.version}.png"
end
end
|
.update_ip_address!(user_id, new_ip:, old_ip:) ⇒ Object
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
|
# File 'app/models/user.rb', line 998
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
if SiteSetting.keep_old_ip_address_count > 0
DB.exec(<<~SQL, user_id: user_id, ip_address: new_ip, current_timestamp: Time.zone.now)
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
DB.exec(<<~SQL, user_id: user_id, offset: SiteSetting.keep_old_ip_address_count)
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
end
end
|
.user_tips ⇒ Object
342
343
344
345
346
347
348
349
350
351
352
|
# File 'app/models/user.rb', line 342
def self.user_tips
@user_tips ||=
Enum.new(
first_notification: 1,
topic_timeline: 2,
post_menu: 3,
topic_notification_levels: 4,
suggested_topics: 5,
admin_guide: 6,
)
end
|
.username_available?(username, email = nil, allow_reserved_username: false) ⇒ Boolean
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
|
# File 'app/models/user.rb', line 384
def self.username_available?(username, email = nil, allow_reserved_username: false)
lower = normalize_username(username)
return false if !allow_reserved_username && reserved_username?(lower)
return true if !username_exists?(lower)
email.present? &&
User.joins(:user_emails).exists?(
staged: true,
username_lower: lower,
user_emails: {
primary: true,
email: email,
},
)
end
|
.username_hash(username) ⇒ Object
1087
1088
1089
1090
1091
1092
1093
1094
|
# File 'app/models/user.rb', line 1087
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
|
.username_length ⇒ Object
376
377
378
|
# File 'app/models/user.rb', line 376
def self.username_length
SiteSetting.min_username_length.to_i..SiteSetting.max_username_length.to_i
end
|
Instance Method Details
#activate ⇒ Object
1321
1322
1323
1324
1325
|
# File 'app/models/user.rb', line 1321
def activate
email_token = self.email_tokens.create!(email: self.email, scope: EmailToken.scopes[:signup])
EmailToken.confirm(email_token.token, scope: EmailToken.scopes[:signup])
reload
end
|
#active_do_not_disturb_timings ⇒ Object
1740
1741
1742
1743
|
# File 'app/models/user.rb', line 1740
def active_do_not_disturb_timings
now = Time.zone.now
do_not_disturb_timings.where("starts_at <= ? AND ends_at > ?", now, now)
end
|
#admin? ⇒ Boolean
a touch faster than automatic
1304
1305
1306
|
# File 'app/models/user.rb', line 1304
def admin?
admin
end
|
#all_unread_notifications_count ⇒ Object
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
|
# File 'app/models/user.rb', line 729
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,
)[
0
].to_i
end
end
|
#allow_live_notifications? ⇒ Boolean
1753
1754
1755
|
# File 'app/models/user.rb', line 1753
def allow_live_notifications?
seen_since?(30.days.ago)
end
|
#anonymous? ⇒ Boolean
1561
1562
1563
|
# File 'app/models/user.rb', line 1561
def anonymous?
SiteSetting.allow_anonymous_posting && trust_level >= 1 && !!anonymous_user_master
end
|
#apply_watched_words ⇒ Object
1502
1503
1504
1505
1506
1507
1508
|
# File 'app/models/user.rb', line 1502
def apply_watched_words
validatable_user_fields.each do |id, value|
field = WordWatcher.censor_text(value)
field = WordWatcher.replace_text(field)
set_user_field(id, field)
end
end
|
#associated_accounts ⇒ Object
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
|
# File 'app/models/user.rb', line 1473
def associated_accounts
result = []
Discourse.authenticators.each do |authenticator|
account_description = authenticator.description_for_user(self)
unless account_description.empty?
result << { name: authenticator.name, description: account_description }
end
end
result
end
|
#avatar_template ⇒ Object
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
|
# File 'app/models/user.rb', line 1153
def avatar_template
use_small_logo =
is_system_user? && SiteSetting.logo_small && SiteSetting.use_site_small_logo_as_system_avatar
if use_small_logo
Discourse.store.cdn_url(SiteSetting.logo_small.url)
else
self.class.avatar_template(username, uploaded_avatar_id)
end
end
|
#avatar_template_url ⇒ Object
#badge_count ⇒ Object
1343
1344
1345
|
# File 'app/models/user.rb', line 1343
def badge_count
user_stat&.distinct_badge_count
end
|
#belonging_to_group_ids ⇒ Object
519
520
521
|
# File 'app/models/user.rb', line 519
def belonging_to_group_ids
@belonging_to_group_ids ||= group_users.pluck(:group_id)
end
|
#bookmarks_of_type(type) ⇒ Object
461
462
463
|
# File 'app/models/user.rb', line 461
def bookmarks_of_type(type)
bookmarks.where(bookmarkable_type: type)
end
|
#bot? ⇒ Boolean
449
450
451
|
# File 'app/models/user.rb', line 449
def bot?
!self.human?
end
|
#bump_last_seen_notification! ⇒ Object
776
777
778
779
780
781
782
783
784
785
|
# File 'app/models/user.rb', line 776
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
|
#bump_last_seen_reviewable! ⇒ Object
787
788
789
790
791
792
793
794
795
796
797
|
# File 'app/models/user.rb', line 787
def bump_last_seen_reviewable!
query = Reviewable.unseen_list_for(self, preload: false)
query = query.where("reviewables.id > ?", last_seen_reviewable_id) if last_seen_reviewable_id
max_reviewable_id = query.maximum(:id)
if max_reviewable_id
update!(last_seen_reviewable_id: max_reviewable_id)
publish_reviewable_counts
end
end
|
#change_trust_level!(level, opts = nil) ⇒ Object
#change_username(new_username, actor = nil) ⇒ Object
560
561
562
|
# File 'app/models/user.rb', line 560
def change_username(new_username, actor = nil)
UsernameChanger.change(self, new_username, actor)
end
|
#clear_last_seen_cache!(now = Time.zone.now) ⇒ Object
1043
1044
1045
|
# File 'app/models/user.rb', line 1043
def clear_last_seen_cache!(now = Time.zone.now)
Discourse.redis.del(last_seen_redis_key(now))
end
|
#clear_status! ⇒ Object
1773
1774
1775
1776
|
# File 'app/models/user.rb', line 1773
def clear_status!
user_status.destroy! if user_status
publish_user_status(nil)
end
|
#confirm_password?(password) ⇒ Boolean
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
|
# File 'app/models/user.rb', line 914
def confirm_password?(password)
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
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
end
|
#create_or_fetch_secure_identifier ⇒ Object
1718
1719
1720
1721
1722
1723
|
# File 'app/models/user.rb', line 1718
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
|
#create_reviewable ⇒ Object
1680
1681
1682
1683
1684
1685
|
# File 'app/models/user.rb', line 1680
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
|
#create_user_profile ⇒ Object
1548
1549
1550
|
# File 'app/models/user.rb', line 1548
def create_user_profile
UserProfile.create!(user_id: id)
end
|
#create_visit_record!(date, opts = {}) ⇒ Object
952
953
954
955
956
957
958
959
|
# File 'app/models/user.rb', line 952
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
|
#created_topic_count ⇒ Object
Also known as:
topic_count
564
565
566
|
# File 'app/models/user.rb', line 564
def created_topic_count
stat.topic_count
end
|
#deactivate(performed_by) ⇒ Object
1327
1328
1329
1330
1331
1332
1333
|
# File 'app/models/user.rb', line 1327
def deactivate(performed_by)
self.update!(active: false)
if reviewable = ReviewableUser.pending.find_by(target: self)
reviewable.perform(performed_by, :delete_user)
end
end
|
#delete_posts_in_batches(guardian, batch_size = 20) ⇒ Object
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
|
# File 'app/models/user.rb', line 1215
def delete_posts_in_batches(guardian, batch_size = 20)
raise Discourse::InvalidAccess unless guardian.can_delete_all_posts? self
Reviewable.where(created_by_id: id).delete_all
posts
.order("post_number desc")
.limit(batch_size)
.each { |p| PostDestroyer.new(guardian.user, p).destroy }
end
|
#display_name ⇒ Object
1765
1766
1767
1768
1769
1770
1771
|
# File 'app/models/user.rb', line 1765
def display_name
if SiteSetting.prioritize_username_in_ux?
username
else
name.presence || username
end
end
|
#do_not_disturb? ⇒ Boolean
1736
1737
1738
|
# File 'app/models/user.rb', line 1736
def do_not_disturb?
active_do_not_disturb_timings.exists?
end
|
#do_not_disturb_until ⇒ Object
1745
1746
1747
|
# File 'app/models/user.rb', line 1745
def do_not_disturb_until
active_do_not_disturb_timings.maximum(:ends_at)
end
|
#effective_locale ⇒ Object
453
454
455
456
457
458
459
|
# File 'app/models/user.rb', line 453
def effective_locale
if SiteSetting.allow_user_locale && self.locale.present?
self.locale
else
SiteSetting.default_locale
end
end
|
#email ⇒ Object
1598
1599
1600
|
# File 'app/models/user.rb', line 1598
def email
primary_email&.email
end
|
#email=(new_email) ⇒ Object
Shortcut to set the primary email of the user. Automatically removes any identical secondary emails.
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
|
# File 'app/models/user.rb', line 1604
def email=(new_email)
if primary_email
primary_email.email = new_email
else
build_primary_email email: new_email, skip_validate_email: !should_validate_email_address?
end
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
new_email
end
|
#email_confirmed? ⇒ Boolean
1316
1317
1318
1319
|
# File 'app/models/user.rb', line 1316
def email_confirmed?
email_tokens.where(email: email, confirmed: true).present? || email_tokens.empty? ||
single_sign_on_record&.external_email&.downcase == email
end
|
#email_hash ⇒ Object
590
591
592
|
# File 'app/models/user.rb', line 590
def email_hash
User.email_hash(email)
end
|
#emails ⇒ Object
1622
1623
1624
|
# File 'app/models/user.rb', line 1622
def emails
self.user_emails.order("user_emails.primary DESC NULLS LAST").pluck(:email)
end
|
#encoded_username(lower: false) ⇒ Object
1732
1733
1734
|
# File 'app/models/user.rb', line 1732
def encoded_username(lower: false)
UrlHelper.encode_component(lower ? username_lower : username)
end
|
#enqueue_member_welcome_message ⇒ Object
536
537
538
539
|
# File 'app/models/user.rb', line 536
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
|
#enqueue_staff_welcome_message(role) ⇒ Object
546
547
548
549
550
551
552
553
554
555
556
557
558
|
# File 'app/models/user.rb', line 546
def enqueue_staff_welcome_message(role)
return unless staff?
return if role == :admin && User.real.where(admin: true).count == 1
Jobs.enqueue(
:send_system_message,
user_id: id,
message_type: "welcome_staff",
message_options: {
role: role.to_s,
},
)
end
|
541
542
543
544
|
# File 'app/models/user.rb', line 541
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
|
#enqueue_welcome_message(message_type) ⇒ Object
531
532
533
534
|
# File 'app/models/user.rb', line 531
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
|
1801
1802
1803
|
# File 'app/models/user.rb', line 1801
def
in_any_groups?(SiteSetting.)
end
|
#featured_user_badges(limit = nil) ⇒ Object
1347
1348
1349
1350
1351
1352
1353
|
# File 'app/models/user.rb', line 1347
def featured_user_badges(limit = nil)
if limit.nil?
default_featured_user_badges
else
user_badges.grouped_with_count.where("featured_rank <= ?", limit)
end
end
|
#find_email ⇒ Object
1430
1431
1432
1433
1434
1435
1436
1437
|
# File 'app/models/user.rb', line 1430
def find_email
if last_sent_email_address.present? &&
EmailAddressValidator.valid_value?(last_sent_email_address)
last_sent_email_address
else
email
end
end
|
#first_post_created_at ⇒ Object
1469
1470
1471
|
# File 'app/models/user.rb', line 1469
def first_post_created_at
user_stat.try(:first_post_created_at)
end
|
#flag_linked_posts_as_spam ⇒ Object
Flag all posts from a user as spam
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
|
# File 'app/models/user.rb', line 1401
def flag_linked_posts_as_spam
results = []
disagreed_flag_post_ids =
PostAction
.where(post_action_type_id: PostActionType.types[:spam])
.where.not(disagreed_at: nil)
.pluck(:post_id)
topic_links
.includes(:post)
.where.not(post_id: disagreed_flag_post_ids)
.each do |tl|
message =
I18n.t(
"flag_reason.spam_hosts",
base_path: Discourse.base_path,
locale: SiteSetting.default_locale,
)
results << PostActionCreator.create(Discourse.system_user, tl.post, :spam, message: message)
end
results
end
|
#flags_given_count ⇒ Object
#from_staged? ⇒ Boolean
1658
1659
1660
|
# File 'app/models/user.rb', line 1658
def from_staged?
custom_fields[User::FROM_STAGED]
end
|
#full_suspend_reason ⇒ Object
1254
1255
1256
|
# File 'app/models/user.rb', line 1254
def full_suspend_reason
suspend_record.try(:details) if suspended?
end
|
#full_url ⇒ Object
1761
1762
1763
|
# File 'app/models/user.rb', line 1761
def full_url
"#{Discourse.base_url}/u/#{encoded_username}"
end
|
#group_granted_trust_level ⇒ Object
523
524
525
|
# File 'app/models/user.rb', line 523
def group_granted_trust_level
GroupUser.where(user_id: id).includes(:group).maximum("groups.grant_trust_level")
end
|
#grouped_unread_notifications ⇒ Object
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
|
# File 'app/models/user.rb', line 649
def grouped_unread_notifications
results = DB.query(<<~SQL, user_id: self.id, limit: MAX_UNREAD_BACKLOG)
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
|
#guardian ⇒ Object
1308
1309
1310
|
# File 'app/models/user.rb', line 1308
def guardian
Guardian.new(self)
end
|
#has_more_posts_than?(max_post_count) ⇒ Boolean
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
|
# File 'app/models/user.rb', line 1687
def has_more_posts_than?(max_post_count)
return true if user_stat && (user_stat.topic_count + user_stat.post_count) > max_post_count
return true if max_post_count < 0
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
|
#has_password? ⇒ Boolean
906
907
908
|
# File 'app/models/user.rb', line 906
def has_password?
password_hash.present?
end
|
#has_status? ⇒ Boolean
1793
1794
1795
|
# File 'app/models/user.rb', line 1793
def has_status?
user_status && !user_status.expired?
end
|
#has_trust_level?(level) ⇒ Boolean
Use this helper to determine if the user has a particular trust level. Takes into account admin, etc.
1291
1292
1293
1294
1295
|
# File 'app/models/user.rb', line 1291
def has_trust_level?(level)
raise InvalidTrustLevel.new("Invalid trust level #{level}") unless TrustLevel.valid?(level)
admin? || moderator? || staged? || TrustLevel.compare(trust_level, level)
end
|
#has_trust_level_or_staff?(level) ⇒ Boolean
1297
1298
1299
1300
1301
|
# File 'app/models/user.rb', line 1297
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
|
#has_uploaded_avatar ⇒ Object
1426
1427
1428
|
# File 'app/models/user.rb', line 1426
def has_uploaded_avatar
uploaded_avatar.present?
end
|
#human? ⇒ Boolean
445
446
447
|
# File 'app/models/user.rb', line 445
def human?
User.human_user_id?(self.id)
end
|
#ignored_user_ids ⇒ Object
607
608
609
|
# File 'app/models/user.rb', line 607
def ignored_user_ids
@ignored_user_ids ||= ignored_users.pluck(:id)
end
|
#in_any_groups?(group_ids) ⇒ Boolean
515
516
517
|
# File 'app/models/user.rb', line 515
def in_any_groups?(group_ids)
group_ids.include?(Group::AUTO_GROUPS[:everyone]) || (group_ids & belonging_to_group_ids).any?
end
|
#increment_post_edits_count ⇒ Object
1182
1183
1184
|
# File 'app/models/user.rb', line 1182
def increment_post_edits_count
stat.increment!(:post_edits_count)
end
|
#invited_by ⇒ Object
576
577
578
579
580
|
# File 'app/models/user.rb', line 576
def invited_by
used_invite =
Invite.with_deleted.joins(:invited_users).where("invited_users.user_id = ?", self.id).first
used_invite.try(:invited_by)
end
|
#is_singular_admin? ⇒ Boolean
1565
1566
1567
|
# File 'app/models/user.rb', line 1565
def is_singular_admin?
User.where(admin: true).where.not(id: id).human_users.blank?
end
|
#is_system_user? ⇒ Boolean
1149
1150
1151
|
# File 'app/models/user.rb', line 1149
def is_system_user?
id == Discourse::SYSTEM_USER_ID
end
|
#last_seen_redis_key(now) ⇒ Object
1039
1040
1041
|
# File 'app/models/user.rb', line 1039
def last_seen_redis_key(now)
User.last_seen_redis_key(id, now)
end
|
#like_count ⇒ Object
The following count methods are somewhat slow - definitely don’t use them in a loop. They might need to be denormalized
#like_given_count ⇒ Object
1170
1171
1172
|
# File 'app/models/user.rb', line 1170
def like_given_count
UserAction.where(user_id: id, action_type: UserAction::LIKE).count
end
|
#logged_out ⇒ Object
1569
1570
1571
1572
|
# File 'app/models/user.rb', line 1569
def logged_out
MessageBus.publish "/logout/#{self.id}", self.id, user_ids: [self.id]
DiscourseEvent.trigger(:user_logged_out, self)
end
|
#mature_staged? ⇒ Boolean
1662
1663
1664
|
# File 'app/models/user.rb', line 1662
def mature_staged?
from_staged? && self.created_at && self.created_at < 1.day.ago
end
|
#muted_user_ids ⇒ Object
611
612
613
|
# File 'app/models/user.rb', line 611
def muted_user_ids
@muted_user_ids ||= muted_users.pluck(:id)
end
|
#new_new_view_enabled? ⇒ Boolean
1797
1798
1799
|
# File 'app/models/user.rb', line 1797
def new_new_view_enabled?
in_any_groups?(SiteSetting.experimental_new_new_view_groups_map)
end
|
#new_personal_messages_notifications_count ⇒ Object
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
|
# File 'app/models/user.rb', line 670
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
|
#new_user? ⇒ Boolean
939
940
941
942
|
# File 'app/models/user.rb', line 939
def new_user?
(created_at >= 24.hours.ago || trust_level == TrustLevel[0]) && trust_level < TrustLevel[2] &&
!staff?
end
|
#new_user_posting_on_first_day? ⇒ Boolean
931
932
933
934
935
936
937
|
# File 'app/models/user.rb', line 931
def new_user_posting_on_first_day?
!staff? && trust_level < TrustLevel[2] &&
(
trust_level == TrustLevel[0] || self.first_post_created_at.nil? ||
self.first_post_created_at >= 24.hours.ago
)
end
|
#next_best_title ⇒ Object
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
|
# File 'app/models/user.rb', line 1666
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)
if next_best_group_title = group_titles_query.pick(:title)
return next_best_group_title
end
next_best_badge_title = badges.where(allow_title: true).pick(:name)
next_best_badge_title ? Badge.display_name(next_best_badge_title) : nil
end
|
#number_of_deleted_posts ⇒ Object
1518
1519
1520
|
# File 'app/models/user.rb', line 1518
def number_of_deleted_posts
Post.with_deleted.where(user_id: self.id).where.not(deleted_at: nil).count
end
|
#number_of_flagged_posts ⇒ Object
Also known as:
flags_received_count
1522
1523
1524
1525
1526
1527
1528
1529
|
# File 'app/models/user.rb', line 1522
def number_of_flagged_posts
posts
.with_deleted
.includes(:post_actions)
.where("post_actions.post_action_type_id" => PostActionType.flag_types_without_custom.values)
.where("post_actions.agreed_at IS NOT NULL")
.count
end
|
#number_of_flags_given ⇒ Object
1536
1537
1538
1539
1540
1541
1542
|
# File 'app/models/user.rb', line 1536
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
|
#number_of_rejected_posts ⇒ Object
1532
1533
1534
|
# File 'app/models/user.rb', line 1532
def number_of_rejected_posts
ReviewableQueuedPost.rejected.where(target_created_by_id: self.id).count
end
|
#number_of_suspensions ⇒ Object
1544
1545
1546
|
# File 'app/models/user.rb', line 1544
def number_of_suspensions
UserHistory.for(self, :suspend_user).count
end
|
#on_tl3_grace_period? ⇒ Boolean
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
|
# File 'app/models/user.rb', line 1443
def on_tl3_grace_period?
return true if SiteSetting.tl3_promotion_min_duration.to_i.days.ago.year < 2013
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
|
#password ⇒ Object
889
890
891
|
# File 'app/models/user.rb', line 889
def password
"" end
|
#password=(password) ⇒ Object
884
885
886
887
|
# File 'app/models/user.rb', line 884
def password=(password)
@raw_password = password unless password.blank?
end
|
#password_required! ⇒ Object
Indicate that this is NOT a passwordless account for the purposes of validation
894
895
896
|
# File 'app/models/user.rb', line 894
def password_required!
@password_required = true
end
|
#password_required? ⇒ Boolean
898
899
900
|
# File 'app/models/user.rb', line 898
def password_required?
!!@password_required
end
|
#password_validation_required? ⇒ Boolean
902
903
904
|
# File 'app/models/user.rb', line 902
def password_validation_required?
password_required? || @raw_password.present?
end
|
#password_validator ⇒ Object
910
911
912
|
# File 'app/models/user.rb', line 910
def password_validator
PasswordValidator.new(attributes: :password).validate_each(self, :password, @raw_password)
end
|
#post_count ⇒ Object
1174
1175
1176
|
# File 'app/models/user.rb', line 1174
def post_count
stat.post_count
end
|
#post_edits_count ⇒ Object
1178
1179
1180
|
# File 'app/models/user.rb', line 1178
def post_edits_count
stat.post_edits_count
end
|
#posted_too_much_in_topic?(topic_id) ⇒ Boolean
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
|
# File 'app/models/user.rb', line 1201
def posted_too_much_in_topic?(topic_id)
return false if staff? || (trust_level != TrustLevel[0])
topic = Topic.where(id: topic_id).first
return false if topic.try(:private_message?) || (topic.try(:user_id) == self.id)
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)
end
|
#preload_recent_time_read(time) ⇒ Object
1649
1650
1651
|
# File 'app/models/user.rb', line 1649
def preload_recent_time_read(time)
@recent_time_read = time
end
|
#private_topics_count ⇒ Object
1197
1198
1199
|
# File 'app/models/user.rb', line 1197
def private_topics_count
topics_allowed.where(archetype: Archetype.private_message).count
end
|
#publish_do_not_disturb(ends_at: nil) ⇒ Object
862
863
864
|
# File 'app/models/user.rb', line 862
def publish_do_not_disturb(ends_at: nil)
MessageBus.publish("/do-not-disturb/#{id}", { ends_at: ends_at&.httpdate }, user_ids: [id])
end
|
#publish_notifications_state ⇒ Object
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
|
# File 'app/models/user.rb', line 812
def publish_notifications_state
return if !self.allow_live_notifications?
notification = notifications.visible.order("notifications.created_at desc").first
json = NotificationSerializer.new(notification).as_json if notification
sql = (<<~SQL)
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
n.high_priority AND
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
(n.high_priority = FALSE OR read) AND
n.user_id = :user_id
ORDER BY n.id DESC
LIMIT 20
) AS y
SQL
recent = DB.query(sql, user_id: id).map! { |r| [r.id, r.read] }
payload = {
unread_notifications: unread_notifications,
unread_high_priority_notifications: unread_high_priority_notifications,
read_first_notification: read_first_notification?,
last_notification: json,
recent: recent,
seen_notification_id: seen_notification_id,
}
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
MessageBus.publish("/notification/#{id}", payload, user_ids: [id])
end
|
#publish_reviewable_counts(extra_data = nil) ⇒ Object
799
800
801
802
803
804
805
806
|
# File 'app/models/user.rb', line 799
def publish_reviewable_counts( = nil)
data = {
reviewable_count: self.reviewable_count,
unseen_reviewable_count: Reviewable.unseen_reviewable_count(self),
}
data.merge!() if .present?
MessageBus.publish("/reviewable_counts/#{self.id}", data, user_ids: [self.id])
end
|
#publish_user_status(status) ⇒ Object
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
|
# File 'app/models/user.rb', line 866
def publish_user_status(status)
if status
payload = {
description: status.description,
emoji: status.emoji,
ends_at: status.ends_at&.iso8601,
}
else
payload = nil
end
MessageBus.publish(
"/user-status",
{ id => payload },
group_ids: [Group::AUTO_GROUPS[:trust_level_0]],
)
end
|
#read_first_notification? ⇒ Boolean
808
809
810
|
# File 'app/models/user.rb', line 808
def read_first_notification?
self.seen_notification_id != 0 || user_option.skip_new_user_tips
end
|
#readable_name ⇒ Object
1339
1340
1341
|
# File 'app/models/user.rb', line 1339
def readable_name
name.present? && name != username ? "#{name} (#{username})" : username
end
|
#recent_time_read ⇒ Object
1653
1654
1655
1656
|
# File 'app/models/user.rb', line 1653
def recent_time_read
@recent_time_read ||=
self.user_visits.where("visited_at >= ?", RECENT_TIME_READ_THRESHOLD.ago).sum(:time_read)
end
|
#refresh_avatar ⇒ Object
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
|
# File 'app/models/user.rb', line 1454
def refresh_avatar
return if @import_mode
avatar = user_avatar || create_user_avatar
if self.primary_email.present? && SiteSetting.automatically_download_gravatars? &&
!avatar.last_gravatar_download_attempt
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)
end
Post.rebake_all_quoted_posts(self.id) if self.will_save_change_to_uploaded_avatar_id?
end
|
#reload ⇒ Object
594
595
596
597
598
599
600
601
602
603
604
605
|
# File 'app/models/user.rb', line 594
def reload
@unread_notifications = nil
@all_unread_notifications_count = nil
@unread_total_notifications = nil
@unread_pms = nil
@unread_bookmarks = nil
@unread_high_prios = nil
@ignored_user_ids = nil
@muted_user_ids = nil
@belonging_to_group_ids = nil
super
end
|
#reviewable_count ⇒ Object
760
761
762
|
# File 'app/models/user.rb', line 760
def reviewable_count
Reviewable.list_for(self, include_claimed_by_others: false).count
end
|
#saw_notification_id(notification_id) ⇒ Object
764
765
766
767
768
769
770
771
772
773
774
|
# File 'app/models/user.rb', line 764
def saw_notification_id(notification_id)
Discourse.deprecate(<<~TEXT, since: "2.9", drop_from: "3.0")
User#saw_notification_id is deprecated. Please use User#bump_last_seen_notification! instead.
TEXT
if seen_notification_id.to_i < notification_id.to_i
update_columns(seen_notification_id: notification_id.to_i)
true
else
false
end
end
|
#second_factor_security_key_credential_ids ⇒ Object
1725
1726
1727
1728
1729
1730
|
# File 'app/models/user.rb', line 1725
def second_factor_security_key_credential_ids
security_keys
.select(:credential_id)
.where(factor_type: UserSecurityKey.factor_types[:second_factor])
.pluck(:credential_id)
end
|
#secondary_emails ⇒ Object
1626
1627
1628
|
# File 'app/models/user.rb', line 1626
def secondary_emails
self.user_emails.secondary.pluck(:email)
end
|
#secure_category_ids ⇒ Object
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
|
# File 'app/models/user.rb', line 1389
def secure_category_ids
cats =
if self.admin? && !SiteSetting.suppress_secured_categories_from_admin
Category.unscoped.where(read_restricted: true)
else
secure_categories.references(:categories)
end
cats.pluck("categories.id").sort
end
|
354
355
356
357
358
359
|
# File 'app/models/user.rb', line 354
def (user_guardian = nil)
user_guardian ||= guardian
SidebarSectionLink.where(user_id: self.id, linkable_type: "Category").pluck(:linkable_id) &
user_guardian.allowed_category_ids
end
|
#seen_before? ⇒ Boolean
944
945
946
|
# File 'app/models/user.rb', line 944
def seen_before?
last_seen_at.present?
end
|
#seen_since?(datetime) ⇒ Boolean
948
949
950
|
# File 'app/models/user.rb', line 948
def seen_since?(datetime)
seen_before? && last_seen_at >= datetime
end
|
#set_automatic_groups ⇒ Object
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
|
# File 'app/models/user.rb', line 1580
def set_automatic_groups
return if !active || staged || !email_confirmed?
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)
end
end
@belonging_to_group_ids = nil
end
|
#set_random_avatar ⇒ Object
1552
1553
1554
1555
1556
1557
1558
1559
|
# File 'app/models/user.rb', line 1552
def set_random_avatar
if SiteSetting.selectable_avatars_mode != "disabled"
if upload = SiteSetting.selectable_avatars.sample
update_column(:uploaded_avatar_id, upload.id)
UserAvatar.create!(user_id: id, custom_upload_id: upload.id)
end
end
end
|
#set_status!(description, emoji, ends_at = nil) ⇒ Object
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
|
# File 'app/models/user.rb', line 1778
def set_status!(description, emoji, ends_at = nil)
status = {
description: description,
emoji: emoji,
set_at: Time.zone.now,
ends_at: ends_at,
user_id: id,
}
validate_status!(status)
UserStatus.upsert(status)
reload_user_status
publish_user_status(user_status)
end
|
#set_user_field(field_id, value) ⇒ Object
1498
1499
1500
|
# File 'app/models/user.rb', line 1498
def set_user_field(field_id, value)
custom_fields["#{USER_FIELD_PREFIX}#{field_id}"] = value
end
|
#shelved_notifications ⇒ Object
1749
1750
1751
|
# File 'app/models/user.rb', line 1749
def shelved_notifications
ShelvedNotification.joins(:notification).where("notifications.user_id = ?", self.id)
end
|
#should_validate_email_address? ⇒ Boolean
582
583
584
|
# File 'app/models/user.rb', line 582
def should_validate_email_address?
!skip_email_validation && !staged?
end
|
#silence_reason ⇒ Object
1238
1239
1240
|
# File 'app/models/user.rb', line 1238
def silence_reason
silenced_record.try(:details) if silenced?
end
|
#silenced? ⇒ Boolean
1230
1231
1232
|
# File 'app/models/user.rb', line 1230
def silenced?
!!(silenced_till && silenced_till > Time.zone.now)
end
|
#silenced_at ⇒ Object
1242
1243
1244
|
# File 'app/models/user.rb', line 1242
def silenced_at
silenced_record.try(:created_at) if silenced?
end
|
#silenced_forever? ⇒ Boolean
1246
1247
1248
|
# File 'app/models/user.rb', line 1246
def silenced_forever?
silenced_till > 100.years.from_now
end
|
#silenced_record ⇒ Object
1234
1235
1236
|
# File 'app/models/user.rb', line 1234
def silenced_record
UserHistory.for(self, :silence_user).order("id DESC").first
end
|
#small_avatar_url ⇒ Object
Don’t pass this up to the client - it’s meant for server side use This is used in
- self oneboxes in open graph data
- emails
1079
1080
1081
|
# File 'app/models/user.rb', line 1079
def small_avatar_url
avatar_template_url.gsub("{size}", "45")
end
|
#suspend_reason ⇒ Object
1258
1259
1260
1261
1262
1263
1264
|
# File 'app/models/user.rb', line 1258
def suspend_reason
if details = full_suspend_reason
return details.split("\n")[0]
end
nil
end
|
#suspend_record ⇒ Object
1250
1251
1252
|
# File 'app/models/user.rb', line 1250
def suspend_record
UserHistory.for(self, :suspend_user).order("id DESC").first
end
|
#suspended? ⇒ Boolean
1226
1227
1228
|
# File 'app/models/user.rb', line 1226
def suspended?
!!(suspended_till && suspended_till > Time.zone.now)
end
|
#suspended_forever? ⇒ Boolean
1285
1286
1287
|
# File 'app/models/user.rb', line 1285
def suspended_forever?
suspended_till > 100.years.from_now
end
|
#suspended_message ⇒ Object
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
|
# File 'app/models/user.rb', line 1266
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
|
#sync_notification_channel_position ⇒ Object
tricky, we need our bus to be subscribed from the right spot
571
572
573
574
|
# File 'app/models/user.rb', line 571
def sync_notification_channel_position
@unread_notifications_by_type = nil
self.notification_channel_position = MessageBus.last_id("/notification/#{id}")
end
|
#tl3_requirements ⇒ Object
#total_unread_notifications ⇒ Object
756
757
758
|
# File 'app/models/user.rb', line 756
def total_unread_notifications
@unread_total_notifications ||= notifications.where("read = false").count
end
|
#unconfirmed_emails ⇒ Object
1630
1631
1632
1633
1634
1635
|
# File 'app/models/user.rb', line 1630
def unconfirmed_emails
self
.email_change_requests
.where.not(change_state: EmailChangeRequest.states[:complete])
.pluck(:new_email)
end
|
#unread_high_priority_notifications ⇒ Object
666
667
668
|
# File 'app/models/user.rb', line 666
def unread_high_priority_notifications
@unread_high_prios ||= unread_notifications_of_priority(high_priority: true)
end
|
#unread_notifications ⇒ Object
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
|
# File 'app/models/user.rb', line 700
def unread_notifications
@unread_notifications ||=
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.high_priority = FALSE 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,
)[
0
].to_i
end
end
|
#unread_notifications_of_priority(high_priority:) ⇒ Object
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
|
# File 'app/models/user.rb', line 632
def unread_notifications_of_priority(high_priority:)
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
DB.query_single(sql, user_id: id, high_priority: high_priority)[0].to_i
end
|
#unread_notifications_of_type(notification_type, since: nil) ⇒ Object
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
|
# File 'app/models/user.rb', line 615
def unread_notifications_of_type(notification_type, since: nil)
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.notification_type = :notification_type
AND n.user_id = :user_id
AND NOT read
#{since ? "AND n.created_at > :since" : ""}
SQL
DB.query_single(sql, user_id: id, notification_type: notification_type, since: since)[0].to_i
end
|
#unstage! ⇒ Object
477
478
479
480
481
482
483
484
485
486
487
488
|
# File 'app/models/user.rb', line 477
def unstage!
if self.staged
ActiveRecord::Base.transaction do
self.staged = false
self.custom_fields[FROM_STAGED] = true
self.notifications.destroy_all
save!
end
DiscourseEvent.trigger(:user_unstaged, self)
end
end
|
#update_ip_address!(new_ip_address) ⇒ Object
1030
1031
1032
|
# File 'app/models/user.rb', line 1030
def update_ip_address!(new_ip_address)
User.update_ip_address!(id, new_ip: new_ip_address, old_ip: ip_address)
end
|
#update_last_seen!(now = Time.zone.now, force: false) ⇒ Object
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
|
# File 'app/models/user.rb', line 1058
def update_last_seen!(now = Time.zone.now, force: false)
if !force
return if !User.should_update_last_seen?(self.id, now)
end
update_previous_visit(now)
update_column(:last_seen_at, now)
update_column(:first_seen_at, now) unless self.first_seen_at
DiscourseEvent.trigger(:user_seen, self)
end
|
#update_posts_read!(num_posts, opts = {}) ⇒ Object
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
|
# File 'app/models/user.rb', line 976
def update_posts_read!(num_posts, opts = {})
now = opts[:at] || Time.zone.now
_retry = opts[:retry] || false
if user_visit = visit_record_for(now.to_date)
user_visit.posts_read += num_posts
user_visit.mobile = true if opts[:mobile]
user_visit.save
user_visit
else
begin
create_visit_record!(now.to_date, posts_read: num_posts, mobile: opts.fetch(:mobile, false))
rescue ActiveRecord::RecordNotUnique
if !_retry
update_posts_read!(num_posts, opts.merge(retry: true))
else
raise
end
end
end
end
|
#update_timezone_if_missing(timezone) ⇒ Object
969
970
971
972
973
974
|
# File 'app/models/user.rb', line 969
def update_timezone_if_missing(timezone)
return if timezone.blank? || !TimezoneValidator.valid?(timezone)
UserOption.where(user_id: self.id, timezone: nil).update_all(timezone: timezone)
end
|
#update_visit_record!(date) ⇒ Object
965
966
967
|
# File 'app/models/user.rb', line 965
def update_visit_record!(date)
create_visit_record!(date) unless visit_record_for(date)
end
|
#user_fields(field_ids = nil) ⇒ Object
1488
1489
1490
1491
1492
|
# File 'app/models/user.rb', line 1488
def user_fields(field_ids = nil)
field_ids = (@all_user_field_ids ||= UserField.pluck(:id)) if field_ids.nil?
field_ids.map { |fid| [fid.to_s, custom_fields["#{USER_FIELD_PREFIX}#{fid}"]] }.to_h
end
|
#username_equals_to?(another_username) ⇒ Boolean
1757
1758
1759
|
# File 'app/models/user.rb', line 1757
def username_equals_to?(another_username)
username_lower == User.normalize_username(another_username)
end
|
#validatable_user_fields ⇒ Object
1510
1511
1512
1513
1514
1515
1516
|
# File 'app/models/user.rb', line 1510
def validatable_user_fields
@public_user_field_ids ||=
UserField.public_fields.where.not(field_type: "multiselect").pluck(:id)
user_fields(@public_user_field_ids)
end
|
#validatable_user_fields_values ⇒ Object
1494
1495
1496
|
# File 'app/models/user.rb', line 1494
def validatable_user_fields_values
validatable_user_fields.values.join(" ")
end
|
#visible_groups ⇒ Object
527
528
529
|
# File 'app/models/user.rb', line 527
def visible_groups
groups.visible_groups(self)
end
|
361
362
363
364
365
366
367
368
369
370
|
# File 'app/models/user.rb', line 361
def (user_guardian = nil)
user_guardian ||= guardian
DiscourseTagging.filter_visible(
Tag.where(
id: SidebarSectionLink.where(user_id: self.id, linkable_type: "Tag").select(:linkable_id),
),
user_guardian,
)
end
|
#visit_record_for(date) ⇒ Object
961
962
963
|
# File 'app/models/user.rb', line 961
def visit_record_for(date)
user_visits.find_by(visited_at: date)
end
|
#warnings_received_count ⇒ Object
1193
1194
1195
|
# File 'app/models/user.rb', line 1193
def warnings_received_count
user_warnings.count
end
|
#watched_precedence_over_muted ⇒ Object
1805
1806
1807
1808
1809
1810
1811
|
# File 'app/models/user.rb', line 1805
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
|