diff --git a/.streerc b/.streerc index fd138211ec6..cc0be494538 100644 --- a/.streerc +++ b/.streerc @@ -1,3 +1,2 @@ --print-width=100 --plugins=plugin/trailing_comma,disable_ternary ---ignore-files=app/* diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb index 3f3f9e15a36..ba5d59879e1 100644 --- a/app/controllers/about_controller.rb +++ b/app/controllers/about_controller.rb @@ -1,30 +1,28 @@ # frozen_string_literal: true class AboutController < ApplicationController - requires_login only: [:live_post_counts] skip_before_action :check_xhr, only: [:index] def index - return redirect_to path('/login') if SiteSetting.login_required? && current_user.nil? + return redirect_to path("/login") if SiteSetting.login_required? && current_user.nil? @about = About.new(current_user) @title = "#{I18n.t("js.about.simple_title")} - #{SiteSetting.title}" respond_to do |format| - format.html do - render :index - end - format.json do - render_json_dump(AboutSerializer.new(@about, scope: guardian)) - end + format.html { render :index } + format.json { render_json_dump(AboutSerializer.new(@about, scope: guardian)) } end end def live_post_counts - RateLimiter.new(current_user, "live_post_counts", 1, 10.minutes).performed! unless current_user.staff? + unless current_user.staff? + RateLimiter.new(current_user, "live_post_counts", 1, 10.minutes).performed! + end category_topic_ids = Category.pluck(:topic_id).compact! - public_topics = Topic.listable_topics.visible.secured(Guardian.new(nil)).where.not(id: category_topic_ids) + public_topics = + Topic.listable_topics.visible.secured(Guardian.new(nil)).where.not(id: category_topic_ids) stats = { public_topic_count: public_topics.count } stats[:public_post_count] = public_topics.sum(:posts_count) - stats[:public_topic_count] render json: stats diff --git a/app/controllers/admin/admin_controller.rb b/app/controllers/admin/admin_controller.rb index d9b1d705d97..2220a511e77 100644 --- a/app/controllers/admin/admin_controller.rb +++ b/app/controllers/admin/admin_controller.rb @@ -7,5 +7,4 @@ class Admin::AdminController < ApplicationController def index render body: nil end - end diff --git a/app/controllers/admin/api_controller.rb b/app/controllers/admin/api_controller.rb index 627c4c40093..49b8a70414e 100644 --- a/app/controllers/admin/api_controller.rb +++ b/app/controllers/admin/api_controller.rb @@ -8,40 +8,40 @@ class Admin::ApiController < Admin::AdminController offset = (params[:offset] || 0).to_i limit = (params[:limit] || 50).to_i.clamp(1, 50) - keys = ApiKey - .where(hidden: false) - .includes(:user, :api_key_scopes) - # Sort revoked keys by revoked_at and active keys by created_at - .order("revoked_at DESC NULLS FIRST, created_at DESC") - .offset(offset) - .limit(limit) + keys = + ApiKey + .where(hidden: false) + .includes(:user, :api_key_scopes) + # Sort revoked keys by revoked_at and active keys by created_at + .order("revoked_at DESC NULLS FIRST, created_at DESC") + .offset(offset) + .limit(limit) - render_json_dump( - keys: serialize_data(keys, ApiKeySerializer), - offset: offset, - limit: limit - ) + render_json_dump(keys: serialize_data(keys, ApiKeySerializer), offset: offset, limit: limit) end def show api_key = ApiKey.includes(:api_key_scopes).find_by!(id: params[:id]) - render_serialized(api_key, ApiKeySerializer, root: 'key') + render_serialized(api_key, ApiKeySerializer, root: "key") end def scopes - scopes = ApiKeyScope.scope_mappings.reduce({}) do |memo, (resource, actions)| - memo.tap do |m| - m[resource] = actions.map do |k, v| - { - scope_id: "#{resource}:#{k}", - key: k, - name: k.to_s.gsub('_', ' '), - params: v[:params], - urls: v[:urls] - } + scopes = + ApiKeyScope + .scope_mappings + .reduce({}) do |memo, (resource, actions)| + memo.tap do |m| + m[resource] = actions.map do |k, v| + { + scope_id: "#{resource}:#{k}", + key: k, + name: k.to_s.gsub("_", " "), + params: v[:params], + urls: v[:urls], + } + end + end end - end - end render json: { scopes: scopes } end @@ -52,7 +52,7 @@ class Admin::ApiController < Admin::AdminController api_key.update!(update_params) log_api_key(api_key, UserHistory.actions[:api_key_update], changes: api_key.saved_changes) end - render_serialized(api_key, ApiKeySerializer, root: 'key') + render_serialized(api_key, ApiKeySerializer, root: "key") end def destroy @@ -76,7 +76,7 @@ class Admin::ApiController < Admin::AdminController api_key.save! log_api_key(api_key, UserHistory.actions[:api_key_create], changes: api_key.saved_changes) end - render_serialized(api_key, ApiKeySerializer, root: 'key') + render_serialized(api_key, ApiKeySerializer, root: "key") end def undo_revoke_key @@ -105,7 +105,7 @@ class Admin::ApiController < Admin::AdminController def build_scopes params.require(:key)[:scopes].to_a.map do |scope_params| - resource, action = scope_params[:scope_id].split(':') + resource, action = scope_params[:scope_id].split(":") mapping = ApiKeyScope.scope_mappings.dig(resource.to_sym, action.to_sym) raise Discourse::InvalidParameters if mapping.nil? # invalid mapping @@ -113,7 +113,7 @@ class Admin::ApiController < Admin::AdminController ApiKeyScope.new( resource: resource, action: action, - allowed_parameters: build_params(scope_params, mapping[:params]) + allowed_parameters: build_params(scope_params, mapping[:params]), ) end end @@ -121,11 +121,13 @@ class Admin::ApiController < Admin::AdminController def build_params(scope_params, params) return if params.nil? - scope_params.slice(*params).tap do |allowed_params| - allowed_params.each do |k, v| - v.blank? ? allowed_params.delete(k) : allowed_params[k] = v.split(',') + scope_params + .slice(*params) + .tap do |allowed_params| + allowed_params.each do |k, v| + v.blank? ? allowed_params.delete(k) : allowed_params[k] = v.split(",") + end end - end end def update_params @@ -146,5 +148,4 @@ class Admin::ApiController < Admin::AdminController def log_api_key_restore(*args) StaffActionLogger.new(current_user).log_api_key_restore(*args) end - end diff --git a/app/controllers/admin/backups_controller.rb b/app/controllers/admin/backups_controller.rb index 64dfeb0ee48..2660c9895e8 100644 --- a/app/controllers/admin/backups_controller.rb +++ b/app/controllers/admin/backups_controller.rb @@ -7,7 +7,7 @@ class Admin::BackupsController < Admin::AdminController include ExternalUploadHelpers before_action :ensure_backups_enabled - skip_before_action :check_xhr, only: [:index, :show, :logs, :check_backup_chunk, :upload_backup_chunk] + skip_before_action :check_xhr, only: %i[index show logs check_backup_chunk upload_backup_chunk] def index respond_to do |format| @@ -62,7 +62,7 @@ class Admin::BackupsController < Admin::AdminController Jobs.enqueue( :download_backup_email, user_id: current_user.id, - backup_file_path: url_for(controller: 'backups', action: 'show') + backup_file_path: url_for(controller: "backups", action: "show"), ) render body: nil @@ -73,8 +73,8 @@ class Admin::BackupsController < Admin::AdminController def show if !EmailBackupToken.compare(current_user.id, params.fetch(:token)) - @error = I18n.t('download_backup_mailer.no_token') - return render layout: 'no_ember', status: 422, formats: [:html] + @error = I18n.t("download_backup_mailer.no_token") + return render layout: "no_ember", status: 422, formats: [:html] end store = BackupRestore::BackupStore.create @@ -86,7 +86,7 @@ class Admin::BackupsController < Admin::AdminController if store.remote? redirect_to backup.source else - headers['Content-Length'] = File.size(backup.source).to_s + headers["Content-Length"] = File.size(backup.source).to_s send_file backup.source end else @@ -170,9 +170,15 @@ class Admin::BackupsController < Admin::AdminController identifier = params.fetch(:resumableIdentifier) raise Discourse::InvalidParameters.new(:resumableIdentifier) unless valid_filename?(identifier) - return render status: 415, plain: I18n.t("backup.backup_file_should_be_tar_gz") unless valid_extension?(filename) - return render status: 415, plain: I18n.t("backup.not_enough_space_on_disk") unless has_enough_space_on_disk?(total_size) - return render status: 415, plain: I18n.t("backup.invalid_filename") unless valid_filename?(filename) + unless valid_extension?(filename) + return render status: 415, plain: I18n.t("backup.backup_file_should_be_tar_gz") + end + unless has_enough_space_on_disk?(total_size) + return render status: 415, plain: I18n.t("backup.not_enough_space_on_disk") + end + unless valid_filename?(filename) + return render status: 415, plain: I18n.t("backup.invalid_filename") + end file = params.fetch(:file) chunk_number = params.fetch(:resumableChunkNumber).to_i @@ -187,7 +193,13 @@ class Admin::BackupsController < Admin::AdminController uploaded_file_size = previous_chunk_number * chunk_size if uploaded_file_size + current_chunk_size >= total_size # merge all the chunks in a background thread - Jobs.enqueue_in(5.seconds, :backup_chunks_merger, filename: filename, identifier: identifier, chunks: chunk_number) + Jobs.enqueue_in( + 5.seconds, + :backup_chunks_merger, + filename: filename, + identifier: identifier, + chunks: chunk_number, + ) end render body: nil @@ -197,7 +209,9 @@ class Admin::BackupsController < Admin::AdminController params.require(:filename) filename = params.fetch(:filename) - return render_json_error(I18n.t("backup.backup_file_should_be_tar_gz")) unless valid_extension?(filename) + unless valid_extension?(filename) + return render_json_error(I18n.t("backup.backup_file_should_be_tar_gz")) + end return render_json_error(I18n.t("backup.invalid_filename")) unless valid_filename?(filename) store = BackupRestore::BackupStore.create @@ -236,8 +250,16 @@ class Admin::BackupsController < Admin::AdminController end def validate_before_create_multipart(file_name:, file_size:, upload_type:) - raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.backup_file_should_be_tar_gz")) unless valid_extension?(file_name) - raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.invalid_filename")) unless valid_filename?(file_name) + unless valid_extension?(file_name) + raise ExternalUploadHelpers::ExternalUploadValidationError.new( + I18n.t("backup.backup_file_should_be_tar_gz"), + ) + end + unless valid_filename?(file_name) + raise ExternalUploadHelpers::ExternalUploadValidationError.new( + I18n.t("backup.invalid_filename"), + ) + end end def self.serialize_upload(_upload) @@ -248,7 +270,11 @@ class Admin::BackupsController < Admin::AdminController begin yield rescue BackupRestore::BackupStore::StorageError => err - message = debug_upload_error(err, I18n.t("upload.create_multipart_failure", additional_detail: err.message)) + message = + debug_upload_error( + err, + I18n.t("upload.create_multipart_failure", additional_detail: err.message), + ) raise ExternalUploadHelpers::ExternalUploadValidationError.new(message) rescue BackupRestore::BackupStore::BackupFileExists raise ExternalUploadHelpers::ExternalUploadValidationError.new(I18n.t("backup.file_exists")) diff --git a/app/controllers/admin/badges_controller.rb b/app/controllers/admin/badges_controller.rb index 6d278679e5f..4efb86cff89 100644 --- a/app/controllers/admin/badges_controller.rb +++ b/app/controllers/admin/badges_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'csv' +require "csv" class Admin::BadgesController < Admin::AdminController MAX_CSV_LINES = 50_000 @@ -10,25 +10,29 @@ class Admin::BadgesController < Admin::AdminController data = { badge_types: BadgeType.all.order(:id).to_a, badge_groupings: BadgeGrouping.all.order(:position).to_a, - badges: Badge.includes(:badge_grouping) - .includes(:badge_type, :image_upload) - .references(:badge_grouping) - .order('badge_groupings.position, badge_type_id, badges.name').to_a, + badges: + Badge + .includes(:badge_grouping) + .includes(:badge_type, :image_upload) + .references(:badge_grouping) + .order("badge_groupings.position, badge_type_id, badges.name") + .to_a, protected_system_fields: Badge.protected_system_fields, - triggers: Badge.trigger_hash + triggers: Badge.trigger_hash, } render_serialized(OpenStruct.new(data), AdminBadgesSerializer) end def preview - unless SiteSetting.enable_badge_sql - return render json: "preview not allowed", status: 403 - end + return render json: "preview not allowed", status: 403 unless SiteSetting.enable_badge_sql - render json: BadgeGranter.preview(params[:sql], - target_posts: params[:target_posts] == "true", - explain: params[:explain] == "true", - trigger: params[:trigger].to_i) + render json: + BadgeGranter.preview( + params[:sql], + target_posts: params[:target_posts] == "true", + explain: params[:explain] == "true", + trigger: params[:trigger].to_i, + ) end def new @@ -47,18 +51,21 @@ class Admin::BadgesController < Admin::AdminController if !badge.enabled? render_json_error( - I18n.t('badges.mass_award.errors.badge_disabled', badge_name: badge.display_name), - status: 422 + I18n.t("badges.mass_award.errors.badge_disabled", badge_name: badge.display_name), + status: 422, ) return end - replace_badge_owners = params[:replace_badge_owners] == 'true' - ensure_users_have_badge_once = params[:grant_existing_holders] != 'true' + replace_badge_owners = params[:replace_badge_owners] == "true" + ensure_users_have_badge_once = params[:grant_existing_holders] != "true" if !ensure_users_have_badge_once && !badge.multiple_grant? render_json_error( - I18n.t('badges.mass_award.errors.cant_grant_multiple_times', badge_name: badge.display_name), - status: 422 + I18n.t( + "badges.mass_award.errors.cant_grant_multiple_times", + badge_name: badge.display_name, + ), + status: 422, ) return end @@ -72,7 +79,7 @@ class Admin::BadgesController < Admin::AdminController line_number += 1 if line.present? - if line.include?('@') + if line.include?("@") emails << line else usernames << line @@ -80,26 +87,35 @@ class Admin::BadgesController < Admin::AdminController end if emails.size + usernames.size > MAX_CSV_LINES - return render_json_error I18n.t('badges.mass_award.errors.too_many_csv_entries', count: MAX_CSV_LINES), status: 400 + return( + render_json_error I18n.t( + "badges.mass_award.errors.too_many_csv_entries", + count: MAX_CSV_LINES, + ), + status: 400 + ) end end end BadgeGranter.revoke_all(badge) if replace_badge_owners - results = BadgeGranter.enqueue_mass_grant_for_users( - badge, - emails: emails, - usernames: usernames, - ensure_users_have_badge_once: ensure_users_have_badge_once - ) + results = + BadgeGranter.enqueue_mass_grant_for_users( + badge, + emails: emails, + usernames: usernames, + ensure_users_have_badge_once: ensure_users_have_badge_once, + ) render json: { - unmatched_entries: results[:unmatched_entries].first(100), - matched_users_count: results[:matched_users_count], - unmatched_entries_count: results[:unmatched_entries_count] - }, status: :ok + unmatched_entries: results[:unmatched_entries].first(100), + matched_users_count: results[:matched_users_count], + unmatched_entries_count: results[:unmatched_entries_count], + }, + status: :ok rescue CSV::MalformedCSVError - render_json_error I18n.t('badges.mass_award.errors.invalid_csv', line_number: line_number), status: 400 + render_json_error I18n.t("badges.mass_award.errors.invalid_csv", line_number: line_number), + status: 400 end def badge_types @@ -119,9 +135,7 @@ class Admin::BadgesController < Admin::AdminController group.save end - badge_groupings.each do |g| - g.destroy unless g.system? || ids.include?(g.id) - end + badge_groupings.each { |g| g.destroy unless g.system? || ids.include?(g.id) } badge_groupings = BadgeGrouping.all.order(:position).to_a render_serialized(badge_groupings, BadgeGroupingSerializer, root: "badge_groupings") @@ -173,21 +187,23 @@ class Admin::BadgesController < Admin::AdminController def update_badge_from_params(badge, opts = {}) errors = [] Badge.transaction do - allowed = Badge.column_names.map(&:to_sym) - allowed -= [:id, :created_at, :updated_at, :grant_count] + allowed = Badge.column_names.map(&:to_sym) + allowed -= %i[id created_at updated_at grant_count] allowed -= Badge.protected_system_fields if badge.system? allowed -= [:query] unless SiteSetting.enable_badge_sql params.permit(*allowed) - allowed.each do |key| - badge.public_send("#{key}=" , params[key]) if params[key] - end + allowed.each { |key| badge.public_send("#{key}=", params[key]) if params[key] } # Badge query contract checks begin if SiteSetting.enable_badge_sql - BadgeGranter.contract_checks!(badge.query, target_posts: badge.target_posts, trigger: badge.trigger) + BadgeGranter.contract_checks!( + badge.query, + target_posts: badge.target_posts, + trigger: badge.trigger, + ) end rescue => e errors << e.message @@ -203,7 +219,7 @@ class Admin::BadgesController < Admin::AdminController :bulk_user_title_update, new_title: badge.name, granted_badge_id: badge.id, - action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION + action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION, ) end diff --git a/app/controllers/admin/color_schemes_controller.rb b/app/controllers/admin/color_schemes_controller.rb index 4b6407158ab..26a066477ce 100644 --- a/app/controllers/admin/color_schemes_controller.rb +++ b/app/controllers/admin/color_schemes_controller.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true class Admin::ColorSchemesController < Admin::AdminController - - before_action :fetch_color_scheme, only: [:update, :destroy] + before_action :fetch_color_scheme, only: %i[update destroy] def index - render_serialized(ColorScheme.base_color_schemes + ColorScheme.order('id ASC').all.to_a, ColorSchemeSerializer) + render_serialized( + ColorScheme.base_color_schemes + ColorScheme.order("id ASC").all.to_a, + ColorSchemeSerializer, + ) end def create @@ -38,6 +40,8 @@ class Admin::ColorSchemesController < Admin::AdminController end def color_scheme_params - params.permit(color_scheme: [:base_scheme_id, :name, :user_selectable, colors: [:name, :hex]])[:color_scheme] + params.permit(color_scheme: [:base_scheme_id, :name, :user_selectable, colors: %i[name hex]])[ + :color_scheme + ] end end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 99f9fcff891..d2ed26665dd 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -11,9 +11,12 @@ class Admin::DashboardController < Admin::StaffController render json: data end - def moderation; end - def security; end - def reports; end + def moderation + end + def security + end + def reports + end def general render json: AdminDashboardGeneralData.fetch_cached_stats @@ -33,7 +36,7 @@ class Admin::DashboardController < Admin::StaffController data = { new_features: new_features, has_unseen_features: DiscourseUpdates.has_unseen_features?(current_user.id), - release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"] + release_notes_link: AdminDashboardGeneralData.fetch_cached_stats["release_notes_link"], } render json: data end diff --git a/app/controllers/admin/email_controller.rb b/app/controllers/admin/email_controller.rb index b56f10f9f68..b165827f4d0 100644 --- a/app/controllers/admin/email_controller.rb +++ b/app/controllers/admin/email_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::EmailController < Admin::AdminController - def index data = { delivery_method: delivery_method, settings: delivery_settings } render_json_dump(data) @@ -35,29 +34,22 @@ class Admin::EmailController < Admin::AdminController else email_logs.where( "replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?", - "%#{reply_key}%" + "%#{reply_key}%", ) end end email_logs = email_logs.to_a - tuples = email_logs.map do |email_log| - [email_log.post_id, email_log.user_id] - end + tuples = email_logs.map { |email_log| [email_log.post_id, email_log.user_id] } reply_keys = {} if tuples.present? PostReplyKey - .where( - "(post_id,user_id) IN (#{(['(?)'] * tuples.size).join(', ')})", - *tuples - ) + .where("(post_id,user_id) IN (#{(["(?)"] * tuples.size).join(", ")})", *tuples) .pluck(:post_id, :user_id, "reply_key::text") - .each do |post_id, user_id, key| - reply_keys[[post_id, user_id]] = key - end + .each { |post_id, user_id, key| reply_keys[[post_id, user_id]] = key } end render_serialized(email_logs, EmailLogSerializer, reply_keys: reply_keys) @@ -96,14 +88,10 @@ class Admin::EmailController < Admin::AdminController def advanced_test params.require(:email) - receiver = Email::Receiver.new(params['email']) + receiver = Email::Receiver.new(params["email"]) text, elided, format = receiver.select_body - render json: success_json.merge!( - text: text, - elided: elided, - format: format - ) + render json: success_json.merge!(text: text, elided: elided, format: format) end def send_digest @@ -112,9 +100,8 @@ class Admin::EmailController < Admin::AdminController params.require(:email) user = User.find_by_username(params[:username]) - message, skip_reason = UserNotifications.public_send(:digest, user, - since: params[:last_seen_at] - ) + message, skip_reason = + UserNotifications.public_send(:digest, user, since: params[:last_seen_at]) if message message.to = params[:email] @@ -134,9 +121,16 @@ class Admin::EmailController < Admin::AdminController params.require(:to) # These strings aren't localized; they are sent to an anonymous SMTP user. if !User.with_email(Email.downcase(params[:from])).exists? && !SiteSetting.enable_staged_users - render json: { reject: true, reason: "Mail from your address is not accepted. Do you have an account here?" } + render json: { + reject: true, + reason: "Mail from your address is not accepted. Do you have an account here?", + } elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil? - render json: { reject: true, reason: "Mail to this address is not accepted. Check the address and try to send again?" } + render json: { + reject: true, + reason: + "Mail to this address is not accepted. Check the address and try to send again?", + } else render json: { reject: false } end @@ -157,10 +151,15 @@ class Admin::EmailController < Admin::AdminController retry_count = 0 begin - Jobs.enqueue(:process_email, mail: email_raw, retry_on_rate_limit: true, source: "handle_mail") + Jobs.enqueue( + :process_email, + mail: email_raw, + retry_on_rate_limit: true, + source: "handle_mail", + ) rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e if retry_count == 0 - email_raw = email_raw.force_encoding('iso-8859-1').encode("UTF-8") + email_raw = email_raw.force_encoding("iso-8859-1").encode("UTF-8") retry_count += 1 retry else @@ -171,7 +170,8 @@ class Admin::EmailController < Admin::AdminController # TODO: 2022-05-01 Remove this route once all sites have migrated over # to using the new email_encoded param. if deprecated_email_param_used - render plain: "warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing" + render plain: + "warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing" else render plain: "email has been received and is queued for processing" end @@ -204,15 +204,15 @@ class Admin::EmailController < Admin::AdminController end if incoming_email.nil? - email_local_part, email_domain = SiteSetting.notification_email.split('@') + email_local_part, email_domain = SiteSetting.notification_email.split("@") bounced_to_address = "#{email_local_part}+verp-#{email_log.bounce_key}@#{email_domain}" incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address) end # Temporary fix until all old format of emails has been purged via lib/email/cleaner.rb if incoming_email.nil? - email_local_part, email_domain = SiteSetting.reply_by_email_address.split('@') - subdomain, root_domain, extension = email_domain&.split('.') + email_local_part, email_domain = SiteSetting.reply_by_email_address.split("@") + subdomain, root_domain, extension = email_domain&.split(".") bounced_to_address = "#{subdomain}+verp-#{email_log.bounce_key}@#{root_domain}.#{extension}" incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address) end @@ -231,41 +231,61 @@ class Admin::EmailController < Admin::AdminController def filter_logs(logs, params) table_name = logs.table_name - logs = logs.includes(:user, post: :topic) - .references(:user) - .order(created_at: :desc) - .offset(params[:offset] || 0) - .limit(50) + logs = + logs + .includes(:user, post: :topic) + .references(:user) + .order(created_at: :desc) + .offset(params[:offset] || 0) + .limit(50) logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present? - logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[:address].present? - logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[:type].present? + logs = logs.where("#{table_name}.to_address ILIKE ?", "%#{params[:address]}%") if params[ + :address + ].present? + logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[ + :type + ].present? if table_name == "email_logs" && params[:smtp_transaction_response].present? - logs = logs.where("#{table_name}.smtp_transaction_response ILIKE ?", "%#{params[:smtp_transaction_response]}%") + logs = + logs.where( + "#{table_name}.smtp_transaction_response ILIKE ?", + "%#{params[:smtp_transaction_response]}%", + ) end logs end def filter_incoming_emails(incoming_emails, params) - incoming_emails = incoming_emails.includes(:user, post: :topic) - .order(created_at: :desc) - .offset(params[:offset] || 0) - .limit(50) + incoming_emails = + incoming_emails + .includes(:user, post: :topic) + .order(created_at: :desc) + .offset(params[:offset] || 0) + .limit(50) - incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[:from].present? - incoming_emails = incoming_emails.where("to_addresses ILIKE :to OR cc_addresses ILIKE :to", to: "%#{params[:to]}%") if params[:to].present? - incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[:subject].present? - incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[:error].present? + incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[ + :from + ].present? + incoming_emails = + incoming_emails.where( + "to_addresses ILIKE :to OR cc_addresses ILIKE :to", + to: "%#{params[:to]}%", + ) if params[:to].present? + incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[ + :subject + ].present? + incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[ + :error + ].present? incoming_emails end def delivery_settings - action_mailer_settings - .reject { |k, _| k == :password } - .map { |k, v| { name: k, value: v } } + action_mailer_settings.reject { |k, _| k == :password }.map { |k, v| { name: k, value: v } } end def delivery_method diff --git a/app/controllers/admin/email_templates_controller.rb b/app/controllers/admin/email_templates_controller.rb index 48fffe9483f..f1050e885c3 100644 --- a/app/controllers/admin/email_templates_controller.rb +++ b/app/controllers/admin/email_templates_controller.rb @@ -1,70 +1,69 @@ # frozen_string_literal: true class Admin::EmailTemplatesController < Admin::AdminController - def self.email_keys - @email_keys ||= [ - "custom_invite_forum_mailer", - "custom_invite_mailer", - "invite_forum_mailer", - "invite_mailer", - "invite_password_instructions", - "new_version_mailer", - "new_version_mailer_with_notes", - "system_messages.backup_failed", - "system_messages.backup_succeeded", - "system_messages.bulk_invite_failed", - "system_messages.bulk_invite_succeeded", - "system_messages.csv_export_failed", - "system_messages.csv_export_succeeded", - "system_messages.download_remote_images_disabled", - "system_messages.email_error_notification", - "system_messages.email_reject_auto_generated", - "system_messages.email_reject_bad_destination_address", - "system_messages.email_reject_empty", - "system_messages.email_reject_invalid_access", - "system_messages.email_reject_parsing", - "system_messages.email_reject_reply_key", - "system_messages.email_reject_screened_email", - "system_messages.email_reject_topic_closed", - "system_messages.email_reject_topic_not_found", - "system_messages.email_reject_unrecognized_error", - "system_messages.email_reject_user_not_found", - "system_messages.email_revoked", - "system_messages.pending_users_reminder", - "system_messages.post_hidden", - "system_messages.post_hidden_again", - "system_messages.queued_posts_reminder", - "system_messages.restore_failed", - "system_messages.restore_succeeded", - "system_messages.silenced_by_staff", - "system_messages.spam_post_blocked", - "system_messages.too_many_spam_flags", - "system_messages.unsilenced", - "system_messages.user_automatically_silenced", - "system_messages.welcome_invite", - "system_messages.welcome_user", - "system_messages.welcome_staff", - "test_mailer", - "user_notifications.account_created", - "user_notifications.admin_login", - "user_notifications.confirm_new_email", - "user_notifications.email_login", - "user_notifications.forgot_password", - "user_notifications.notify_old_email", - "user_notifications.set_password", - "user_notifications.signup", - "user_notifications.signup_after_approval", - "user_notifications.suspicious_login", - "user_notifications.user_invited_to_private_message_pm", - "user_notifications.user_invited_to_private_message_pm_group", - "user_notifications.user_invited_to_topic", - "user_notifications.user_linked", - "user_notifications.user_mentioned", - "user_notifications.user_posted", - "user_notifications.user_posted_pm", - "user_notifications.user_quoted", - "user_notifications.user_replied", + @email_keys ||= %w[ + custom_invite_forum_mailer + custom_invite_mailer + invite_forum_mailer + invite_mailer + invite_password_instructions + new_version_mailer + new_version_mailer_with_notes + system_messages.backup_failed + system_messages.backup_succeeded + system_messages.bulk_invite_failed + system_messages.bulk_invite_succeeded + system_messages.csv_export_failed + system_messages.csv_export_succeeded + system_messages.download_remote_images_disabled + system_messages.email_error_notification + system_messages.email_reject_auto_generated + system_messages.email_reject_bad_destination_address + system_messages.email_reject_empty + system_messages.email_reject_invalid_access + system_messages.email_reject_parsing + system_messages.email_reject_reply_key + system_messages.email_reject_screened_email + system_messages.email_reject_topic_closed + system_messages.email_reject_topic_not_found + system_messages.email_reject_unrecognized_error + system_messages.email_reject_user_not_found + system_messages.email_revoked + system_messages.pending_users_reminder + system_messages.post_hidden + system_messages.post_hidden_again + system_messages.queued_posts_reminder + system_messages.restore_failed + system_messages.restore_succeeded + system_messages.silenced_by_staff + system_messages.spam_post_blocked + system_messages.too_many_spam_flags + system_messages.unsilenced + system_messages.user_automatically_silenced + system_messages.welcome_invite + system_messages.welcome_user + system_messages.welcome_staff + test_mailer + user_notifications.account_created + user_notifications.admin_login + user_notifications.confirm_new_email + user_notifications.email_login + user_notifications.forgot_password + user_notifications.notify_old_email + user_notifications.set_password + user_notifications.signup + user_notifications.signup_after_approval + user_notifications.suspicious_login + user_notifications.user_invited_to_private_message_pm + user_notifications.user_invited_to_private_message_pm_group + user_notifications.user_invited_to_topic + user_notifications.user_linked + user_notifications.user_mentioned + user_notifications.user_posted + user_notifications.user_posted_pm + user_notifications.user_quoted + user_notifications.user_replied ] end @@ -91,9 +90,18 @@ class Admin::EmailTemplatesController < Admin::AdminController log_site_text_change(subject_result) log_site_text_change(body_result) - render_serialized(key, AdminEmailTemplateSerializer, root: 'email_template', rest_serializer: true) + render_serialized( + key, + AdminEmailTemplateSerializer, + root: "email_template", + rest_serializer: true, + ) else - TranslationOverride.upsert!(I18n.locale, "#{key}.subject_template", subject_result[:old_value]) + TranslationOverride.upsert!( + I18n.locale, + "#{key}.subject_template", + subject_result[:old_value], + ) TranslationOverride.upsert!(I18n.locale, "#{key}.text_body_template", body_result[:old_value]) render_json_error(error_messages) @@ -105,11 +113,22 @@ class Admin::EmailTemplatesController < Admin::AdminController raise Discourse::NotFound unless self.class.email_keys.include?(params[:id]) revert_and_log("#{key}.subject_template", "#{key}.text_body_template") - render_serialized(key, AdminEmailTemplateSerializer, root: 'email_template', rest_serializer: true) + render_serialized( + key, + AdminEmailTemplateSerializer, + root: "email_template", + rest_serializer: true, + ) end def index - render_serialized(self.class.email_keys, AdminEmailTemplateSerializer, root: 'email_templates', rest_serializer: true, overridden_keys: overridden_keys) + render_serialized( + self.class.email_keys, + AdminEmailTemplateSerializer, + root: "email_templates", + rest_serializer: true, + overridden_keys: overridden_keys, + ) end private @@ -121,11 +140,7 @@ class Admin::EmailTemplatesController < Admin::AdminController translation_override = TranslationOverride.upsert!(I18n.locale, key, value) end - { - key: key, - old_value: old_value, - error_messages: translation_override&.errors&.full_messages - } + { key: key, old_value: old_value, error_messages: translation_override&.errors&.full_messages } end def revert_and_log(*keys) @@ -144,7 +159,10 @@ class Admin::EmailTemplatesController < Admin::AdminController def log_site_text_change(update_result) new_value = I18n.t(update_result[:key]) StaffActionLogger.new(current_user).log_site_text_change( - update_result[:key], new_value, update_result[:old_value]) + update_result[:key], + new_value, + update_result[:old_value], + ) end def format_error_message(update_result, attribute_key) diff --git a/app/controllers/admin/embeddable_hosts_controller.rb b/app/controllers/admin/embeddable_hosts_controller.rb index 765408039ce..96e08759af6 100644 --- a/app/controllers/admin/embeddable_hosts_controller.rb +++ b/app/controllers/admin/embeddable_hosts_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::EmbeddableHostsController < Admin::AdminController - def create save_host(EmbeddableHost.new, :create) end @@ -14,7 +13,10 @@ class Admin::EmbeddableHostsController < Admin::AdminController def destroy host = EmbeddableHost.where(id: params[:id]).first host.destroy - StaffActionLogger.new(current_user).log_embeddable_host(host, UserHistory.actions[:embeddable_host_destroy]) + StaffActionLogger.new(current_user).log_embeddable_host( + host, + UserHistory.actions[:embeddable_host_destroy], + ) render json: success_json end @@ -23,17 +25,25 @@ class Admin::EmbeddableHostsController < Admin::AdminController def save_host(host, action) host.host = params[:embeddable_host][:host] host.allowed_paths = params[:embeddable_host][:allowed_paths] - host.class_name = params[:embeddable_host][:class_name] + host.class_name = params[:embeddable_host][:class_name] host.category_id = params[:embeddable_host][:category_id] host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank? if host.save changes = host.saved_changes if action == :update - StaffActionLogger.new(current_user).log_embeddable_host(host, UserHistory.actions[:"embeddable_host_#{action}"], changes: changes) - render_serialized(host, EmbeddableHostSerializer, root: 'embeddable_host', rest_serializer: true) + StaffActionLogger.new(current_user).log_embeddable_host( + host, + UserHistory.actions[:"embeddable_host_#{action}"], + changes: changes, + ) + render_serialized( + host, + EmbeddableHostSerializer, + root: "embeddable_host", + rest_serializer: true, + ) else render_json_error(host) end end - end diff --git a/app/controllers/admin/embedding_controller.rb b/app/controllers/admin/embedding_controller.rb index c4540edd80b..51b0d48785e 100644 --- a/app/controllers/admin/embedding_controller.rb +++ b/app/controllers/admin/embedding_controller.rb @@ -1,25 +1,22 @@ # frozen_string_literal: true class Admin::EmbeddingController < Admin::AdminController - before_action :fetch_embedding def show - render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true) + render_serialized(@embedding, EmbeddingSerializer, root: "embedding", rest_serializer: true) end def update if params[:embedding][:embed_by_username].blank? - return render_json_error(I18n.t('site_settings.embed_username_required')) + return render_json_error(I18n.t("site_settings.embed_username_required")) end - Embedding.settings.each do |s| - @embedding.public_send("#{s}=", params[:embedding][s]) - end + Embedding.settings.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) } if @embedding.save fetch_embedding - render_serialized(@embedding, EmbeddingSerializer, root: 'embedding', rest_serializer: true) + render_serialized(@embedding, EmbeddingSerializer, root: "embedding", rest_serializer: true) else render_json_error(@embedding) end diff --git a/app/controllers/admin/emojis_controller.rb b/app/controllers/admin/emojis_controller.rb index 486a06c666d..774506744d8 100644 --- a/app/controllers/admin/emojis_controller.rb +++ b/app/controllers/admin/emojis_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::EmojisController < Admin::AdminController - def index render_serialized(Emoji.custom, EmojiSerializer, root: false) end @@ -17,15 +16,12 @@ class Admin::EmojisController < Admin::AdminController hijack do # fix the name name = File.basename(name, ".*") - name = name.gsub(/[^a-z0-9]+/i, '_') - .gsub(/_{2,}/, '_') - .downcase + name = name.gsub(/[^a-z0-9]+/i, "_").gsub(/_{2,}/, "_").downcase - upload = UploadCreator.new( - file.tempfile, - file.original_filename, - type: 'custom_emoji' - ).create_for(current_user.id) + upload = + UploadCreator.new(file.tempfile, file.original_filename, type: "custom_emoji").create_for( + current_user.id, + ) good = true @@ -61,5 +57,4 @@ class Admin::EmojisController < Admin::AdminController render json: success_json end - end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 935931de224..4d9e581fd3c 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -7,27 +7,19 @@ class Admin::GroupsController < Admin::StaffController attributes = group_params.to_h.except(:owner_usernames, :usernames) group = Group.new(attributes) - unless group_params[:allow_membership_requests] - group.membership_request_template = nil - end + group.membership_request_template = nil unless group_params[:allow_membership_requests] if group_params[:owner_usernames].present? - owner_ids = User.where( - username: group_params[:owner_usernames].split(",") - ).pluck(:id) + owner_ids = User.where(username: group_params[:owner_usernames].split(",")).pluck(:id) - owner_ids.each do |user_id| - group.group_users.build(user_id: user_id, owner: true) - end + owner_ids.each { |user_id| group.group_users.build(user_id: user_id, owner: true) } end if group_params[:usernames].present? user_ids = User.where(username: group_params[:usernames].split(",")).pluck(:id) user_ids -= owner_ids if owner_ids - user_ids.each do |user_id| - group.group_users.build(user_id: user_id) - end + user_ids.each { |user_id| group.group_users.build(user_id: user_id) } end if group.save @@ -140,45 +132,43 @@ class Admin::GroupsController < Admin::StaffController protected def can_not_modify_automatic - render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) + render_json_error(I18n.t("groups.errors.can_not_modify_automatic")) end private def group_params - permitted = [ - :name, - :mentionable_level, - :messageable_level, - :visibility_level, - :members_visibility_level, - :automatic_membership_email_domains, - :title, - :primary_group, - :grant_trust_level, - :incoming_email, - :flair_icon, - :flair_upload_id, - :flair_bg_color, - :flair_color, - :bio_raw, - :public_admission, - :public_exit, - :allow_membership_requests, - :full_name, - :default_notification_level, - :membership_request_template, - :owner_usernames, - :usernames, - :publish_read_state, - :notify_users + permitted = %i[ + name + mentionable_level + messageable_level + visibility_level + members_visibility_level + automatic_membership_email_domains + title + primary_group + grant_trust_level + incoming_email + flair_icon + flair_upload_id + flair_bg_color + flair_color + bio_raw + public_admission + public_exit + allow_membership_requests + full_name + default_notification_level + membership_request_template + owner_usernames + usernames + publish_read_state + notify_users ] custom_fields = DiscoursePluginRegistry.editable_group_custom_fields permitted << { custom_fields: custom_fields } unless custom_fields.blank? - if guardian.can_associate_groups? - permitted << { associated_group_ids: [] } - end + permitted << { associated_group_ids: [] } if guardian.can_associate_groups? permitted = permitted | DiscoursePluginRegistry.group_params diff --git a/app/controllers/admin/impersonate_controller.rb b/app/controllers/admin/impersonate_controller.rb index 8045f20d1e2..76df6b32d83 100644 --- a/app/controllers/admin/impersonate_controller.rb +++ b/app/controllers/admin/impersonate_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::ImpersonateController < Admin::AdminController - def create params.require(:username_or_email) @@ -18,5 +17,4 @@ class Admin::ImpersonateController < Admin::AdminController render body: nil end - end diff --git a/app/controllers/admin/permalinks_controller.rb b/app/controllers/admin/permalinks_controller.rb index 4e60a1ad010..54bea1f5280 100644 --- a/app/controllers/admin/permalinks_controller.rb +++ b/app/controllers/admin/permalinks_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::PermalinksController < Admin::AdminController - before_action :fetch_permalink, only: [:destroy] def index @@ -20,7 +19,8 @@ class Admin::PermalinksController < Admin::AdminController params[:permalink_type_value] = Tag.find_by_name(params[:permalink_type_value])&.id end - permalink = Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value]) + permalink = + Permalink.new(:url => params[:url], params[:permalink_type] => params[:permalink_type_value]) if permalink.save render_serialized(permalink, PermalinkSerializer) else @@ -38,5 +38,4 @@ class Admin::PermalinksController < Admin::AdminController def fetch_permalink @permalink = Permalink.find(params[:id]) end - end diff --git a/app/controllers/admin/plugins_controller.rb b/app/controllers/admin/plugins_controller.rb index b9e4913cc9b..202019844b9 100644 --- a/app/controllers/admin/plugins_controller.rb +++ b/app/controllers/admin/plugins_controller.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class Admin::PluginsController < Admin::StaffController - def index - render_serialized(Discourse.visible_plugins, AdminPluginSerializer, root: 'plugins') + render_serialized(Discourse.visible_plugins, AdminPluginSerializer, root: "plugins") end - end diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index 90e7c4de1fb..abc8b9e83e2 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -2,24 +2,28 @@ class Admin::ReportsController < Admin::StaffController def index - reports_methods = ['page_view_total_reqs'] + - ApplicationRequest.req_types.keys - .select { |r| r =~ /^page_view_/ && r !~ /mobile/ } - .map { |r| r + "_reqs" } + - Report.singleton_methods.grep(/^report_(?!about|storage_stats)/) + reports_methods = + ["page_view_total_reqs"] + + ApplicationRequest + .req_types + .keys + .select { |r| r =~ /^page_view_/ && r !~ /mobile/ } + .map { |r| r + "_reqs" } + + Report.singleton_methods.grep(/^report_(?!about|storage_stats)/) - reports = reports_methods.map do |name| - type = name.to_s.gsub('report_', '') - description = I18n.t("reports.#{type}.description", default: '') - description_link = I18n.t("reports.#{type}.description_link", default: '') + reports = + reports_methods.map do |name| + type = name.to_s.gsub("report_", "") + description = I18n.t("reports.#{type}.description", default: "") + description_link = I18n.t("reports.#{type}.description_link", default: "") - { - type: type, - title: I18n.t("reports.#{type}.title"), - description: description.presence ? description : nil, - description_link: description_link.presence ? description_link : nil - } - end + { + type: type, + title: I18n.t("reports.#{type}.title"), + description: description.presence ? description : nil, + description_link: description_link.presence ? description_link : nil, + } + end render_json_dump(reports: reports.sort_by { |report| report[:title] }) end @@ -32,18 +36,14 @@ class Admin::ReportsController < Admin::StaffController args = parse_params(report_params) report = nil - if (report_params[:cache]) - report = Report.find_cached(report_type, args) - end + report = Report.find_cached(report_type, args) if (report_params[:cache]) if report reports << report else report = Report.find(report_type, args) - if (report_params[:cache]) && report - Report.cache(report) - end + Report.cache(report) if (report_params[:cache]) && report if report.blank? report = Report._get(report_type, args) @@ -66,22 +66,16 @@ class Admin::ReportsController < Admin::StaffController args = parse_params(params) report = nil - if (params[:cache]) - report = Report.find_cached(report_type, args) - end + report = Report.find_cached(report_type, args) if (params[:cache]) - if report - return render_json_dump(report: report) - end + return render_json_dump(report: report) if report hijack do report = Report.find(report_type, args) raise Discourse::NotFound if report.blank? - if (params[:cache]) - Report.cache(report) - end + Report.cache(report) if (params[:cache]) render_json_dump(report: report) end @@ -91,16 +85,28 @@ class Admin::ReportsController < Admin::StaffController def parse_params(report_params) begin - start_date = (report_params[:start_date].present? ? Time.parse(report_params[:start_date]).to_date : 1.days.ago).beginning_of_day - end_date = (report_params[:end_date].present? ? Time.parse(report_params[:end_date]).to_date : start_date + 30.days).end_of_day + start_date = + ( + if report_params[:start_date].present? + Time.parse(report_params[:start_date]).to_date + else + 1.days.ago + end + ).beginning_of_day + end_date = + ( + if report_params[:end_date].present? + Time.parse(report_params[:end_date]).to_date + else + start_date + 30.days + end + ).end_of_day rescue ArgumentError => e raise Discourse::InvalidParameters.new(e.message) end facets = nil - if Array === report_params[:facets] - facets = report_params[:facets].map { |s| s.to_s.to_sym } - end + facets = report_params[:facets].map { |s| s.to_s.to_sym } if Array === report_params[:facets] limit = nil if report_params.has_key?(:limit) && report_params[:limit].to_i > 0 @@ -108,16 +114,8 @@ class Admin::ReportsController < Admin::StaffController end filters = nil - if report_params.has_key?(:filters) - filters = report_params[:filters] - end + filters = report_params[:filters] if report_params.has_key?(:filters) - { - start_date: start_date, - end_date: end_date, - filters: filters, - facets: facets, - limit: limit - } + { start_date: start_date, end_date: end_date, filters: filters, facets: facets, limit: limit } end end diff --git a/app/controllers/admin/robots_txt_controller.rb b/app/controllers/admin/robots_txt_controller.rb index b269a6c9ec0..d2912f06927 100644 --- a/app/controllers/admin/robots_txt_controller.rb +++ b/app/controllers/admin/robots_txt_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::RobotsTxtController < Admin::AdminController - def show render json: { robots_txt: current_robots_txt, overridden: @overridden } end diff --git a/app/controllers/admin/screened_emails_controller.rb b/app/controllers/admin/screened_emails_controller.rb index 0e2ccc91614..b8688a0998d 100644 --- a/app/controllers/admin/screened_emails_controller.rb +++ b/app/controllers/admin/screened_emails_controller.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true class Admin::ScreenedEmailsController < Admin::StaffController - def index - screened_emails = ScreenedEmail.limit(200).order('last_match_at desc').to_a + screened_emails = ScreenedEmail.limit(200).order("last_match_at desc").to_a render_serialized(screened_emails, ScreenedEmailSerializer) end @@ -12,5 +11,4 @@ class Admin::ScreenedEmailsController < Admin::StaffController screen.destroy! render json: success_json end - end diff --git a/app/controllers/admin/screened_ip_addresses_controller.rb b/app/controllers/admin/screened_ip_addresses_controller.rb index a270fb01937..27c50387d22 100644 --- a/app/controllers/admin/screened_ip_addresses_controller.rb +++ b/app/controllers/admin/screened_ip_addresses_controller.rb @@ -1,16 +1,19 @@ # frozen_string_literal: true class Admin::ScreenedIpAddressesController < Admin::StaffController - - before_action :fetch_screened_ip_address, only: [:update, :destroy] + before_action :fetch_screened_ip_address, only: %i[update destroy] def index filter = params[:filter] filter = IPAddr.handle_wildcards(filter) screened_ip_addresses = ScreenedIpAddress - screened_ip_addresses = screened_ip_addresses.where("cidr :filter >>= ip_address OR ip_address >>= cidr :filter", filter: filter) if filter.present? - screened_ip_addresses = screened_ip_addresses.limit(200).order('match_count desc') + screened_ip_addresses = + screened_ip_addresses.where( + "cidr :filter >>= ip_address OR ip_address >>= cidr :filter", + filter: filter, + ) if filter.present? + screened_ip_addresses = screened_ip_addresses.limit(200).order("match_count desc") begin screened_ip_addresses = screened_ip_addresses.to_a @@ -54,5 +57,4 @@ class Admin::ScreenedIpAddressesController < Admin::StaffController def fetch_screened_ip_address @screened_ip_address = ScreenedIpAddress.find(params[:id]) end - end diff --git a/app/controllers/admin/screened_urls_controller.rb b/app/controllers/admin/screened_urls_controller.rb index cedd7b0dfaf..20cc8a2d6a4 100644 --- a/app/controllers/admin/screened_urls_controller.rb +++ b/app/controllers/admin/screened_urls_controller.rb @@ -1,10 +1,15 @@ # frozen_string_literal: true class Admin::ScreenedUrlsController < Admin::StaffController - def index - screened_urls = ScreenedUrl.select("domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at").group(:domain).order('last_match_at DESC').to_a + screened_urls = + ScreenedUrl + .select( + "domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at", + ) + .group(:domain) + .order("last_match_at DESC") + .to_a render_serialized(screened_urls, GroupedScreenedUrlSerializer) end - end diff --git a/app/controllers/admin/search_logs_controller.rb b/app/controllers/admin/search_logs_controller.rb index a36b77d2b78..bd15b9e3814 100644 --- a/app/controllers/admin/search_logs_controller.rb +++ b/app/controllers/admin/search_logs_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Admin::SearchLogsController < Admin::StaffController - def index period = params[:period] || "all" search_type = params[:search_type] || "all" @@ -22,5 +21,4 @@ class Admin::SearchLogsController < Admin::StaffController details[:search_result] = serialize_data(result, GroupedSearchResultSerializer, result: result) render_json_dump(term: details) end - end diff --git a/app/controllers/admin/site_settings_controller.rb b/app/controllers/admin/site_settings_controller.rb index 4db8b72e43f..ad28e93af44 100644 --- a/app/controllers/admin/site_settings_controller.rb +++ b/app/controllers/admin/site_settings_controller.rb @@ -6,10 +6,7 @@ class Admin::SiteSettingsController < Admin::AdminController end def index - render_json_dump( - site_settings: SiteSetting.all_settings, - diags: SiteSetting.diags - ) + render_json_dump(site_settings: SiteSetting.all_settings, diags: SiteSetting.diags) end def update @@ -18,15 +15,17 @@ class Admin::SiteSettingsController < Admin::AdminController value = params[id] value.strip! if value.is_a?(String) - new_setting_name = SiteSettings::DeprecatedSettings::SETTINGS.find do |old_name, new_name, override, _| - if old_name == id - if !override - raise Discourse::InvalidParameters, "You cannot change this site setting because it is deprecated, use #{new_name} instead." - end + new_setting_name = + SiteSettings::DeprecatedSettings::SETTINGS.find do |old_name, new_name, override, _| + if old_name == id + if !override + raise Discourse::InvalidParameters, + "You cannot change this site setting because it is deprecated, use #{new_name} instead." + end - break new_name + break new_name + end end - end id = new_setting_name if new_setting_name @@ -36,9 +35,7 @@ class Admin::SiteSettingsController < Admin::AdminController value = Upload.get_from_urls(value.split("|")).to_a end - if SiteSetting.type_supervisor.get_type(id) == :upload - value = Upload.get_from_url(value) || "" - end + value = Upload.get_from_url(value) || "" if SiteSetting.type_supervisor.get_type(id) == :upload update_existing_users = params[:update_existing_user].present? previous_value = value_or_default(SiteSetting.public_send(id)) if update_existing_users @@ -68,22 +65,40 @@ class Admin::SiteSettingsController < Admin::AdminController notification_level = category_notification_level(id) categories_to_unwatch = previous_category_ids - new_category_ids - CategoryUser.where(category_id: categories_to_unwatch, notification_level: notification_level).delete_all + CategoryUser.where( + category_id: categories_to_unwatch, + notification_level: notification_level, + ).delete_all TopicUser .joins(:topic) - .where(notification_level: TopicUser.notification_levels[:watching], - notifications_reason_id: TopicUser.notification_reasons[:auto_watch_category], - topics: { category_id: categories_to_unwatch }) + .where( + notification_level: TopicUser.notification_levels[:watching], + notifications_reason_id: TopicUser.notification_reasons[:auto_watch_category], + topics: { + category_id: categories_to_unwatch, + }, + ) .update_all(notification_level: TopicUser.notification_levels[:regular]) (new_category_ids - previous_category_ids).each do |category_id| skip_user_ids = CategoryUser.where(category_id: category_id).pluck(:user_id) - User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users| - category_users = [] - users.each { |user| category_users << { category_id: category_id, user_id: user.id, notification_level: notification_level } } - CategoryUser.insert_all!(category_users) - end + User + .real + .where(staged: false) + .where.not(id: skip_user_ids) + .select(:id) + .find_in_batches do |users| + category_users = [] + users.each do |user| + category_users << { + category_id: category_id, + user_id: user.id, + notification_level: notification_level, + } + end + CategoryUser.insert_all!(category_users) + end end elsif id.start_with?("default_tags_") previous_tag_ids = Tag.where(name: previous_value.split("|")).pluck(:id) @@ -92,19 +107,40 @@ class Admin::SiteSettingsController < Admin::AdminController notification_level = tag_notification_level(id) - TagUser.where(tag_id: (previous_tag_ids - new_tag_ids), notification_level: notification_level).delete_all + TagUser.where( + tag_id: (previous_tag_ids - new_tag_ids), + notification_level: notification_level, + ).delete_all (new_tag_ids - previous_tag_ids).each do |tag_id| skip_user_ids = TagUser.where(tag_id: tag_id).pluck(:user_id) - User.real.where(staged: false).where.not(id: skip_user_ids).select(:id).find_in_batches do |users| - tag_users = [] - users.each { |user| tag_users << { tag_id: tag_id, user_id: user.id, notification_level: notification_level, created_at: now, updated_at: now } } - TagUser.insert_all!(tag_users) - end + User + .real + .where(staged: false) + .where.not(id: skip_user_ids) + .select(:id) + .find_in_batches do |users| + tag_users = [] + users.each do |user| + tag_users << { + tag_id: tag_id, + user_id: user.id, + notification_level: notification_level, + created_at: now, + updated_at: now, + } + end + TagUser.insert_all!(tag_users) + end end elsif is_sidebar_default_setting?(id) - Jobs.enqueue(:backfill_sidebar_site_settings, setting_name: id, previous_value: previous_value, new_value: new_value) + Jobs.enqueue( + :backfill_sidebar_site_settings, + setting_name: id, + previous_value: previous_value, + new_value: new_value, + ) end end @@ -135,15 +171,26 @@ class Admin::SiteSettingsController < Admin::AdminController notification_level = category_notification_level(id) - user_ids = CategoryUser.where(category_id: previous_category_ids - new_category_ids, notification_level: notification_level).distinct.pluck(:user_id) - user_ids += User - .real - .joins("CROSS JOIN categories c") - .joins("LEFT JOIN category_users cu ON users.id = cu.user_id AND c.id = cu.category_id") - .where(staged: false) - .where("c.id IN (?) AND cu.notification_level IS NULL", new_category_ids - previous_category_ids) - .distinct - .pluck("users.id") + user_ids = + CategoryUser + .where( + category_id: previous_category_ids - new_category_ids, + notification_level: notification_level, + ) + .distinct + .pluck(:user_id) + user_ids += + User + .real + .joins("CROSS JOIN categories c") + .joins("LEFT JOIN category_users cu ON users.id = cu.user_id AND c.id = cu.category_id") + .where(staged: false) + .where( + "c.id IN (?) AND cu.notification_level IS NULL", + new_category_ids - previous_category_ids, + ) + .distinct + .pluck("users.id") json[:user_count] = user_ids.uniq.count elsif id.start_with?("default_tags_") @@ -152,19 +199,28 @@ class Admin::SiteSettingsController < Admin::AdminController notification_level = tag_notification_level(id) - user_ids = TagUser.where(tag_id: previous_tag_ids - new_tag_ids, notification_level: notification_level).distinct.pluck(:user_id) - user_ids += User - .real - .joins("CROSS JOIN tags t") - .joins("LEFT JOIN tag_users tu ON users.id = tu.user_id AND t.id = tu.tag_id") - .where(staged: false) - .where("t.id IN (?) AND tu.notification_level IS NULL", new_tag_ids - previous_tag_ids) - .distinct - .pluck("users.id") + user_ids = + TagUser + .where(tag_id: previous_tag_ids - new_tag_ids, notification_level: notification_level) + .distinct + .pluck(:user_id) + user_ids += + User + .real + .joins("CROSS JOIN tags t") + .joins("LEFT JOIN tag_users tu ON users.id = tu.user_id AND t.id = tu.tag_id") + .where(staged: false) + .where("t.id IN (?) AND tu.notification_level IS NULL", new_tag_ids - previous_tag_ids) + .distinct + .pluck("users.id") json[:user_count] = user_ids.uniq.count elsif is_sidebar_default_setting?(id) - json[:user_count] = SidebarSiteSettingsBackfiller.new(id, previous_value: previous_value, new_value: new_value).number_of_users_to_backfill + json[:user_count] = SidebarSiteSettingsBackfiller.new( + id, + previous_value: previous_value, + new_value: new_value, + ).number_of_users_to_backfill end render json: json @@ -173,7 +229,7 @@ class Admin::SiteSettingsController < Admin::AdminController private def is_sidebar_default_setting?(setting_name) - %w{default_sidebar_categories default_sidebar_tags}.include?(setting_name.to_s) + %w[default_sidebar_categories default_sidebar_tags].include?(setting_name.to_s) end def user_options @@ -198,7 +254,7 @@ class Admin::SiteSettingsController < Admin::AdminController default_include_tl0_in_digests: "include_tl0_in_digests", default_text_size: "text_size_key", default_title_count_mode: "title_count_mode_key", - default_hide_profile_and_presence: "hide_profile_and_presence" + default_hide_profile_and_presence: "hide_profile_and_presence", } end diff --git a/app/controllers/admin/site_texts_controller.rb b/app/controllers/admin/site_texts_controller.rb index a3ed9f7d607..041c59128c8 100644 --- a/app/controllers/admin/site_texts_controller.rb +++ b/app/controllers/admin/site_texts_controller.rb @@ -1,22 +1,25 @@ # frozen_string_literal: true class Admin::SiteTextsController < Admin::AdminController - def self.preferred_keys - ['system_messages.usage_tips.text_body_template', - 'education.new-topic', - 'education.new-reply', - 'login_required.welcome_message'] + %w[ + system_messages.usage_tips.text_body_template + education.new-topic + education.new-reply + login_required.welcome_message + ] end def self.restricted_keys - ['user_notifications.confirm_old_email.title', - 'user_notifications.confirm_old_email.subject_template', - 'user_notifications.confirm_old_email.text_body_template'] + %w[ + user_notifications.confirm_old_email.title + user_notifications.confirm_old_email.subject_template + user_notifications.confirm_old_email.text_body_template + ] end def index - overridden = params[:overridden] == 'true' + overridden = params[:overridden] == "true" extras = {} query = params[:q] || "" @@ -61,7 +64,7 @@ class Admin::SiteTextsController < Admin::AdminController render_serialized( results[first..last - 1], SiteTextSerializer, - root: 'site_texts', + root: "site_texts", rest_serializer: true, extras: extras, overridden_keys: overridden, @@ -71,7 +74,7 @@ class Admin::SiteTextsController < Admin::AdminController def show locale = fetch_locale(params[:locale]) site_text = find_site_text(locale) - render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) + render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) end def update @@ -92,14 +95,14 @@ class Admin::SiteTextsController < Admin::AdminController :bulk_user_title_update, new_title: value, granted_badge_id: system_badge_id, - action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION + action: Jobs::BulkUserTitleUpdate::UPDATE_ACTION, ) end - render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) + render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) else - render json: failed_json.merge( - message: translation_override.errors.full_messages.join("\n\n") - ), status: 422 + render json: + failed_json.merge(message: translation_override.errors.full_messages.join("\n\n")), + status: 422 end end @@ -118,31 +121,27 @@ class Admin::SiteTextsController < Admin::AdminController Jobs.enqueue( :bulk_user_title_update, granted_badge_id: system_badge_id, - action: Jobs::BulkUserTitleUpdate::RESET_ACTION + action: Jobs::BulkUserTitleUpdate::RESET_ACTION, ) end - render_serialized(site_text, SiteTextSerializer, root: 'site_text', rest_serializer: true) + render_serialized(site_text, SiteTextSerializer, root: "site_text", rest_serializer: true) end def get_reseed_options render_json_dump( categories: SeedData::Categories.with_default_locale.reseed_options, - topics: SeedData::Topics.with_default_locale.reseed_options + topics: SeedData::Topics.with_default_locale.reseed_options, ) end def reseed hijack do if params[:category_ids].present? - SeedData::Categories.with_default_locale.update( - site_setting_names: params[:category_ids] - ) + SeedData::Categories.with_default_locale.update(site_setting_names: params[:category_ids]) end if params[:topic_ids].present? - SeedData::Topics.with_default_locale.update( - site_setting_names: params[:topic_ids] - ) + SeedData::Topics.with_default_locale.update(site_setting_names: params[:topic_ids]) end render json: success_json @@ -152,8 +151,8 @@ class Admin::SiteTextsController < Admin::AdminController protected def is_badge_title?(id = "") - badge_parts = id.split('.') - badge_parts[0] == 'badges' && badge_parts[2] == 'name' + badge_parts = id.split(".") + badge_parts[0] == "badges" && badge_parts[2] == "name" end def record_for(key:, value: nil, locale:) @@ -165,10 +164,15 @@ class Admin::SiteTextsController < Admin::AdminController def find_site_text(locale) if self.class.restricted_keys.include?(params[:id]) - raise Discourse::InvalidAccess.new(nil, nil, custom_message: 'email_template_cant_be_modified') + raise Discourse::InvalidAccess.new( + nil, + nil, + custom_message: "email_template_cant_be_modified", + ) end - if I18n.exists?(params[:id], locale) || TranslationOverride.exists?(locale: locale, translation_key: params[:id]) + if I18n.exists?(params[:id], locale) || + TranslationOverride.exists?(locale: locale, translation_key: params[:id]) return record_for(key: params[:id], locale: locale) end @@ -182,9 +186,7 @@ class Admin::SiteTextsController < Admin::AdminController def find_translations(query, overridden, locale) translations = Hash.new { |hash, key| hash[key] = {} } - search_results = I18n.with_locale(locale) do - I18n.search(query, only_overridden: overridden) - end + search_results = I18n.with_locale(locale) { I18n.search(query, only_overridden: overridden) } search_results.each do |key, value| if PLURALIZED_REGEX.match(key) @@ -205,7 +207,9 @@ class Admin::SiteTextsController < Admin::AdminController plural_value = plural[1] results << record_for( - key: "#{key}.#{plural_key}", value: plural_value, locale: plural.last + key: "#{key}.#{plural_key}", + value: plural_value, + locale: plural.last, ) end else @@ -218,7 +222,7 @@ class Admin::SiteTextsController < Admin::AdminController def fix_plural_keys(key, value, locale) value = value.with_indifferent_access - plural_keys = I18n.with_locale(locale) { I18n.t('i18n.plural.keys') } + plural_keys = I18n.with_locale(locale) { I18n.t("i18n.plural.keys") } return value if value.keys.size == plural_keys.size && plural_keys.all? { |k| value.key?(k) } fallback_value = I18n.t(key, locale: :en, default: {}) diff --git a/app/controllers/admin/staff_action_logs_controller.rb b/app/controllers/admin/staff_action_logs_controller.rb index a01e38ca82e..69687294ca9 100644 --- a/app/controllers/admin/staff_action_logs_controller.rb +++ b/app/controllers/admin/staff_action_logs_controller.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true class Admin::StaffActionLogsController < Admin::StaffController - def index - filters = params.slice(*UserHistory.staff_filters + [:page, :limit]) + filters = params.slice(*UserHistory.staff_filters + %i[page limit]) page = (params[:page] || 0).to_i page_size = (params[:limit] || 200).to_i.clamp(1, 200) @@ -20,8 +19,8 @@ class Admin::StaffActionLogsController < Admin::StaffController total_rows_staff_action_logs: count, load_more_staff_action_logs: admin_staff_action_logs_path(load_more_params), extras: { - user_history_actions: staff_available_actions - } + user_history_actions: staff_available_actions, + }, ) end @@ -37,16 +36,10 @@ class Admin::StaffActionLogsController < Admin::StaffController output = +"

#{CGI.escapeHTML(cur&.dig("name").to_s)}

" - diff_fields["name"] = { - prev: prev&.dig("name").to_s, - cur: cur&.dig("name").to_s, - } + diff_fields["name"] = { prev: prev&.dig("name").to_s, cur: cur&.dig("name").to_s } - ["default", "user_selectable"].each do |f| - diff_fields[f] = { - prev: (!!prev&.dig(f)).to_s, - cur: (!!cur&.dig(f)).to_s - } + %w[default user_selectable].each do |f| + diff_fields[f] = { prev: (!!prev&.dig(f)).to_s, cur: (!!cur&.dig(f)).to_s } end diff_fields["color scheme"] = { @@ -54,10 +47,7 @@ class Admin::StaffActionLogsController < Admin::StaffController cur: cur&.dig("color_scheme", "name").to_s, } - diff_fields["included themes"] = { - prev: child_themes(prev), - cur: child_themes(cur) - } + diff_fields["included themes"] = { prev: child_themes(prev), cur: child_themes(cur) } load_diff(diff_fields, :cur, cur) load_diff(diff_fields, :prev, prev) @@ -94,10 +84,7 @@ class Admin::StaffActionLogsController < Admin::StaffController def staff_available_actions UserHistory.staff_actions.sort.map do |name| - { - id: name, - action_id: UserHistory.actions[name] || UserHistory.actions[:custom_staff], - } + { id: name, action_id: UserHistory.actions[name] || UserHistory.actions[:custom_staff] } end end end diff --git a/app/controllers/admin/themes_controller.rb b/app/controllers/admin/themes_controller.rb index a47f2016d77..bf8ebb27d94 100644 --- a/app/controllers/admin/themes_controller.rb +++ b/app/controllers/admin/themes_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'base64' +require "base64" class Admin::ThemesController < Admin::AdminController - - skip_before_action :check_xhr, only: [:show, :preview, :export] + skip_before_action :check_xhr, only: %i[show preview export] before_action :ensure_admin def preview @@ -15,7 +14,6 @@ class Admin::ThemesController < Admin::AdminController end def upload_asset - ban_in_allowlist_mode! path = params[:file].path @@ -34,32 +32,30 @@ class Admin::ThemesController < Admin::AdminController end def generate_key_pair - require 'sshkey' + require "sshkey" k = SSHKey.generate Discourse.redis.setex("ssh_key_#{k.ssh_public_key}", 1.hour, k.private_key) render json: { public_key: k.ssh_public_key } end - THEME_CONTENT_TYPES ||= %w{ + THEME_CONTENT_TYPES ||= %w[ application/gzip application/x-gzip application/x-zip-compressed application/zip - } + ] def import @theme = nil if params[:theme] && params[:theme].content_type == "application/json" - ban_in_allowlist_mode! # .dcstyle.json import. Deprecated, but still available to allow conversion - json = JSON::parse(params[:theme].read) - theme = json['theme'] + json = JSON.parse(params[:theme].read) + theme = json["theme"] @theme = Theme.new(name: theme["name"], user_id: theme_user.id, auto_update: false) theme["theme_fields"]&.each do |field| - if field["raw_upload"] begin tmp = Tempfile.new @@ -79,7 +75,7 @@ class Admin::ThemesController < Admin::AdminController name: field["name"], value: field["value"], type_id: field["type_id"], - upload_id: field["upload_id"] + upload_id: field["upload_id"], ) end @@ -93,17 +89,22 @@ class Admin::ThemesController < Admin::AdminController begin guardian.ensure_allowed_theme_repo_import!(remote.strip) rescue Discourse::InvalidAccess - render_json_error I18n.t("themes.import_error.not_allowed_theme", { repo: remote.strip }), status: :forbidden + render_json_error I18n.t("themes.import_error.not_allowed_theme", { repo: remote.strip }), + status: :forbidden return end hijack do begin branch = params[:branch] ? params[:branch] : nil - private_key = params[:public_key] ? Discourse.redis.get("ssh_key_#{params[:public_key]}") : nil - return render_json_error I18n.t("themes.import_error.ssh_key_gone") if params[:public_key].present? && private_key.blank? + private_key = + params[:public_key] ? Discourse.redis.get("ssh_key_#{params[:public_key]}") : nil + if params[:public_key].present? && private_key.blank? + return render_json_error I18n.t("themes.import_error.ssh_key_gone") + end - @theme = RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch) + @theme = + RemoteTheme.import_theme(remote, theme_user, private_key: private_key, branch: branch) render json: @theme, status: :created rescue RemoteTheme::ImportError => e if params[:force] @@ -125,8 +126,8 @@ class Admin::ThemesController < Admin::AdminController end end end - elsif params[:bundle] || (params[:theme] && THEME_CONTENT_TYPES.include?(params[:theme].content_type)) - + elsif params[:bundle] || + (params[:theme] && THEME_CONTENT_TYPES.include?(params[:theme].content_type)) ban_in_allowlist_mode! # params[:bundle] used by theme CLI. params[:theme] used by admin UI @@ -135,21 +136,23 @@ class Admin::ThemesController < Admin::AdminController update_components = params[:components] match_theme_by_name = !!params[:bundle] && !params.key?(:theme_id) # Old theme CLI behavior, match by name. Remove Jan 2020 begin - @theme = RemoteTheme.update_zipped_theme( - bundle.path, - bundle.original_filename, - match_theme: match_theme_by_name, - user: theme_user, - theme_id: theme_id, - update_components: update_components - ) + @theme = + RemoteTheme.update_zipped_theme( + bundle.path, + bundle.original_filename, + match_theme: match_theme_by_name, + user: theme_user, + theme_id: theme_id, + update_components: update_components, + ) log_theme_change(nil, @theme) render json: @theme, status: :created rescue RemoteTheme::ImportError => e render_json_error e.message end else - render_json_error I18n.t("themes.import_error.unknown_file_type"), status: :unprocessable_entity + render_json_error I18n.t("themes.import_error.unknown_file_type"), + status: :unprocessable_entity end end @@ -160,24 +163,25 @@ class Admin::ThemesController < Admin::AdminController payload = { themes: ActiveModel::ArraySerializer.new(@themes, each_serializer: ThemeSerializer), extras: { - color_schemes: ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer) - } + color_schemes: + ActiveModel::ArraySerializer.new(@color_schemes, each_serializer: ColorSchemeSerializer), + }, } - respond_to do |format| - format.json { render json: payload } - end + respond_to { |format| format.json { render json: payload } } end def create - ban_in_allowlist_mode! - @theme = Theme.new(name: theme_params[:name], - user_id: theme_user.id, - user_selectable: theme_params[:user_selectable] || false, - color_scheme_id: theme_params[:color_scheme_id], - component: [true, "true"].include?(theme_params[:component])) + @theme = + Theme.new( + name: theme_params[:name], + user_id: theme_user.id, + user_selectable: theme_params[:user_selectable] || false, + color_scheme_id: theme_params[:color_scheme_id], + component: [true, "true"].include?(theme_params[:component]), + ) set_fields respond_to do |format| @@ -199,10 +203,8 @@ class Admin::ThemesController < Admin::AdminController disables_component = [false, "false"].include?(theme_params[:enabled]) enables_component = [true, "true"].include?(theme_params[:enabled]) - [:name, :color_scheme_id, :user_selectable, :enabled, :auto_update].each do |field| - if theme_params.key?(field) - @theme.public_send("#{field}=", theme_params[field]) - end + %i[name color_scheme_id user_selectable enabled auto_update].each do |field| + @theme.public_send("#{field}=", theme_params[field]) if theme_params.key?(field) end if theme_params.key?(:child_theme_ids) @@ -218,13 +220,9 @@ class Admin::ThemesController < Admin::AdminController update_translations handle_switch - if params[:theme][:remote_check] - @theme.remote_theme.update_remote_version - end + @theme.remote_theme.update_remote_version if params[:theme][:remote_check] - if params[:theme][:remote_update] - @theme.remote_theme.update_from_remote - end + @theme.remote_theme.update_from_remote if params[:theme][:remote_update] respond_to do |format| if @theme.save @@ -245,7 +243,7 @@ class Admin::ThemesController < Admin::AdminController error = I18n.t("themes.bad_color_scheme") if @theme.errors[:color_scheme].present? error ||= I18n.t("themes.other_error") - render json: { errors: [ error ] }, status: :unprocessable_entity + render json: { errors: [error] }, status: :unprocessable_entity end end end @@ -260,9 +258,7 @@ class Admin::ThemesController < Admin::AdminController StaffActionLogger.new(current_user).log_theme_destroy(@theme) @theme.destroy - respond_to do |format| - format.json { head :no_content } - end + respond_to { |format| format.json { head :no_content } } end def show @@ -279,10 +275,10 @@ class Admin::ThemesController < Admin::AdminController exporter = ThemeStore::ZipExporter.new(@theme) file_path = exporter.package_filename - headers['Content-Length'] = File.size(file_path).to_s + headers["Content-Length"] = File.size(file_path).to_s send_data File.read(file_path), - filename: File.basename(file_path), - content_type: "application/zip" + filename: File.basename(file_path), + content_type: "application/zip" ensure exporter.cleanup! end @@ -330,9 +326,7 @@ class Admin::ThemesController < Admin::AdminController end end - Theme.where(id: expected).each do |theme| - @theme.add_relative_theme!(kind, theme) - end + Theme.where(id: expected).each { |theme| @theme.add_relative_theme!(kind, theme) } end def update_default_theme @@ -361,11 +355,13 @@ class Admin::ThemesController < Admin::AdminController :component, :enabled, :auto_update, - settings: {}, - translations: {}, - theme_fields: [:name, :target, :value, :upload_id, :type_id], + settings: { + }, + translations: { + }, + theme_fields: %i[name target value upload_id type_id], child_theme_ids: [], - parent_theme_ids: [] + parent_theme_ids: [], ) end end @@ -382,7 +378,7 @@ class Admin::ThemesController < Admin::AdminController name: field[:name], value: field[:value], type_id: field[:type_id], - upload_id: field[:upload_id] + upload_id: field[:upload_id], ) end end @@ -408,7 +404,12 @@ class Admin::ThemesController < Admin::AdminController end def log_theme_setting_change(setting_name, previous_value, new_value) - StaffActionLogger.new(current_user).log_theme_setting_change(setting_name, previous_value, new_value, @theme) + StaffActionLogger.new(current_user).log_theme_setting_change( + setting_name, + previous_value, + new_value, + @theme, + ) end def log_theme_component_disabled @@ -422,10 +423,14 @@ class Admin::ThemesController < Admin::AdminController def handle_switch param = theme_params[:component] if param.to_s == "false" && @theme.component? - raise Discourse::InvalidParameters.new(:component) if @theme.id == SiteSetting.default_theme_id + if @theme.id == SiteSetting.default_theme_id + raise Discourse::InvalidParameters.new(:component) + end @theme.switch_to_theme! elsif param.to_s == "true" && !@theme.component? - raise Discourse::InvalidParameters.new(:component) if @theme.id == SiteSetting.default_theme_id + if @theme.id == SiteSetting.default_theme_id + raise Discourse::InvalidParameters.new(:component) + end @theme.switch_to_component! end end diff --git a/app/controllers/admin/user_fields_controller.rb b/app/controllers/admin/user_fields_controller.rb index 5d6cae243cd..b244867b7ae 100644 --- a/app/controllers/admin/user_fields_controller.rb +++ b/app/controllers/admin/user_fields_controller.rb @@ -1,9 +1,18 @@ # frozen_string_literal: true class Admin::UserFieldsController < Admin::AdminController - def self.columns - %i(name field_type editable description required show_on_profile show_on_user_card position searchable) + %i[ + name + field_type + editable + description + required + show_on_profile + show_on_user_card + position + searchable + ] end def create @@ -13,14 +22,12 @@ class Admin::UserFieldsController < Admin::AdminController field.required = params[:user_field][:required] == "true" update_options(field) - json_result(field, serializer: UserFieldSerializer) do - field.save - end + json_result(field, serializer: UserFieldSerializer) { field.save } end def index user_fields = UserField.all.includes(:user_field_options).order(:position) - render_serialized(user_fields, UserFieldSerializer, root: 'user_fields') + render_serialized(user_fields, UserFieldSerializer, root: "user_fields") end def update @@ -28,9 +35,7 @@ class Admin::UserFieldsController < Admin::AdminController field = UserField.where(id: params.require(:id)).first Admin::UserFieldsController.columns.each do |col| - unless field_params[col].nil? - field.public_send("#{col}=", field_params[col]) - end + field.public_send("#{col}=", field_params[col]) unless field_params[col].nil? end update_options(field) @@ -38,7 +43,7 @@ class Admin::UserFieldsController < Admin::AdminController if !field.show_on_profile && !field.show_on_user_card DirectoryColumn.where(user_field_id: field.id).destroy_all end - render_serialized(field, UserFieldSerializer, root: 'user_field') + render_serialized(field, UserFieldSerializer, root: "user_field") else render_json_error(field) end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 7d7526417de..45c8f9313cb 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -3,28 +3,31 @@ class Admin::UsersController < Admin::StaffController MAX_SIMILAR_USERS = 10 - before_action :fetch_user, only: [:suspend, - :unsuspend, - :log_out, - :revoke_admin, - :revoke_moderation, - :grant_moderation, - :approve, - :activate, - :deactivate, - :silence, - :unsilence, - :trust_level, - :trust_level_lock, - :add_group, - :remove_group, - :primary_group, - :anonymize, - :merge, - :reset_bounce_score, - :disable_second_factor, - :delete_posts_batch, - :sso_record] + before_action :fetch_user, + only: %i[ + suspend + unsuspend + log_out + revoke_admin + revoke_moderation + grant_moderation + approve + activate + deactivate + silence + unsilence + trust_level + trust_level_lock + add_group + remove_group + primary_group + anonymize + merge + reset_bounce_score + disable_second_factor + delete_posts_batch + sso_record + ] def index users = ::AdminUserIndexQuery.new(params).find_users @@ -42,9 +45,7 @@ class Admin::UsersController < Admin::StaffController @user = User.find_by(id: params[:id]) raise Discourse::NotFound unless @user - similar_users = User.real - .where.not(id: @user.id) - .where(ip_address: @user.ip_address) + similar_users = User.real.where.not(id: @user.id).where(ip_address: @user.ip_address) render_serialized( @user, @@ -64,7 +65,6 @@ class Admin::UsersController < Admin::StaffController # DELETE action to delete penalty history for a user def penalty_history - # We don't delete any history, we merely remove the action type # with a removed type. It can still be viewed in the logs but # will not affect TL3 promotions. @@ -87,16 +87,19 @@ class Admin::UsersController < Admin::StaffController DB.exec( sql, - UserHistory.actions.slice( - :silence_user, - :suspend_user, - :unsilence_user, - :unsuspend_user, - :removed_silence_user, - :removed_unsilence_user, - :removed_suspend_user, - :removed_unsuspend_user - ).merge(user_id: params[:user_id].to_i) + UserHistory + .actions + .slice( + :silence_user, + :suspend_user, + :unsilence_user, + :unsuspend_user, + :removed_silence_user, + :removed_unsilence_user, + :removed_suspend_user, + :removed_unsuspend_user, + ) + .merge(user_id: params[:user_id].to_i), ) render json: success_json @@ -107,14 +110,21 @@ class Admin::UsersController < Admin::StaffController if @user.suspended? suspend_record = @user.suspend_record - message = I18n.t("user.already_suspended", - staff: suspend_record.acting_user.username, - time_ago: FreedomPatches::Rails4.time_ago_in_words(suspend_record.created_at, true, scope: :'datetime.distance_in_words_verbose') - ) + message = + I18n.t( + "user.already_suspended", + staff: suspend_record.acting_user.username, + time_ago: + FreedomPatches::Rails4.time_ago_in_words( + suspend_record.created_at, + true, + scope: :"datetime.distance_in_words_verbose", + ), + ) return render json: failed_json.merge(message: message), status: 409 end - params.require([:suspend_until, :reason]) + params.require(%i[suspend_until reason]) all_users = [@user] if Array === params[:other_user_ids] @@ -133,12 +143,13 @@ class Admin::UsersController < Admin::StaffController User.transaction do user.save! - user_history = StaffActionLogger.new(current_user).log_user_suspend( - user, - params[:reason], - message: message, - post_id: params[:post_id] - ) + user_history = + StaffActionLogger.new(current_user).log_user_suspend( + user, + params[:reason], + message: message, + post_id: params[:post_id], + ) end user.logged_out @@ -147,7 +158,7 @@ class Admin::UsersController < Admin::StaffController :critical_user_email, type: "account_suspended", user_id: user.id, - user_history_id: user_history.id + user_history_id: user_history.id, ) end @@ -159,7 +170,7 @@ class Admin::UsersController < Admin::StaffController user_history: user_history, post_id: params[:post_id], suspended_till: params[:suspend_until], - suspended_at: DateTime.now + suspended_at: DateTime.now, ) end @@ -171,8 +182,8 @@ class Admin::UsersController < Admin::StaffController full_suspend_reason: user_history.try(:details), suspended_till: @user.suspended_till, suspended_at: @user.suspended_at, - suspended_by: BasicUserSerializer.new(current_user, root: false).as_json - } + suspended_by: BasicUserSerializer.new(current_user, root: false).as_json, + }, ) end @@ -185,12 +196,7 @@ class Admin::UsersController < Admin::StaffController DiscourseEvent.trigger(:user_unsuspended, user: @user) - render_json_dump( - suspension: { - suspended_till: nil, - suspended_at: nil - } - ) + render_json_dump(suspension: { suspended_till: nil, suspended_at: nil }) end def log_out @@ -199,7 +205,7 @@ class Admin::UsersController < Admin::StaffController @user.logged_out render json: success_json else - render json: { error: I18n.t('admin_js.admin.users.id_not_found') }, status: 404 + render json: { error: I18n.t("admin_js.admin.users.id_not_found") }, status: 404 end end @@ -237,7 +243,7 @@ class Admin::UsersController < Admin::StaffController group = Group.find(params[:group_id].to_i) raise Discourse::NotFound unless group - return render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) if group.automatic + return render_json_error(I18n.t("groups.errors.can_not_modify_automatic")) if group.automatic guardian.ensure_can_edit!(group) group.add(@user) @@ -250,7 +256,7 @@ class Admin::UsersController < Admin::StaffController group = Group.find(params[:group_id].to_i) raise Discourse::NotFound unless group - return render_json_error(I18n.t('groups.errors.can_not_modify_automatic')) if group.automatic + return render_json_error(I18n.t("groups.errors.can_not_modify_automatic")) if group.automatic guardian.ensure_can_edit!(group) if group.remove(@user) @@ -266,9 +272,7 @@ class Admin::UsersController < Admin::StaffController if group = Group.find(primary_group_id) guardian.ensure_can_change_primary_group!(@user, group) - if group.user_ids.include?(@user.id) - @user.primary_group_id = primary_group_id - end + @user.primary_group_id = primary_group_id if group.user_ids.include?(@user.id) end else @user.primary_group_id = nil @@ -304,9 +308,7 @@ class Admin::UsersController < Admin::StaffController guardian.ensure_can_change_trust_level!(@user) new_lock = params[:locked].to_s - unless new_lock =~ /true|false/ - return render_json_error I18n.t('errors.invalid_boolean') - end + return render_json_error I18n.t("errors.invalid_boolean") unless new_lock =~ /true|false/ @user.manual_locked_trust_level = (new_lock == "true") ? @user.trust_level : nil @user.save @@ -320,31 +322,38 @@ class Admin::UsersController < Admin::StaffController def approve guardian.ensure_can_approve!(@user) - reviewable = ReviewableUser.find_by(target: @user) || - Jobs::CreateUserReviewable.new.execute(user_id: @user.id).reviewable + reviewable = + ReviewableUser.find_by(target: @user) || + Jobs::CreateUserReviewable.new.execute(user_id: @user.id).reviewable reviewable.perform(current_user, :approve_user) render body: nil end def approve_bulk - Reviewable.bulk_perform_targets(current_user, :approve_user, 'ReviewableUser', params[:users]) + Reviewable.bulk_perform_targets(current_user, :approve_user, "ReviewableUser", params[:users]) render body: nil end def activate guardian.ensure_can_activate!(@user) # ensure there is an active email token - @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) if !@user.email_tokens.active.exists? + if !@user.email_tokens.active.exists? + @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) + end @user.activate - StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t('user.activated_by_staff')) + StaffActionLogger.new(current_user).log_user_activate(@user, I18n.t("user.activated_by_staff")) render json: success_json end def deactivate guardian.ensure_can_deactivate!(@user) @user.deactivate(current_user) - StaffActionLogger.new(current_user).log_user_deactivate(@user, I18n.t('user.deactivated_by_staff'), params.slice(:context)) + StaffActionLogger.new(current_user).log_user_deactivate( + @user, + I18n.t("user.deactivated_by_staff"), + params.slice(:context), + ) refresh_browser @user render json: success_json end @@ -354,10 +363,17 @@ class Admin::UsersController < Admin::StaffController if @user.silenced? silenced_record = @user.silenced_record - message = I18n.t("user.already_silenced", - staff: silenced_record.acting_user.username, - time_ago: FreedomPatches::Rails4.time_ago_in_words(silenced_record.created_at, true, scope: :'datetime.distance_in_words_verbose') - ) + message = + I18n.t( + "user.already_silenced", + staff: silenced_record.acting_user.username, + time_ago: + FreedomPatches::Rails4.time_ago_in_words( + silenced_record.created_at, + true, + scope: :"datetime.distance_in_words_verbose", + ), + ) return render json: failed_json.merge(message: message), status: 409 end @@ -370,15 +386,16 @@ class Admin::UsersController < Admin::StaffController user_history = nil all_users.each do |user| - silencer = UserSilencer.new( - user, - current_user, - silenced_till: params[:silenced_till], - reason: params[:reason], - message_body: params[:message], - keep_posts: true, - post_id: params[:post_id] - ) + silencer = + UserSilencer.new( + user, + current_user, + silenced_till: params[:silenced_till], + reason: params[:reason], + message_body: params[:message], + keep_posts: true, + post_id: params[:post_id], + ) if silencer.silence user_history = silencer.user_history @@ -386,7 +403,7 @@ class Admin::UsersController < Admin::StaffController :critical_user_email, type: "account_silenced", user_id: user.id, - user_history_id: user_history.id + user_history_id: user_history.id, ) end end @@ -399,8 +416,8 @@ class Admin::UsersController < Admin::StaffController silence_reason: user_history.try(:details), silenced_till: @user.silenced_till, silenced_at: @user.silenced_at, - silenced_by: BasicUserSerializer.new(current_user, root: false).as_json - } + silenced_by: BasicUserSerializer.new(current_user, root: false).as_json, + }, ) end @@ -413,8 +430,8 @@ class Admin::UsersController < Admin::StaffController silenced: false, silence_reason: nil, silenced_till: nil, - silenced_at: nil - } + silenced_at: nil, + }, ) end @@ -428,11 +445,7 @@ class Admin::UsersController < Admin::StaffController user_security_key.destroy_all StaffActionLogger.new(current_user).log_disable_second_factor_auth(@user) - Jobs.enqueue( - :critical_user_email, - type: "account_second_factor_disabled", - user_id: @user.id - ) + Jobs.enqueue(:critical_user_email, type: "account_second_factor_disabled", user_id: @user.id) render json: success_json end @@ -442,7 +455,7 @@ class Admin::UsersController < Admin::StaffController guardian.ensure_can_delete_user!(user) options = params.slice(:context, :delete_as_spammer) - [:delete_posts, :block_email, :block_urls, :block_ip].each do |param_name| + %i[delete_posts block_email block_urls block_ip].each do |param_name| options[param_name] = ActiveModel::Type::Boolean.new.cast(params[param_name]) end options[:prepare_for_destroy] = true @@ -453,15 +466,21 @@ class Admin::UsersController < Admin::StaffController render json: { deleted: true } else render json: { - deleted: false, - user: AdminDetailedUserSerializer.new(user, root: false).as_json - } + deleted: false, + user: AdminDetailedUserSerializer.new(user, root: false).as_json, + } end rescue UserDestroyer::PostsExistError render json: { - deleted: false, - message: I18n.t("user.cannot_delete_has_posts", username: user.username, count: user.posts.joins(:topic).count), - }, status: 403 + deleted: false, + message: + I18n.t( + "user.cannot_delete_has_posts", + username: user.username, + count: user.posts.joins(:topic).count, + ), + }, + status: 403 end end end @@ -482,9 +501,16 @@ class Admin::UsersController < Admin::StaffController return render body: nil, status: 404 unless SiteSetting.enable_discourse_connect begin - sso = DiscourseConnect.parse("sso=#{params[:sso]}&sig=#{params[:sig]}", secure_session: secure_session) + sso = + DiscourseConnect.parse( + "sso=#{params[:sso]}&sig=#{params[:sig]}", + secure_session: secure_session, + ) rescue DiscourseConnect::ParseError - return render json: failed_json.merge(message: I18n.t("discourse_connect.login_error")), status: 422 + return( + render json: failed_json.merge(message: I18n.t("discourse_connect.login_error")), + status: 422 + ) end begin @@ -494,7 +520,8 @@ class Admin::UsersController < Admin::StaffController rescue ActiveRecord::RecordInvalid => ex render json: failed_json.merge(message: ex.message), status: 403 rescue DiscourseConnect::BlankExternalId => ex - render json: failed_json.merge(message: I18n.t('discourse_connect.blank_id_error')), status: 422 + render json: failed_json.merge(message: I18n.t("discourse_connect.blank_id_error")), + status: 422 end end @@ -510,12 +537,13 @@ class Admin::UsersController < Admin::StaffController block_urls: true, block_ip: true, delete_as_spammer: true, - context: I18n.t("user.destroy_reasons.same_ip_address", ip_address: params[:ip]) + context: I18n.t("user.destroy_reasons.same_ip_address", ip_address: params[:ip]), } - AdminUserIndexQuery.new(params).find_users(50).each do |user| - user_destroyer.destroy(user, options) - end + AdminUserIndexQuery + .new(params) + .find_users(50) + .each { |user| user_destroyer.destroy(user, options) } render json: success_json end @@ -536,7 +564,8 @@ class Admin::UsersController < Admin::StaffController if user = UserAnonymizer.new(@user, current_user, opts).make_anonymous render json: success_json.merge(username: user.username) else - render json: failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json) + render json: + failed_json.merge(user: AdminDetailedUserSerializer.new(user, root: false).as_json) end end @@ -547,7 +576,12 @@ class Admin::UsersController < Admin::StaffController guardian.ensure_can_merge_users!(@user, target_user) - Jobs.enqueue(:merge_user, user_id: @user.id, target_user_id: target_user.id, current_user_id: current_user.id) + Jobs.enqueue( + :merge_user, + user_id: @user.id, + target_user_id: target_user.id, + current_user_id: current_user.id, + ) render json: success_json end @@ -566,24 +600,25 @@ class Admin::UsersController < Admin::StaffController private def perform_post_action - return unless params[:post_id].present? && - params[:post_action].present? + return unless params[:post_id].present? && params[:post_action].present? if post = Post.where(id: params[:post_id]).first case params[:post_action] - when 'delete' + when "delete" PostDestroyer.new(current_user, post).destroy if guardian.can_delete_post_or_topic?(post) when "delete_replies" - PostDestroyer.delete_with_replies(current_user, post) if guardian.can_delete_post_or_topic?(post) - when 'edit' + if guardian.can_delete_post_or_topic?(post) + PostDestroyer.delete_with_replies(current_user, post) + end + when "edit" revisor = PostRevisor.new(post) # Take what the moderator edited in as gospel revisor.revise!( current_user, - { raw: params[:post_edit] }, + { raw: params[:post_edit] }, skip_validations: true, - skip_revision: true + skip_revision: true, ) end end @@ -597,5 +632,4 @@ class Admin::UsersController < Admin::StaffController def refresh_browser(user) MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id] end - end diff --git a/app/controllers/admin/watched_words_controller.rb b/app/controllers/admin/watched_words_controller.rb index badfcd21735..2a5cbb496fd 100644 --- a/app/controllers/admin/watched_words_controller.rb +++ b/app/controllers/admin/watched_words_controller.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true -require 'csv' +require "csv" class Admin::WatchedWordsController < Admin::StaffController skip_before_action :check_xhr, only: [:download] def index watched_words = WatchedWord.by_action - watched_words = watched_words.where.not(action: WatchedWord.actions[:tag]) if !SiteSetting.tagging_enabled + watched_words = + watched_words.where.not(action: WatchedWord.actions[:tag]) if !SiteSetting.tagging_enabled render_json_dump WatchedWordListSerializer.new(watched_words, scope: guardian, root: false) end @@ -38,19 +39,20 @@ class Admin::WatchedWordsController < Admin::StaffController begin CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row| if row[0].present? && (!has_replacement || row[1].present?) - watched_word = WatchedWord.create_or_update_word( - word: row[0], - replacement: has_replacement ? row[1] : nil, - action_key: action_key, - case_sensitive: "true" == row[2]&.strip&.downcase - ) + watched_word = + WatchedWord.create_or_update_word( + word: row[0], + replacement: has_replacement ? row[1] : nil, + action_key: action_key, + case_sensitive: "true" == row[2]&.strip&.downcase, + ) if watched_word.valid? StaffActionLogger.new(current_user).log_watched_words_creation(watched_word) end end end - data = { url: '/ok' } + data = { url: "/ok" } rescue => e data = failed_json.merge(errors: [e.message]) end @@ -73,10 +75,10 @@ class Admin::WatchedWordsController < Admin::StaffController content = content.pluck(:word).join("\n") end - headers['Content-Length'] = content.bytesize.to_s + headers["Content-Length"] = content.bytesize.to_s send_data content, - filename: "#{Discourse.current_hostname}-watched-words-#{name}.csv", - content_type: "text/csv" + filename: "#{Discourse.current_hostname}-watched-words-#{name}.csv", + content_type: "text/csv" end def clear_all @@ -85,10 +87,12 @@ class Admin::WatchedWordsController < Admin::StaffController action = WatchedWord.actions[name] raise Discourse::NotFound if !action - WatchedWord.where(action: action).find_each do |watched_word| - watched_word.destroy! - StaffActionLogger.new(current_user).log_watched_words_deletion(watched_word) - end + WatchedWord + .where(action: action) + .find_each do |watched_word| + watched_word.destroy! + StaffActionLogger.new(current_user).log_watched_words_deletion(watched_word) + end WordWatcher.clear_cache! render json: success_json end diff --git a/app/controllers/admin/web_hooks_controller.rb b/app/controllers/admin/web_hooks_controller.rb index cb747112bd4..1ca6e81a268 100644 --- a/app/controllers/admin/web_hooks_controller.rb +++ b/app/controllers/admin/web_hooks_controller.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true class Admin::WebHooksController < Admin::AdminController - before_action :fetch_web_hook, only: %i(show update destroy list_events bulk_events ping) + before_action :fetch_web_hook, only: %i[show update destroy list_events bulk_events ping] def index limit = 50 offset = params[:offset].to_i - web_hooks = WebHook.limit(limit) - .offset(offset) - .includes(:web_hook_event_types) - .includes(:categories) - .includes(:groups) + web_hooks = + WebHook + .limit(limit) + .offset(offset) + .includes(:web_hook_event_types) + .includes(:categories) + .includes(:groups) json = { web_hooks: serialize_data(web_hooks, AdminWebHookSerializer), @@ -19,29 +21,34 @@ class Admin::WebHooksController < Admin::AdminController event_types: WebHookEventType.active, default_event_types: WebHook.default_event_types, content_types: WebHook.content_types.map { |name, id| { id: id, name: name } }, - delivery_statuses: WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } }, + delivery_statuses: + WebHook.last_delivery_statuses.map { |name, id| { id: id, name: name.to_s } }, }, total_rows_web_hooks: WebHook.count, - load_more_web_hooks: admin_web_hooks_path(limit: limit, offset: offset + limit, format: :json) + load_more_web_hooks: + admin_web_hooks_path(limit: limit, offset: offset + limit, format: :json), } render json: MultiJson.dump(json), status: 200 end def show - render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') + render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook") end def edit - render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') + render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook") end def create web_hook = WebHook.new(web_hook_params) if web_hook.save - StaffActionLogger.new(current_user).log_web_hook(web_hook, UserHistory.actions[:web_hook_create]) - render_serialized(web_hook, AdminWebHookSerializer, root: 'web_hook') + StaffActionLogger.new(current_user).log_web_hook( + web_hook, + UserHistory.actions[:web_hook_create], + ) + render_serialized(web_hook, AdminWebHookSerializer, root: "web_hook") else render_json_error web_hook.errors.full_messages end @@ -49,8 +56,12 @@ class Admin::WebHooksController < Admin::AdminController def update if @web_hook.update(web_hook_params) - StaffActionLogger.new(current_user).log_web_hook(@web_hook, UserHistory.actions[:web_hook_update], changes: @web_hook.saved_changes) - render_serialized(@web_hook, AdminWebHookSerializer, root: 'web_hook') + StaffActionLogger.new(current_user).log_web_hook( + @web_hook, + UserHistory.actions[:web_hook_update], + changes: @web_hook.saved_changes, + ) + render_serialized(@web_hook, AdminWebHookSerializer, root: "web_hook") else render_json_error @web_hook.errors.full_messages end @@ -58,7 +69,10 @@ class Admin::WebHooksController < Admin::AdminController def destroy @web_hook.destroy! - StaffActionLogger.new(current_user).log_web_hook(@web_hook, UserHistory.actions[:web_hook_destroy]) + StaffActionLogger.new(current_user).log_web_hook( + @web_hook, + UserHistory.actions[:web_hook_destroy], + ) render json: success_json end @@ -67,12 +81,17 @@ class Admin::WebHooksController < Admin::AdminController offset = params[:offset].to_i json = { - web_hook_events: serialize_data(@web_hook.web_hook_events.limit(limit).offset(offset), AdminWebHookEventSerializer), + web_hook_events: + serialize_data( + @web_hook.web_hook_events.limit(limit).offset(offset), + AdminWebHookEventSerializer, + ), total_rows_web_hook_events: @web_hook.web_hook_events.count, - load_more_web_hook_events: web_hook_events_admin_api_index_path(limit: limit, offset: offset + limit, format: :json), + load_more_web_hook_events: + web_hook_events_admin_api_index_path(limit: limit, offset: offset + limit, format: :json), extras: { - web_hook_id: @web_hook.id - } + web_hook_id: @web_hook.id, + }, } render json: MultiJson.dump(json), status: 200 @@ -91,26 +110,37 @@ class Admin::WebHooksController < Admin::AdminController web_hook = web_hook_event.web_hook emitter = WebHookEmitter.new(web_hook, web_hook_event) emitter.emit!(headers: MultiJson.load(web_hook_event.headers), body: web_hook_event.payload) - render_serialized(web_hook_event, AdminWebHookEventSerializer, root: 'web_hook_event') + render_serialized(web_hook_event, AdminWebHookEventSerializer, root: "web_hook_event") else render json: failed_json end end def ping - Jobs.enqueue(:emit_web_hook_event, web_hook_id: @web_hook.id, event_type: 'ping', event_name: 'ping') + Jobs.enqueue( + :emit_web_hook_event, + web_hook_id: @web_hook.id, + event_type: "ping", + event_name: "ping", + ) render json: success_json end private def web_hook_params - params.require(:web_hook).permit(:payload_url, :content_type, :secret, - :wildcard_web_hook, :active, :verify_certificate, - web_hook_event_type_ids: [], - group_ids: [], - tag_names: [], - category_ids: []) + params.require(:web_hook).permit( + :payload_url, + :content_type, + :secret, + :wildcard_web_hook, + :active, + :verify_certificate, + web_hook_event_type_ids: [], + group_ids: [], + tag_names: [], + category_ids: [], + ) end def fetch_web_hook diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0ab26fef897..41bd4889bc7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'current_user' +require "current_user" class ApplicationController < ActionController::Base include CurrentUser @@ -43,17 +43,18 @@ class ApplicationController < ActionController::Base before_action :block_if_requires_login before_action :preload_json before_action :check_xhr - after_action :add_readonly_header - after_action :perform_refresh_session - after_action :dont_cache_page - after_action :conditionally_allow_site_embedding - after_action :ensure_vary_header - after_action :add_noindex_header, if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt } - after_action :add_noindex_header_to_non_canonical, if: :spa_boot_request? + after_action :add_readonly_header + after_action :perform_refresh_session + after_action :dont_cache_page + after_action :conditionally_allow_site_embedding + after_action :ensure_vary_header + after_action :add_noindex_header, + if: -> { is_feed_request? || !SiteSetting.allow_index_in_robots_txt } + after_action :add_noindex_header_to_non_canonical, if: :spa_boot_request? around_action :link_preload, if: -> { spa_boot_request? && GlobalSetting.preload_link_header } - HONEYPOT_KEY ||= 'HONEYPOT_KEY' - CHALLENGE_KEY ||= 'CHALLENGE_KEY' + HONEYPOT_KEY ||= "HONEYPOT_KEY" + CHALLENGE_KEY ||= "CHALLENGE_KEY" layout :set_layout @@ -68,12 +69,12 @@ class ApplicationController < ActionController::Base def use_crawler_layout? @use_crawler_layout ||= - request.user_agent && - (request.media_type.blank? || request.media_type.include?('html')) && - !['json', 'rss'].include?(params[:format]) && - (has_escaped_fragment? || params.key?("print") || show_browser_update? || - CrawlerDetection.crawler?(request.user_agent, request.headers["HTTP_VIA"]) - ) + request.user_agent && (request.media_type.blank? || request.media_type.include?("html")) && + !%w[json rss].include?(params[:format]) && + ( + has_escaped_fragment? || params.key?("print") || show_browser_update? || + CrawlerDetection.crawler?(request.user_agent, request.headers["HTTP_VIA"]) + ) end def perform_refresh_session @@ -91,19 +92,16 @@ class ApplicationController < ActionController::Base response.cache_control[:no_cache] = true response.cache_control[:extras] = ["no-store"] end - if SiteSetting.login_required - response.headers['Discourse-No-Onebox'] = '1' - end + response.headers["Discourse-No-Onebox"] = "1" if SiteSetting.login_required end def conditionally_allow_site_embedding - if SiteSetting.allow_embedding_site_in_an_iframe - response.headers.delete('X-Frame-Options') - end + response.headers.delete("X-Frame-Options") if SiteSetting.allow_embedding_site_in_an_iframe end def ember_cli_required? - Rails.env.development? && ENV["ALLOW_EMBER_CLI_PROXY_BYPASS"] != "1" && request.headers['X-Discourse-Ember-CLI'] != 'true' + Rails.env.development? && ENV["ALLOW_EMBER_CLI_PROXY_BYPASS"] != "1" && + request.headers["X-Discourse-Ember-CLI"] != "true" end def application_layout @@ -118,14 +116,16 @@ class ApplicationController < ActionController::Base return "crawler" end - use_crawler_layout? ? 'crawler' : application_layout + use_crawler_layout? ? "crawler" : application_layout end - class RenderEmpty < StandardError; end - class PluginDisabled < StandardError; end + class RenderEmpty < StandardError + end + class PluginDisabled < StandardError + end rescue_from RenderEmpty do - with_resolved_locale { render 'default/empty' } + with_resolved_locale { render "default/empty" } end rescue_from ArgumentError do |e| @@ -147,10 +147,10 @@ class ApplicationController < ActionController::Base end rescue_from Discourse::SiteSettingMissing do |e| - render_json_error I18n.t('site_setting_missing', name: e.message), status: 500 + render_json_error I18n.t("site_setting_missing", name: e.message), status: 500 end - rescue_from ActionController::RoutingError, PluginDisabled do + rescue_from ActionController::RoutingError, PluginDisabled do rescue_discourse_actions(:not_found, 404) end @@ -180,21 +180,20 @@ class ApplicationController < ActionController::Base rescue_from RateLimiter::LimitExceeded do |e| retry_time_in_seconds = e&.available_in - response_headers = { - 'Retry-After': retry_time_in_seconds.to_s - } + response_headers = { "Retry-After": retry_time_in_seconds.to_s } - if e&.error_code - response_headers['Discourse-Rate-Limit-Error-Code'] = e.error_code - end + response_headers["Discourse-Rate-Limit-Error-Code"] = e.error_code if e&.error_code with_resolved_locale do render_json_error( e.description, type: :rate_limit, status: 429, - extras: { wait_seconds: retry_time_in_seconds, time_left: e&.time_left }, - headers: response_headers + extras: { + wait_seconds: retry_time_in_seconds, + time_left: e&.time_left, + }, + headers: response_headers, ) end end @@ -208,10 +207,7 @@ class ApplicationController < ActionController::Base end rescue_from Discourse::InvalidParameters do |e| - opts = { - custom_message: 'invalid_params', - custom_message_params: { message: e.message } - } + opts = { custom_message: "invalid_params", custom_message_params: { message: e.message } } if (request.format && request.format.json?) || request.xhr? || !request.get? rescue_discourse_actions(:invalid_parameters, 400, opts.merge(include_ember: true)) @@ -226,14 +222,12 @@ class ApplicationController < ActionController::Base e.status, check_permalinks: e.check_permalinks, original_path: e.original_path, - custom_message: e.custom_message + custom_message: e.custom_message, ) end rescue_from Discourse::InvalidAccess do |e| - if e.opts[:delete_cookie].present? - cookies.delete(e.opts[:delete_cookie]) - end + cookies.delete(e.opts[:delete_cookie]) if e.opts[:delete_cookie].present? rescue_discourse_actions( :invalid_access, @@ -241,7 +235,7 @@ class ApplicationController < ActionController::Base include_ember: true, custom_message: e.custom_message, custom_message_params: e.custom_message_params, - group: e.group + group: e.group, ) end @@ -249,20 +243,16 @@ class ApplicationController < ActionController::Base unless response_body respond_to do |format| format.json do - render_json_error I18n.t('read_only_mode_enabled'), type: :read_only, status: 503 - end - format.html do - render status: 503, layout: 'no_ember', template: 'exceptions/read_only' + render_json_error I18n.t("read_only_mode_enabled"), type: :read_only, status: 503 end + format.html { render status: 503, layout: "no_ember", template: "exceptions/read_only" } end end end rescue_from SecondFactor::AuthManager::SecondFactorRequired do |e| if request.xhr? - render json: { - second_factor_challenge_nonce: e.nonce - }, status: 403 + render json: { second_factor_challenge_nonce: e.nonce }, status: 403 else redirect_to session_2fa_path(nonce: e.nonce) end @@ -274,7 +264,7 @@ class ApplicationController < ActionController::Base def redirect_with_client_support(url, options = {}) if request.xhr? - response.headers['Discourse-Xhr-Redirect'] = 'true' + response.headers["Discourse-Xhr-Redirect"] = "true" render plain: url else redirect_to url, options @@ -283,9 +273,9 @@ class ApplicationController < ActionController::Base def rescue_discourse_actions(type, status_code, opts = nil) opts ||= {} - show_json_errors = (request.format && request.format.json?) || - (request.xhr?) || - ((params[:external_id] || '').ends_with? '.json') + show_json_errors = + (request.format && request.format.json?) || (request.xhr?) || + ((params[:external_id] || "").ends_with? ".json") if type == :not_found && opts[:check_permalinks] url = opts[:original_path] || request.fullpath @@ -295,7 +285,9 @@ class ApplicationController < ActionController::Base # cause category / topic was deleted if permalink.present? && permalink.target_url # permalink present, redirect to that URL - redirect_with_client_support permalink.target_url, status: :moved_permanently, allow_other_host: true + redirect_with_client_support permalink.target_url, + status: :moved_permanently, + allow_other_host: true return end end @@ -321,11 +313,15 @@ class ApplicationController < ActionController::Base with_resolved_locale(check_current_user: false) do # Include error in HTML format for topics#show. - if (request.params[:controller] == 'topics' && request.params[:action] == 'show') || (request.params[:controller] == 'categories' && request.params[:action] == 'find_by_slug') + if (request.params[:controller] == "topics" && request.params[:action] == "show") || + ( + request.params[:controller] == "categories" && + request.params[:action] == "find_by_slug" + ) opts[:extras] = { - title: I18n.t('page_not_found.page_title'), + title: I18n.t("page_not_found.page_title"), html: build_not_found_page(error_page_opts), - group: error_page_opts[:group] + group: error_page_opts[:group], } end end @@ -340,7 +336,7 @@ class ApplicationController < ActionController::Base return render plain: message, status: status_code end with_resolved_locale do - error_page_opts[:layout] = (opts[:include_ember] && @preloaded) ? 'application' : 'no_ember' + error_page_opts[:layout] = (opts[:include_ember] && @preloaded) ? "application" : "no_ember" render html: build_not_found_page(error_page_opts) end end @@ -373,9 +369,8 @@ class ApplicationController < ActionController::Base def clear_notifications if current_user && !@readonly_mode - - cookie_notifications = cookies['cn'] - notifications = request.headers['Discourse-Clear-Notifications'] + cookie_notifications = cookies["cn"] + notifications = request.headers["Discourse-Clear-Notifications"] if cookie_notifications if notifications.present? @@ -392,22 +387,28 @@ class ApplicationController < ActionController::Base current_user.publish_notifications_state cookie_args = {} cookie_args[:path] = Discourse.base_path if Discourse.base_path.present? - cookies.delete('cn', cookie_args) + cookies.delete("cn", cookie_args) end end end def with_resolved_locale(check_current_user: true) - if check_current_user && (user = current_user rescue nil) + if check_current_user && + ( + user = + begin + current_user + rescue StandardError + nil + end + ) locale = user.effective_locale else locale = Discourse.anonymous_locale(request) locale ||= SiteSetting.default_locale end - if !I18n.locale_available?(locale) - locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE - end + locale = SiteSettings::DefaultsProvider::DEFAULT_LOCALE if !I18n.locale_available?(locale) I18n.ensure_all_loaded! I18n.with_locale(locale) { yield } @@ -458,7 +459,8 @@ class ApplicationController < ActionController::Base safe_mode = safe_mode.split(",") request.env[NO_THEMES] = safe_mode.include?(NO_THEMES) || safe_mode.include?(LEGACY_NO_THEMES) request.env[NO_PLUGINS] = safe_mode.include?(NO_PLUGINS) - request.env[NO_UNOFFICIAL_PLUGINS] = safe_mode.include?(NO_UNOFFICIAL_PLUGINS) || safe_mode.include?(LEGACY_NO_UNOFFICIAL_PLUGINS) + request.env[NO_UNOFFICIAL_PLUGINS] = safe_mode.include?(NO_UNOFFICIAL_PLUGINS) || + safe_mode.include?(LEGACY_NO_UNOFFICIAL_PLUGINS) end end @@ -471,8 +473,7 @@ class ApplicationController < ActionController::Base theme_id = nil if (preview_theme_id = request[:preview_theme_id]&.to_i) && - guardian.allow_themes?([preview_theme_id], include_preview: true) - + guardian.allow_themes?([preview_theme_id], include_preview: true) theme_id = preview_theme_id end @@ -491,7 +492,8 @@ class ApplicationController < ActionController::Base theme_id = ids.first if guardian.allow_themes?(ids) end - if theme_id.blank? && SiteSetting.default_theme_id != -1 && guardian.allow_themes?([SiteSetting.default_theme_id]) + if theme_id.blank? && SiteSetting.default_theme_id != -1 && + guardian.allow_themes?([SiteSetting.default_theme_id]) theme_id = SiteSetting.default_theme_id end @@ -533,13 +535,11 @@ class ApplicationController < ActionController::Base def render_json_dump(obj, opts = nil) opts ||= {} if opts[:rest_serializer] - obj['__rest_serializer'] = "1" - opts.each do |k, v| - obj[k] = v if k.to_s.start_with?("refresh_") - end + obj["__rest_serializer"] = "1" + opts.each { |k, v| obj[k] = v if k.to_s.start_with?("refresh_") } - obj['extras'] = opts[:extras] if opts[:extras] - obj['meta'] = opts[:meta] if opts[:meta] + obj["extras"] = opts[:extras] if opts[:extras] + obj["meta"] = opts[:meta] if opts[:meta] end render json: MultiJson.dump(obj), status: opts[:status] || 200 @@ -557,29 +557,33 @@ class ApplicationController < ActionController::Base def fetch_user_from_params(opts = nil, eager_load = []) opts ||= {} - user = if params[:username] - username_lower = params[:username].downcase.chomp('.json') + user = + if params[:username] + username_lower = params[:username].downcase.chomp(".json") - if current_user && current_user.username_lower == username_lower - current_user - else - find_opts = { username_lower: username_lower } - find_opts[:active] = true unless opts[:include_inactive] || current_user.try(:staff?) - result = User - (result = result.includes(*eager_load)) if !eager_load.empty? - result.find_by(find_opts) + if current_user && current_user.username_lower == username_lower + current_user + else + find_opts = { username_lower: username_lower } + find_opts[:active] = true unless opts[:include_inactive] || current_user.try(:staff?) + result = User + (result = result.includes(*eager_load)) if !eager_load.empty? + result.find_by(find_opts) + end + elsif params[:external_id] + external_id = params[:external_id].chomp(".json") + if provider_name = params[:external_provider] + raise Discourse::InvalidAccess unless guardian.is_admin? # external_id might be something sensitive + provider = Discourse.enabled_authenticators.find { |a| a.name == provider_name } + raise Discourse::NotFound if !provider&.is_managed? # Only managed authenticators use UserAssociatedAccount + UserAssociatedAccount.find_by( + provider_name: provider_name, + provider_uid: external_id, + )&.user + else + SingleSignOnRecord.find_by(external_id: external_id).try(:user) + end end - elsif params[:external_id] - external_id = params[:external_id].chomp('.json') - if provider_name = params[:external_provider] - raise Discourse::InvalidAccess unless guardian.is_admin? # external_id might be something sensitive - provider = Discourse.enabled_authenticators.find { |a| a.name == provider_name } - raise Discourse::NotFound if !provider&.is_managed? # Only managed authenticators use UserAssociatedAccount - UserAssociatedAccount.find_by(provider_name: provider_name, provider_uid: external_id)&.user - else - SingleSignOnRecord.find_by(external_id: external_id).try(:user) - end - end raise Discourse::NotFound if user.blank? guardian.ensure_can_see!(user) @@ -587,15 +591,17 @@ class ApplicationController < ActionController::Base end def post_ids_including_replies - post_ids = params[:post_ids].map(&:to_i) - post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_post_id) if params[:reply_post_ids] + post_ids = params[:post_ids].map(&:to_i) + post_ids |= PostReply.where(post_id: params[:reply_post_ids]).pluck(:reply_post_id) if params[ + :reply_post_ids + ] post_ids end def no_cookies # do your best to ensure response has no cookies # longer term we may want to push this into middleware - headers.delete 'Set-Cookie' + headers.delete "Set-Cookie" request.session_options[:skip] = true end @@ -615,9 +621,7 @@ class ApplicationController < ActionController::Base RateLimiter.new(nil, "second-factor-min-#{request.remote_ip}", 6, 1.minute).performed! - if user - RateLimiter.new(nil, "second-factor-min-#{user.username}", 6, 1.minute).performed! - end + RateLimiter.new(nil, "second-factor-min-#{user.username}", 6, 1.minute).performed! if user end private @@ -634,11 +638,17 @@ class ApplicationController < ActionController::Base end def preload_current_user_data - store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false))) - report = TopicTrackingState.report(current_user) - serializer = ActiveModel::ArraySerializer.new( - report, each_serializer: TopicTrackingStateSerializer, scope: guardian + store_preloaded( + "currentUser", + MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)), ) + report = TopicTrackingState.report(current_user) + serializer = + ActiveModel::ArraySerializer.new( + report, + each_serializer: TopicTrackingStateSerializer, + scope: guardian, + ) store_preloaded("topicTrackingStates", MultiJson.dump(serializer)) end @@ -648,20 +658,18 @@ class ApplicationController < ActionController::Base data = if @theme_id.present? { - top: Theme.lookup_field(@theme_id, target, "after_header"), - footer: Theme.lookup_field(@theme_id, target, "footer") + top: Theme.lookup_field(@theme_id, target, "after_header"), + footer: Theme.lookup_field(@theme_id, target, "footer"), } else {} end - if DiscoursePluginRegistry.custom_html - data.merge! DiscoursePluginRegistry.custom_html - end + data.merge! DiscoursePluginRegistry.custom_html if DiscoursePluginRegistry.custom_html DiscoursePluginRegistry.html_builders.each do |name, _| if name.start_with?("client:") - data[name.sub(/^client:/, '')] = DiscoursePluginRegistry.build_html(name, self) + data[name.sub(/^client:/, "")] = DiscoursePluginRegistry.build_html(name, self) end end @@ -703,7 +711,7 @@ class ApplicationController < ActionController::Base render( json: MultiJson.dump(create_errors_json(obj, opts)), - status: opts[:status] || status_code(obj) + status: opts[:status] || status_code(obj), ) end @@ -714,11 +722,11 @@ class ApplicationController < ActionController::Base end def success_json - { success: 'OK' } + { success: "OK" } end def failed_json - { failed: 'FAILED' } + { failed: "FAILED" } end def json_result(obj, opts = {}) @@ -727,17 +735,21 @@ class ApplicationController < ActionController::Base # If we were given a serializer, add the class to the json that comes back if opts[:serializer].present? - json[obj.class.name.underscore] = opts[:serializer].new(obj, scope: guardian).serializable_hash + json[obj.class.name.underscore] = opts[:serializer].new( + obj, + scope: guardian, + ).serializable_hash end render json: MultiJson.dump(json) else error_obj = nil if opts[:additional_errors] - error_target = opts[:additional_errors].find do |o| - target = obj.public_send(o) - target && target.errors.present? - end + error_target = + opts[:additional_errors].find do |o| + target = obj.public_send(o) + target && target.errors.present? + end error_obj = obj.public_send(error_target) if error_target end render_json_error(error_obj || obj) @@ -756,11 +768,15 @@ class ApplicationController < ActionController::Base def check_xhr # bypass xhr check on PUT / POST / DELETE provided api key is there, otherwise calling api is annoying return if !request.get? && (is_api? || is_user_api?) - raise ApplicationController::RenderEmpty.new unless ((request.format && request.format.json?) || request.xhr?) + unless ((request.format && request.format.json?) || request.xhr?) + raise ApplicationController::RenderEmpty.new + end end def apply_cdn_headers - Discourse.apply_cdn_headers(response.headers) if Discourse.is_cdn_request?(request.env, request.method) + if Discourse.is_cdn_request?(request.env, request.method) + Discourse.apply_cdn_headers(response.headers) + end end def self.requires_login(arg = {}) @@ -811,8 +827,9 @@ class ApplicationController < ActionController::Base if SiteSetting.auth_immediately && SiteSetting.enable_discourse_connect? # save original URL in a session so we can redirect after login session[:destination_url] = destination_url - redirect_to path('/session/sso') - elsif SiteSetting.auth_immediately && !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1 && !cookies[:authentication_data] + redirect_to path("/session/sso") + elsif SiteSetting.auth_immediately && !SiteSetting.enable_local_logins && + Discourse.enabled_authenticators.length == 1 && !cookies[:authentication_data] # Only one authentication provider, direct straight to it. # If authentication_data is present, then we are halfway though registration. Don't redirect offsite cookies[:destination_url] = destination_url @@ -831,9 +848,7 @@ class ApplicationController < ActionController::Base # Redirects to provided URL scheme if # - request uses a valid public key and auth_redirect scheme # - one_time_password scope is allowed - if !current_user && - params.has_key?(:user_api_public_key) && - params.has_key?(:auth_redirect) + if !current_user && params.has_key?(:user_api_public_key) && params.has_key?(:auth_redirect) begin OpenSSL::PKey::RSA.new(params[:user_api_public_key]) rescue OpenSSL::PKey::RSAError @@ -872,26 +887,45 @@ class ApplicationController < ActionController::Base def should_enforce_2fa? disqualified_from_2fa_enforcement = request.format.json? || is_api? || current_user.anonymous? - enforcing_2fa = ((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) || SiteSetting.enforce_second_factor == 'all') - !disqualified_from_2fa_enforcement && enforcing_2fa && !current_user.has_any_second_factor_methods_enabled? + enforcing_2fa = + ( + (SiteSetting.enforce_second_factor == "staff" && current_user.staff?) || + SiteSetting.enforce_second_factor == "all" + ) + !disqualified_from_2fa_enforcement && enforcing_2fa && + !current_user.has_any_second_factor_methods_enabled? end def build_not_found_page(opts = {}) if SiteSetting.bootstrap_error_pages? preload_json - opts[:layout] = 'application' if opts[:layout] == 'no_ember' + opts[:layout] = "application" if opts[:layout] == "no_ember" end - @current_user = current_user rescue nil + @current_user = + begin + current_user + rescue StandardError + nil + end if !SiteSetting.login_required? || @current_user key = "page_not_found_topics:#{I18n.locale}" - @topics_partial = Discourse.cache.fetch(key, expires_in: 10.minutes) do - category_topic_ids = Category.pluck(:topic_id).compact - @top_viewed = TopicQuery.new(nil, except_topic_ids: category_topic_ids).list_top_for("monthly").topics.first(10) - @recent = Topic.includes(:category).where.not(id: category_topic_ids).recent(10) - render_to_string partial: '/exceptions/not_found_topics', formats: [:html] - end.html_safe + @topics_partial = + Discourse + .cache + .fetch(key, expires_in: 10.minutes) do + category_topic_ids = Category.pluck(:topic_id).compact + @top_viewed = + TopicQuery + .new(nil, except_topic_ids: category_topic_ids) + .list_top_for("monthly") + .topics + .first(10) + @recent = Topic.includes(:category).where.not(id: category_topic_ids).recent(10) + render_to_string partial: "/exceptions/not_found_topics", formats: [:html] + end + .html_safe end @container_class = "wrap not-found-container" @@ -902,13 +936,16 @@ class ApplicationController < ActionController::Base params[:slug] = params[:slug].first if params[:slug].kind_of?(Array) params[:id] = params[:id].first if params[:id].kind_of?(Array) - @slug = (params[:slug].presence || params[:id].presence || "").to_s.tr('-', ' ') + @slug = (params[:slug].presence || params[:id].presence || "").to_s.tr("-", " ") - render_to_string status: opts[:status], layout: opts[:layout], formats: [:html], template: '/exceptions/not_found' + render_to_string status: opts[:status], + layout: opts[:layout], + formats: [:html], + template: "/exceptions/not_found" end def is_asset_path - request.env['DISCOURSE_IS_ASSET_PATH'] = 1 + request.env["DISCOURSE_IS_ASSET_PATH"] = 1 end def is_feed_request? @@ -916,19 +953,20 @@ class ApplicationController < ActionController::Base end def add_noindex_header - if request.get? && !response.headers['X-Robots-Tag'] + if request.get? && !response.headers["X-Robots-Tag"] if SiteSetting.allow_index_in_robots_txt - response.headers['X-Robots-Tag'] = 'noindex' + response.headers["X-Robots-Tag"] = "noindex" else - response.headers['X-Robots-Tag'] = 'noindex, nofollow' + response.headers["X-Robots-Tag"] = "noindex, nofollow" end end end def add_noindex_header_to_non_canonical canonical = (@canonical_url || @default_canonical) - if canonical.present? && canonical != request.url && !SiteSetting.allow_indexing_non_canonical_urls - response.headers['X-Robots-Tag'] ||= 'noindex' + if canonical.present? && canonical != request.url && + !SiteSetting.allow_indexing_non_canonical_urls + response.headers["X-Robots-Tag"] ||= "noindex" end end @@ -955,7 +993,7 @@ class ApplicationController < ActionController::Base # returns an array of integers given a param key # returns nil if key is not found - def param_to_integer_list(key, delimiter = ',') + def param_to_integer_list(key, delimiter = ",") case params[key] when String params[key].split(delimiter).map(&:to_i) @@ -978,20 +1016,19 @@ class ApplicationController < ActionController::Base user_agent = request.user_agent&.downcase return if user_agent.blank? - SiteSetting.slow_down_crawler_user_agents.downcase.split("|").each do |crawler| - if user_agent.include?(crawler) - key = "#{crawler}_crawler_rate_limit" - limiter = RateLimiter.new( - nil, - key, - 1, - SiteSetting.slow_down_crawler_rate, - error_code: key - ) - limiter.performed! - break + SiteSetting + .slow_down_crawler_user_agents + .downcase + .split("|") + .each do |crawler| + if user_agent.include?(crawler) + key = "#{crawler}_crawler_rate_limit" + limiter = + RateLimiter.new(nil, key, 1, SiteSetting.slow_down_crawler_rate, error_code: key) + limiter.performed! + break + end end - end end def run_second_factor!(action_class, action_data = nil) @@ -1000,9 +1037,8 @@ class ApplicationController < ActionController::Base yield(manager) if block_given? result = manager.run!(request, params, secure_session) - if !result.no_second_factors_enabled? && - !result.second_factor_auth_completed? && - !result.second_factor_auth_skipped? + if !result.no_second_factors_enabled? && !result.second_factor_auth_completed? && + !result.second_factor_auth_skipped? # should never happen, but I want to know if somehow it does! (osama) raise "2fa process ended up in a bad state!" end @@ -1013,7 +1049,7 @@ class ApplicationController < ActionController::Base def link_preload @links_to_preload = [] yield - response.headers['Link'] = @links_to_preload.join(', ') if !@links_to_preload.empty? + response.headers["Link"] = @links_to_preload.join(", ") if !@links_to_preload.empty? end def spa_boot_request? diff --git a/app/controllers/associated_groups_controller.rb b/app/controllers/associated_groups_controller.rb index cf82ae20c7d..136a97e5948 100644 --- a/app/controllers/associated_groups_controller.rb +++ b/app/controllers/associated_groups_controller.rb @@ -5,6 +5,6 @@ class AssociatedGroupsController < ApplicationController def index guardian.ensure_can_associate_groups! - render_serialized(AssociatedGroup.all, AssociatedGroupSerializer, root: 'associated_groups') + render_serialized(AssociatedGroup.all, AssociatedGroupSerializer, root: "associated_groups") end end diff --git a/app/controllers/badges_controller.rb b/app/controllers/badges_controller.rb index 3b90fce090a..e505ea7c594 100644 --- a/app/controllers/badges_controller.rb +++ b/app/controllers/badges_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class BadgesController < ApplicationController - skip_before_action :check_xhr, only: [:index, :show] + skip_before_action :check_xhr, only: %i[index show] after_action :add_noindex_header def index @@ -16,18 +16,29 @@ class BadgesController < ApplicationController if (params[:only_listable] == "true") || !request.xhr? # NOTE: this is sorted client side if needed - badges = badges.includes(:badge_grouping) - .includes(:badge_type, :image_upload) - .where(enabled: true, listable: true) + badges = + badges + .includes(:badge_grouping) + .includes(:badge_type, :image_upload) + .where(enabled: true, listable: true) end badges = badges.to_a user_badges = nil if current_user - user_badges = Set.new(current_user.user_badges.select('distinct badge_id').pluck(:badge_id)) + user_badges = Set.new(current_user.user_badges.select("distinct badge_id").pluck(:badge_id)) end - serialized = MultiJson.dump(serialize_data(badges, BadgeIndexSerializer, root: "badges", user_badges: user_badges, include_long_description: true)) + serialized = + MultiJson.dump( + serialize_data( + badges, + BadgeIndexSerializer, + root: "badges", + user_badges: user_badges, + include_long_description: true, + ), + ) respond_to do |format| format.html do store_preloaded "badges", serialized @@ -42,27 +53,27 @@ class BadgesController < ApplicationController params.require(:id) @badge = Badge.enabled.find(params[:id]) - @rss_title = I18n.t('rss_description.badge', display_name: @badge.display_name, site_title: SiteSetting.title) + @rss_title = + I18n.t( + "rss_description.badge", + display_name: @badge.display_name, + site_title: SiteSetting.title, + ) @rss_link = "#{Discourse.base_url}/badges/#{@badge.id}/#{@badge.slug}" if current_user user_badge = UserBadge.find_by(user_id: current_user.id, badge_id: @badge.id) - if user_badge && user_badge.notification - user_badge.notification.update read: true - end - if user_badge - @badge.has_badge = true - end + user_badge.notification.update read: true if user_badge && user_badge.notification + @badge.has_badge = true if user_badge end - serialized = MultiJson.dump(serialize_data(@badge, BadgeSerializer, root: "badge", include_long_description: true)) + serialized = + MultiJson.dump( + serialize_data(@badge, BadgeSerializer, root: "badge", include_long_description: true), + ) respond_to do |format| - format.rss do - @rss_description = @badge.long_description - end - format.html do - store_preloaded "badge", serialized - end + format.rss { @rss_description = @badge.long_description } + format.html { store_preloaded "badge", serialized } format.json { render json: serialized } end end diff --git a/app/controllers/bookmarks_controller.rb b/app/controllers/bookmarks_controller.rb index bfeaa58a3b3..a3e385e585c 100644 --- a/app/controllers/bookmarks_controller.rb +++ b/app/controllers/bookmarks_controller.rb @@ -6,26 +6,34 @@ class BookmarksController < ApplicationController def create params.require(:bookmarkable_id) params.require(:bookmarkable_type) - params.permit(:bookmarkable_id, :bookmarkable_type, :name, :reminder_at, :auto_delete_preference) + params.permit( + :bookmarkable_id, + :bookmarkable_type, + :name, + :reminder_at, + :auto_delete_preference, + ) RateLimiter.new( - current_user, "create_bookmark", SiteSetting.max_bookmarks_per_day, 1.day.to_i + current_user, + "create_bookmark", + SiteSetting.max_bookmarks_per_day, + 1.day.to_i, ).performed! bookmark_manager = BookmarkManager.new(current_user) - bookmark = bookmark_manager.create_for( - bookmarkable_id: params[:bookmarkable_id], - bookmarkable_type: params[:bookmarkable_type], - name: params[:name], - reminder_at: params[:reminder_at], - options: { - auto_delete_preference: params[:auto_delete_preference] - } - ) + bookmark = + bookmark_manager.create_for( + bookmarkable_id: params[:bookmarkable_id], + bookmarkable_type: params[:bookmarkable_type], + name: params[:name], + reminder_at: params[:reminder_at], + options: { + auto_delete_preference: params[:auto_delete_preference], + }, + ) - if bookmark_manager.errors.empty? - return render json: success_json.merge(id: bookmark.id) - end + return render json: success_json.merge(id: bookmark.id) if bookmark_manager.errors.empty? render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 end @@ -33,7 +41,8 @@ class BookmarksController < ApplicationController def destroy params.require(:id) destroyed_bookmark = BookmarkManager.new(current_user).destroy(params[:id]) - render json: success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) + render json: + success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) end def update @@ -46,13 +55,11 @@ class BookmarksController < ApplicationController name: params[:name], reminder_at: params[:reminder_at], options: { - auto_delete_preference: params[:auto_delete_preference] - } + auto_delete_preference: params[:auto_delete_preference], + }, ) - if bookmark_manager.errors.empty? - return render json: success_json - end + return render json: success_json if bookmark_manager.errors.empty? render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 end @@ -63,9 +70,7 @@ class BookmarksController < ApplicationController bookmark_manager = BookmarkManager.new(current_user) bookmark_manager.toggle_pin(bookmark_id: params[:bookmark_id]) - if bookmark_manager.errors.empty? - return render json: success_json - end + return render json: success_json if bookmark_manager.errors.empty? render json: failed_json.merge(errors: bookmark_manager.errors.full_messages), status: 400 end diff --git a/app/controllers/bootstrap_controller.rb b/app/controllers/bootstrap_controller.rb index 8f7c4b4f62b..48a31a6e16c 100644 --- a/app/controllers/bootstrap_controller.rb +++ b/app/controllers/bootstrap_controller.rb @@ -37,35 +37,34 @@ class BootstrapController < ApplicationController assets_fake_request.env["QUERY_STRING"] = query end - Discourse.find_plugin_css_assets( - include_official: allow_plugins?, - include_unofficial: allow_third_party_plugins?, - mobile_view: mobile_view?, - desktop_view: !mobile_view?, - request: assets_fake_request - ).each do |file| - add_style(file, plugin: true) - end + Discourse + .find_plugin_css_assets( + include_official: allow_plugins?, + include_unofficial: allow_third_party_plugins?, + mobile_view: mobile_view?, + desktop_view: !mobile_view?, + request: assets_fake_request, + ) + .each { |file| add_style(file, plugin: true) } add_style(mobile_view? ? :mobile_theme : :desktop_theme) if theme_id.present? extra_locales = [] if ExtraLocalesController.client_overrides_exist? - extra_locales << ExtraLocalesController.url('overrides') + extra_locales << ExtraLocalesController.url("overrides") end - if staff? - extra_locales << ExtraLocalesController.url('admin') - end + extra_locales << ExtraLocalesController.url("admin") if staff? - if admin? - extra_locales << ExtraLocalesController.url('wizard') - end + extra_locales << ExtraLocalesController.url("wizard") if admin? - plugin_js = Discourse.find_plugin_js_assets( - include_official: allow_plugins?, - include_unofficial: allow_third_party_plugins?, - request: assets_fake_request - ).map { |f| script_asset_path(f) } + plugin_js = + Discourse + .find_plugin_js_assets( + include_official: allow_plugins?, + include_unofficial: allow_third_party_plugins?, + request: assets_fake_request, + ) + .map { |f| script_asset_path(f) } plugin_test_js = if Rails.env != "production" @@ -76,7 +75,7 @@ class BootstrapController < ApplicationController bootstrap = { theme_id: theme_id, - theme_color: "##{ColorScheme.hex_for_name('header_background', scheme_id)}", + theme_color: "##{ColorScheme.hex_for_name("header_background", scheme_id)}", title: SiteSetting.title, current_homepage: current_homepage, locale_script: locale, @@ -90,7 +89,7 @@ class BootstrapController < ApplicationController html_classes: html_classes, html_lang: html_lang, login_path: main_app.login_path, - authentication_data: authentication_data + authentication_data: authentication_data, } bootstrap[:extra_locales] = extra_locales if extra_locales.present? bootstrap[:csrf_token] = form_authenticity_token if current_user @@ -99,39 +98,44 @@ class BootstrapController < ApplicationController end def plugin_css_for_tests - urls = Discourse.find_plugin_css_assets( - include_disabled: true, - desktop_view: true, - ).map do |target| - details = Stylesheet::Manager.new().stylesheet_details(target, 'all') - details[0][:new_href] - end + urls = + Discourse + .find_plugin_css_assets(include_disabled: true, desktop_view: true) + .map do |target| + details = Stylesheet::Manager.new().stylesheet_details(target, "all") + details[0][:new_href] + end stylesheet = <<~CSS /* For use in tests only - `@import`s all plugin stylesheets */ - #{urls.map { |url| "@import \"#{url}\";" }.join("\n") } + #{urls.map { |url| "@import \"#{url}\";" }.join("\n")} CSS - render plain: stylesheet, content_type: 'text/css' + render plain: stylesheet, content_type: "text/css" end -private + private + def add_scheme(scheme_id, media, css_class) return if scheme_id.to_i == -1 - if style = Stylesheet::Manager.new(theme_id: theme_id).color_scheme_stylesheet_details(scheme_id, media) + if style = + Stylesheet::Manager.new(theme_id: theme_id).color_scheme_stylesheet_details( + scheme_id, + media, + ) @stylesheets << { href: style[:new_href], media: media, class: css_class } end end def add_style(target, opts = nil) - if styles = Stylesheet::Manager.new(theme_id: theme_id).stylesheet_details(target, 'all') + if styles = Stylesheet::Manager.new(theme_id: theme_id).stylesheet_details(target, "all") styles.each do |style| @stylesheets << { href: style[:new_href], - media: 'all', + media: "all", theme_id: style[:theme_id], - target: style[:target] + target: style[:target], }.merge(opts || {}) end end @@ -150,7 +154,11 @@ private end def add_plugin_html(html, key) - add_if_present(html, key, DiscoursePluginRegistry.build_html("server:#{key.to_s.dasherize}", self)) + add_if_present( + html, + key, + DiscoursePluginRegistry.build_html("server:#{key.to_s.dasherize}", self), + ) end def create_theme_html @@ -159,10 +167,14 @@ private theme_view = mobile_view? ? :mobile : :desktop - add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_id, theme_view, 'body_tag')) - add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_id, theme_view, 'head_tag')) - add_if_present(theme_html, :header, Theme.lookup_field(theme_id, theme_view, 'header')) - add_if_present(theme_html, :translations, Theme.lookup_field(theme_id, :translations, I18n.locale)) + add_if_present(theme_html, :body_tag, Theme.lookup_field(theme_id, theme_view, "body_tag")) + add_if_present(theme_html, :head_tag, Theme.lookup_field(theme_id, theme_view, "head_tag")) + add_if_present(theme_html, :header, Theme.lookup_field(theme_id, theme_view, "header")) + add_if_present( + theme_html, + :translations, + Theme.lookup_field(theme_id, :translations, I18n.locale), + ) add_if_present(theme_html, :js, Theme.lookup_field(theme_id, :extra_js, nil)) theme_html @@ -171,5 +183,4 @@ private def add_if_present(hash, key, val) hash[key] = val if val.present? end - end diff --git a/app/controllers/categories_controller.rb b/app/controllers/categories_controller.rb index d6e1a6e2ded..c907c3829c7 100644 --- a/app/controllers/categories_controller.rb +++ b/app/controllers/categories_controller.rb @@ -3,11 +3,19 @@ class CategoriesController < ApplicationController include TopicQueryParams - requires_login except: [:index, :categories_and_latest, :categories_and_top, :show, :redirect, :find_by_slug, :visible_groups] + requires_login except: %i[ + index + categories_and_latest + categories_and_top + show + redirect + find_by_slug + visible_groups + ] - before_action :fetch_category, only: [:show, :update, :destroy, :visible_groups] - before_action :initialize_staff_action_logger, only: [:create, :update, :destroy] - skip_before_action :check_xhr, only: [:index, :categories_and_latest, :categories_and_top, :redirect] + before_action :fetch_category, only: %i[show update destroy visible_groups] + before_action :initialize_staff_action_logger, only: %i[create update destroy] + skip_before_action :check_xhr, only: %i[index categories_and_latest categories_and_top redirect] SYMMETRICAL_CATEGORIES_TO_TOPICS_FACTOR = 1.5 MIN_CATEGORIES_TOPICS = 5 @@ -22,17 +30,20 @@ class CategoriesController < ApplicationController @description = SiteSetting.site_description - parent_category = Category.find_by_slug(params[:parent_category_id]) || Category.find_by(id: params[:parent_category_id].to_i) + parent_category = + Category.find_by_slug(params[:parent_category_id]) || + Category.find_by(id: params[:parent_category_id].to_i) - include_subcategories = SiteSetting.desktop_category_page_style == "subcategories_with_featured_topics" || - params[:include_subcategories] == "true" + include_subcategories = + SiteSetting.desktop_category_page_style == "subcategories_with_featured_topics" || + params[:include_subcategories] == "true" category_options = { is_homepage: current_homepage == "categories", parent_category_id: params[:parent_category_id], include_topics: include_topics(parent_category), include_subcategories: include_subcategories, - tag: params[:tag] + tag: params[:tag], } @category_list = CategoryList.new(guardian, category_options) @@ -40,35 +51,38 @@ class CategoriesController < ApplicationController if category_options[:is_homepage] && SiteSetting.short_site_description.present? @title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}" elsif !category_options[:is_homepage] - @title = "#{I18n.t('js.filters.categories.title')} - #{SiteSetting.title}" + @title = "#{I18n.t("js.filters.categories.title")} - #{SiteSetting.title}" end respond_to do |format| format.html do - store_preloaded(@category_list.preload_key, MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian))) + store_preloaded( + @category_list.preload_key, + MultiJson.dump(CategoryListSerializer.new(@category_list, scope: guardian)), + ) style = SiteSetting.desktop_category_page_style - topic_options = { - per_page: CategoriesController.topics_per_page, - no_definitions: true, - } + topic_options = { per_page: CategoriesController.topics_per_page, no_definitions: true } if style == "categories_and_latest_topics_created_date" - topic_options[:order] = 'created' + topic_options[:order] = "created" @topic_list = TopicQuery.new(current_user, topic_options).list_latest @topic_list.more_topics_url = url_for(public_send("latest_path", sort: :created)) elsif style == "categories_and_latest_topics" @topic_list = TopicQuery.new(current_user, topic_options).list_latest @topic_list.more_topics_url = url_for(public_send("latest_path")) elsif style == "categories_and_top_topics" - @topic_list = TopicQuery.new(current_user, topic_options).list_top_for(SiteSetting.top_page_default_timeframe.to_sym) + @topic_list = + TopicQuery.new(current_user, topic_options).list_top_for( + SiteSetting.top_page_default_timeframe.to_sym, + ) @topic_list.more_topics_url = url_for(public_send("top_path")) end if @topic_list.present? && @topic_list.topics.present? store_preloaded( @topic_list.preload_key, - MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian)) + MultiJson.dump(TopicListSerializer.new(@topic_list, scope: guardian)), ) end @@ -109,7 +123,9 @@ class CategoriesController < ApplicationController by_category = Hash[change_requests.map { |cat, pos| [Category.find(cat.to_i), pos] }] unless guardian.is_admin? - raise Discourse::InvalidAccess unless by_category.keys.all? { |c| guardian.can_see_category? c } + unless by_category.keys.all? { |c| guardian.can_see_category? c } + raise Discourse::InvalidAccess + end end by_category.each do |cat, pos| @@ -187,14 +203,12 @@ class CategoriesController < ApplicationController @category, old_category_params, old_permissions: old_permissions, - old_custom_fields: old_custom_fields + old_custom_fields: old_custom_fields, ) end end - if result - DiscourseEvent.trigger(:category_updated, cat) - end + DiscourseEvent.trigger(:category_updated, cat) if result result end @@ -207,7 +221,7 @@ class CategoriesController < ApplicationController custom_slug = params[:slug].to_s if custom_slug.blank? - error = @category.errors.full_message(:slug, I18n.t('errors.messages.blank')) + error = @category.errors.full_message(:slug, I18n.t("errors.messages.blank")) render_json_error(error) elsif @category.update(slug: custom_slug) render json: success_json @@ -221,7 +235,13 @@ class CategoriesController < ApplicationController notification_level = params[:notification_level].to_i CategoryUser.set_notification_level_for_category(current_user, notification_level, category_id) - render json: success_json.merge({ indirectly_muted_category_ids: CategoryUser.indirectly_muted_category_ids(current_user) }) + render json: + success_json.merge( + { + indirectly_muted_category_ids: + CategoryUser.indirectly_muted_category_ids(current_user), + }, + ) end def destroy @@ -237,34 +257,40 @@ class CategoriesController < ApplicationController def find_by_slug params.require(:category_slug) - @category = Category.find_by_slug_path(params[:category_slug].split('/')) + @category = Category.find_by_slug_path(params[:category_slug].split("/")) raise Discourse::NotFound unless @category.present? if !guardian.can_see?(@category) if SiteSetting.detailed_404 && group = @category.access_category_via_group raise Discourse::InvalidAccess.new( - 'not in group', - @category, - custom_message: 'not_in_group.title_category', - custom_message_params: { group: group.name }, - group: group - ) + "not in group", + @category, + custom_message: "not_in_group.title_category", + custom_message_params: { + group: group.name, + }, + group: group, + ) else raise Discourse::NotFound end end - @category.permission = CategoryGroup.permission_types[:full] if Category.topic_create_allowed(guardian).where(id: @category.id).exists? + @category.permission = CategoryGroup.permission_types[:full] if Category + .topic_create_allowed(guardian) + .where(id: @category.id) + .exists? render_serialized(@category, CategorySerializer) end def visible_groups @guardian.ensure_can_see!(@category) - groups = if !@category.groups.exists?(id: Group::AUTO_GROUPS[:everyone]) - @category.groups.merge(Group.visible_groups(current_user)).pluck("name") - end + groups = + if !@category.groups.exists?(id: Group::AUTO_GROUPS[:everyone]) + @category.groups.merge(Group.visible_groups(current_user)).pluck("name") + end render json: success_json.merge(groups: groups || []) end @@ -285,17 +311,14 @@ class CategoriesController < ApplicationController category_options = { is_homepage: current_homepage == "categories", parent_category_id: params[:parent_category_id], - include_topics: false + include_topics: false, } - topic_options = { - per_page: CategoriesController.topics_per_page, - no_definitions: true, - } + topic_options = { per_page: CategoriesController.topics_per_page, no_definitions: true } topic_options.merge!(build_topic_list_options) style = SiteSetting.desktop_category_page_style - topic_options[:order] = 'created' if style == "categories_and_latest_topics_created_date" + topic_options[:order] = "created" if style == "categories_and_latest_topics_created_date" result = CategoryAndTopicLists.new result.category_list = CategoryList.new(guardian, category_options) @@ -303,9 +326,10 @@ class CategoriesController < ApplicationController if topics_filter == :latest result.topic_list = TopicQuery.new(current_user, topic_options).list_latest elsif topics_filter == :top - result.topic_list = TopicQuery.new(current_user, topic_options).list_top_for( - SiteSetting.top_page_default_timeframe.to_sym - ) + result.topic_list = + TopicQuery.new(current_user, topic_options).list_top_for( + SiteSetting.top_page_default_timeframe.to_sym, + ) end render_serialized(result, CategoryAndTopicListsSerializer, root: false) @@ -316,88 +340,90 @@ class CategoriesController < ApplicationController end def required_create_params - required_param_keys.each do |key| - params.require(key) - end + required_param_keys.each { |key| params.require(key) } category_params end def category_params - @category_params ||= begin - if p = params[:permissions] - p.each do |k, v| - p[k] = v.to_i + @category_params ||= + begin + if p = params[:permissions] + p.each { |k, v| p[k] = v.to_i } end + + if SiteSetting.tagging_enabled + params[:allowed_tags] = params[:allowed_tags].presence || [] if params[:allowed_tags] + params[:allowed_tag_groups] = params[:allowed_tag_groups].presence || [] if params[ + :allowed_tag_groups + ] + params[:required_tag_groups] = params[:required_tag_groups].presence || [] if params[ + :required_tag_groups + ] + end + + if SiteSetting.enable_category_group_moderation? + params[:reviewable_by_group_id] = Group.where( + name: params[:reviewable_by_group_name], + ).pluck_first(:id) if params[:reviewable_by_group_name] + end + + result = + params.permit( + *required_param_keys, + :position, + :name, + :color, + :text_color, + :email_in, + :email_in_allow_strangers, + :mailinglist_mirror, + :all_topics_wiki, + :allow_unlimited_owner_edits_on_first_post, + :default_slow_mode_seconds, + :parent_category_id, + :auto_close_hours, + :auto_close_based_on_last_post, + :uploaded_logo_id, + :uploaded_logo_dark_id, + :uploaded_background_id, + :slug, + :allow_badges, + :topic_template, + :sort_order, + :sort_ascending, + :topic_featured_link_allowed, + :show_subcategory_list, + :num_featured_topics, + :default_view, + :subcategory_list_style, + :default_top_period, + :minimum_required_tags, + :navigate_to_first_post_after_read, + :search_priority, + :allow_global_tags, + :read_only_banner, + :default_list_filter, + :reviewable_by_group_id, + custom_fields: [custom_field_params], + permissions: [*p.try(:keys)], + allowed_tags: [], + allowed_tag_groups: [], + required_tag_groups: %i[name min_count], + ) + + if result[:required_tag_groups] && !result[:required_tag_groups].is_a?(Array) + raise Discourse::InvalidParameters.new(:required_tag_groups) + end + + result end - - if SiteSetting.tagging_enabled - params[:allowed_tags] = params[:allowed_tags].presence || [] if params[:allowed_tags] - params[:allowed_tag_groups] = params[:allowed_tag_groups].presence || [] if params[:allowed_tag_groups] - params[:required_tag_groups] = params[:required_tag_groups].presence || [] if params[:required_tag_groups] - end - - if SiteSetting.enable_category_group_moderation? - params[:reviewable_by_group_id] = Group.where(name: params[:reviewable_by_group_name]).pluck_first(:id) if params[:reviewable_by_group_name] - end - - result = params.permit( - *required_param_keys, - :position, - :name, - :color, - :text_color, - :email_in, - :email_in_allow_strangers, - :mailinglist_mirror, - :all_topics_wiki, - :allow_unlimited_owner_edits_on_first_post, - :default_slow_mode_seconds, - :parent_category_id, - :auto_close_hours, - :auto_close_based_on_last_post, - :uploaded_logo_id, - :uploaded_logo_dark_id, - :uploaded_background_id, - :slug, - :allow_badges, - :topic_template, - :sort_order, - :sort_ascending, - :topic_featured_link_allowed, - :show_subcategory_list, - :num_featured_topics, - :default_view, - :subcategory_list_style, - :default_top_period, - :minimum_required_tags, - :navigate_to_first_post_after_read, - :search_priority, - :allow_global_tags, - :read_only_banner, - :default_list_filter, - :reviewable_by_group_id, - custom_fields: [custom_field_params], - permissions: [*p.try(:keys)], - allowed_tags: [], - allowed_tag_groups: [], - required_tag_groups: [:name, :min_count] - ) - - if result[:required_tag_groups] && !result[:required_tag_groups].is_a?(Array) - raise Discourse::InvalidParameters.new(:required_tag_groups) - end - - result - end end def custom_field_params keys = params[:custom_fields].try(:keys) return if keys.blank? - keys.map do |key| - params[:custom_fields][key].is_a?(Array) ? { key => [] } : key - end + keys.map { |key| params[:custom_fields][key].is_a?(Array) ? { key => [] } : key } end def fetch_category @@ -411,12 +437,9 @@ class CategoriesController < ApplicationController def include_topics(parent_category = nil) style = SiteSetting.desktop_category_page_style - view_context.mobile_view? || - params[:include_topics] || + view_context.mobile_view? || params[:include_topics] || (parent_category && parent_category.subcategory_list_includes_topics?) || - style == "categories_with_featured_topics" || - style == "subcategories_with_featured_topics" || - style == "categories_boxes_with_topics" || - style == "categories_with_top_topics" + style == "categories_with_featured_topics" || style == "subcategories_with_featured_topics" || + style == "categories_boxes_with_topics" || style == "categories_with_top_topics" end end diff --git a/app/controllers/clicks_controller.rb b/app/controllers/clicks_controller.rb index 5b932484e7b..59dabcd121b 100644 --- a/app/controllers/clicks_controller.rb +++ b/app/controllers/clicks_controller.rb @@ -4,17 +4,16 @@ class ClicksController < ApplicationController skip_before_action :check_xhr, :preload_json, :verify_authenticity_token def track - params.require([:url, :post_id, :topic_id]) + params.require(%i[url post_id topic_id]) TopicLinkClick.create_from( url: params[:url], post_id: params[:post_id], topic_id: params[:topic_id], ip: request.remote_ip, - user_id: current_user&.id + user_id: current_user&.id, ) render json: success_json end - end diff --git a/app/controllers/composer_controller.rb b/app/controllers/composer_controller.rb index fd4847e115a..f558a890eb5 100644 --- a/app/controllers/composer_controller.rb +++ b/app/controllers/composer_controller.rb @@ -13,12 +13,13 @@ class ComposerController < ApplicationController end # allowed_names is necessary just for new private messages. - @allowed_names = if params[:allowed_names].present? - raise Discourse::InvalidParameters(:allowed_names) if !params[:allowed_names].is_a?(Array) - params[:allowed_names] << current_user.username - else - [] - end + @allowed_names = + if params[:allowed_names].present? + raise Discourse.InvalidParameters(:allowed_names) if !params[:allowed_names].is_a?(Array) + params[:allowed_names] << current_user.username + else + [] + end user_reasons = {} group_reasons = {} @@ -33,64 +34,73 @@ class ComposerController < ApplicationController end if @topic && @names.include?(SiteSetting.here_mention) && guardian.can_mention_here? - here_count = PostAlerter.new.expand_here_mention(@topic.first_post, exclude_ids: [current_user.id]).size + here_count = + PostAlerter.new.expand_here_mention(@topic.first_post, exclude_ids: [current_user.id]).size end - serialized_groups = groups.values.reduce({}) do |hash, group| - serialized_group = { user_count: group.user_count } + serialized_groups = + groups + .values + .reduce({}) do |hash, group| + serialized_group = { user_count: group.user_count } - if group_reasons[group.name] == :not_allowed && - members_visible_group_ids.include?(group.id) && - (@topic&.private_message? || @allowed_names.present?) + if group_reasons[group.name] == :not_allowed && + members_visible_group_ids.include?(group.id) && + (@topic&.private_message? || @allowed_names.present?) + # Find users that are notified already because they have been invited + # directly or via a group + notified_count = + GroupUser + # invited directly + .where(user_id: topic_allowed_user_ids) + .or( + # invited via a group + GroupUser.where( + user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id), + ), + ) + .where(group_id: group.id) + .select(:user_id) + .distinct + .count - # Find users that are notified already because they have been invited - # directly or via a group - notified_count = GroupUser - # invited directly - .where(user_id: topic_allowed_user_ids) - .or( - # invited via a group - GroupUser.where( - user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id) - ) - ) - .where(group_id: group.id) - .select(:user_id).distinct.count + if notified_count > 0 + group_reasons[group.name] = :some_not_allowed + serialized_group[:notified_count] = notified_count + end + end - if notified_count > 0 - group_reasons[group.name] = :some_not_allowed - serialized_group[:notified_count] = notified_count + hash[group.name] = serialized_group + hash end - end - - hash[group.name] = serialized_group - hash - end render json: { - users: users.keys, - user_reasons: user_reasons, - groups: serialized_groups, - group_reasons: group_reasons, - here_count: here_count, - max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention, - } + users: users.keys, + user_reasons: user_reasons, + groups: serialized_groups, + group_reasons: group_reasons, + here_count: here_count, + max_users_notified_per_group_mention: SiteSetting.max_users_notified_per_group_mention, + } end private def user_reason(user) - reason = if @topic && !user.guardian.can_see?(@topic) - @topic.private_message? ? :private : :category - elsif @allowed_names.present? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) - # This would normally be handled by the previous if, but that does not work for new private messages. - :private - elsif topic_muted_by.include?(user.id) - :muted_topic - elsif @topic&.private_message? && !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) - # Admins can see the topic, but they will not be mentioned if they were not invited. - :not_allowed - end + reason = + if @topic && !user.guardian.can_see?(@topic) + @topic.private_message? ? :private : :category + elsif @allowed_names.present? && + !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) + # This would normally be handled by the previous if, but that does not work for new private messages. + :private + elsif topic_muted_by.include?(user.id) + :muted_topic + elsif @topic&.private_message? && + !is_user_allowed?(user, topic_allowed_user_ids, topic_allowed_group_ids) + # Admins can see the topic, but they will not be mentioned if they were not invited. + :not_allowed + end # Regular users can see only basic information why the users cannot see the topic. reason = nil if !guardian.is_staff? && reason != :private && reason != :category @@ -101,7 +111,8 @@ class ComposerController < ApplicationController def group_reason(group) if !mentionable_group_ids.include?(group.id) :not_mentionable - elsif (@topic&.private_message? || @allowed_names.present?) && !topic_allowed_group_ids.include?(group.id) + elsif (@topic&.private_message? || @allowed_names.present?) && + !topic_allowed_group_ids.include?(group.id) :not_allowed end end @@ -111,74 +122,57 @@ class ComposerController < ApplicationController end def users - @users ||= User - .not_staged - .where(username_lower: @names.map(&:downcase)) - .index_by(&:username_lower) + @users ||= + User.not_staged.where(username_lower: @names.map(&:downcase)).index_by(&:username_lower) end def groups - @groups ||= Group - .visible_groups(current_user) - .where('lower(name) IN (?)', @names.map(&:downcase)) - .index_by(&:name) + @groups ||= + Group + .visible_groups(current_user) + .where("lower(name) IN (?)", @names.map(&:downcase)) + .index_by(&:name) end def mentionable_group_ids - @mentionable_group_ids ||= Group - .mentionable(current_user, include_public: false) - .where(name: @names) - .pluck(:id) - .to_set + @mentionable_group_ids ||= + Group.mentionable(current_user, include_public: false).where(name: @names).pluck(:id).to_set end def members_visible_group_ids - @members_visible_group_ids ||= Group - .members_visible_groups(current_user) - .where(name: @names) - .pluck(:id) - .to_set + @members_visible_group_ids ||= + Group.members_visible_groups(current_user).where(name: @names).pluck(:id).to_set end def topic_muted_by - @topic_muted_by ||= if @topic.present? - TopicUser - .where(topic: @topic) - .where(user_id: users.values.map(&:id)) - .where(notification_level: TopicUser.notification_levels[:muted]) - .pluck(:user_id) - .to_set - else - Set.new - end + @topic_muted_by ||= + if @topic.present? + TopicUser + .where(topic: @topic) + .where(user_id: users.values.map(&:id)) + .where(notification_level: TopicUser.notification_levels[:muted]) + .pluck(:user_id) + .to_set + else + Set.new + end end def topic_allowed_user_ids - @topic_allowed_user_ids ||= if @allowed_names.present? - User - .where(username_lower: @allowed_names.map(&:downcase)) - .pluck(:id) - .to_set - elsif @topic&.private_message? - TopicAllowedUser - .where(topic: @topic) - .pluck(:user_id) - .to_set - end + @topic_allowed_user_ids ||= + if @allowed_names.present? + User.where(username_lower: @allowed_names.map(&:downcase)).pluck(:id).to_set + elsif @topic&.private_message? + TopicAllowedUser.where(topic: @topic).pluck(:user_id).to_set + end end def topic_allowed_group_ids - @topic_allowed_group_ids ||= if @allowed_names.present? - Group - .messageable(current_user) - .where(name: @allowed_names) - .pluck(:id) - .to_set - elsif @topic&.private_message? - TopicAllowedGroup - .where(topic: @topic) - .pluck(:group_id) - .to_set - end + @topic_allowed_group_ids ||= + if @allowed_names.present? + Group.messageable(current_user).where(name: @allowed_names).pluck(:id).to_set + elsif @topic&.private_message? + TopicAllowedGroup.where(topic: @topic).pluck(:group_id).to_set + end end end diff --git a/app/controllers/composer_messages_controller.rb b/app/controllers/composer_messages_controller.rb index b8303014559..3785e257288 100644 --- a/app/controllers/composer_messages_controller.rb +++ b/app/controllers/composer_messages_controller.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true class ComposerMessagesController < ApplicationController - requires_login def index - finder = ComposerMessagesFinder.new(current_user, params.slice(:composer_action, :topic_id, :post_id)) + finder = + ComposerMessagesFinder.new(current_user, params.slice(:composer_action, :topic_id, :post_id)) json = { composer_messages: [finder.find].compact } if params[:topic_id].present? @@ -25,14 +25,24 @@ class ComposerMessagesController < ApplicationController warning_message = nil if user_count > 0 - message_locale = if user_count == 1 - "education.user_not_seen_in_a_while.single" - else - "education.user_not_seen_in_a_while.multiple" - end + message_locale = + if user_count == 1 + "education.user_not_seen_in_a_while.single" + else + "education.user_not_seen_in_a_while.multiple" + end end - json = { user_count: user_count, usernames: users, time_ago: FreedomPatches::Rails4.time_ago_in_words(SiteSetting.pm_warn_user_last_seen_months_ago.month.ago, true, scope: :'datetime.distance_in_words_verbose') } + json = { + user_count: user_count, + usernames: users, + time_ago: + FreedomPatches::Rails4.time_ago_in_words( + SiteSetting.pm_warn_user_last_seen_months_ago.month.ago, + true, + scope: :"datetime.distance_in_words_verbose", + ), + } render_json_dump(json) end end diff --git a/app/controllers/csp_reports_controller.rb b/app/controllers/csp_reports_controller.rb index d18fd7d1c2c..ac206da5b30 100644 --- a/app/controllers/csp_reports_controller.rb +++ b/app/controllers/csp_reports_controller.rb @@ -10,12 +10,11 @@ class CspReportsController < ApplicationController if report.blank? render_json_error("empty CSP report", status: 422) else - Logster.add_to_env(request.env, 'CSP Report', report) - Rails.logger.warn("CSP Violation: '#{report['blocked-uri']}' \n\n#{report['script-sample']}") + Logster.add_to_env(request.env, "CSP Report", report) + Rails.logger.warn("CSP Violation: '#{report["blocked-uri"]}' \n\n#{report["script-sample"]}") head :ok end - rescue JSON::ParserError render_json_error("invalid CSP report", status: 422) end @@ -25,20 +24,20 @@ class CspReportsController < ApplicationController def parse_report obj = JSON.parse(request.body.read) if Hash === obj - obj = obj['csp-report'] + obj = obj["csp-report"] if Hash === obj obj.slice( - 'blocked-uri', - 'disposition', - 'document-uri', - 'effective-directive', - 'original-policy', - 'referrer', - 'script-sample', - 'status-code', - 'violated-directive', - 'line-number', - 'source-file' + "blocked-uri", + "disposition", + "document-uri", + "effective-directive", + "original-policy", + "referrer", + "script-sample", + "status-code", + "violated-directive", + "line-number", + "source-file", ) end end diff --git a/app/controllers/directory_columns_controller.rb b/app/controllers/directory_columns_controller.rb index d11f5e30ff5..572c7e92ad4 100644 --- a/app/controllers/directory_columns_controller.rb +++ b/app/controllers/directory_columns_controller.rb @@ -3,6 +3,8 @@ class DirectoryColumnsController < ApplicationController def index directory_columns = DirectoryColumn.includes(:user_field).where(enabled: true).order(:position) - render_json_dump(directory_columns: serialize_data(directory_columns, DirectoryColumnSerializer)) + render_json_dump( + directory_columns: serialize_data(directory_columns, DirectoryColumnSerializer), + ) end end diff --git a/app/controllers/directory_items_controller.rb b/app/controllers/directory_items_controller.rb index 692088cdc55..15f1451d5a8 100644 --- a/app/controllers/directory_items_controller.rb +++ b/app/controllers/directory_items_controller.rb @@ -4,7 +4,9 @@ class DirectoryItemsController < ApplicationController PAGE_SIZE = 50 def index - raise Discourse::InvalidAccess.new(:enable_user_directory) unless SiteSetting.enable_user_directory? + unless SiteSetting.enable_user_directory? + raise Discourse::InvalidAccess.new(:enable_user_directory) + end period = params.require(:period) period_type = DirectoryItem.period_types[period.to_sym] @@ -23,30 +25,36 @@ class DirectoryItemsController < ApplicationController end if params[:exclude_usernames] - result = result.references(:user).where.not(users: { username: params[:exclude_usernames].split(",") }) + result = + result + .references(:user) + .where.not(users: { username: params[:exclude_usernames].split(",") }) end order = params[:order] || DirectoryColumn.automatic_column_names.first - dir = params[:asc] ? 'ASC' : 'DESC' + dir = params[:asc] ? "ASC" : "DESC" active_directory_column_names = DirectoryColumn.active_column_names if active_directory_column_names.include?(order.to_sym) result = result.order("directory_items.#{order} #{dir}, directory_items.id") - elsif params[:order] === 'username' + elsif params[:order] === "username" result = result.order("users.#{order} #{dir}, directory_items.id") else # Ordering by user field value user_field = UserField.find_by(name: params[:order]) if user_field - result = result - .references(:user) - .joins("LEFT OUTER JOIN user_custom_fields ON user_custom_fields.user_id = users.id AND user_custom_fields.name = 'user_field_#{user_field.id}'") - .order("user_custom_fields.name = 'user_field_#{user_field.id}' ASC, user_custom_fields.value #{dir}") + result = + result + .references(:user) + .joins( + "LEFT OUTER JOIN user_custom_fields ON user_custom_fields.user_id = users.id AND user_custom_fields.name = 'user_field_#{user_field.id}'", + ) + .order( + "user_custom_fields.name = 'user_field_#{user_field.id}' ASC, user_custom_fields.value #{dir}", + ) end end - if period_type == DirectoryItem.period_types[:all] - result = result.includes(:user_stat) - end + result = result.includes(:user_stat) if period_type == DirectoryItem.period_types[:all] page = params[:page].to_i user_ids = nil @@ -54,12 +62,10 @@ class DirectoryItemsController < ApplicationController user_ids = UserSearch.new(params[:name], include_staged_users: true).search.pluck(:id) if user_ids.present? # Add the current user if we have at least one other match - if current_user && result.dup.where(user_id: user_ids).exists? - user_ids << current_user.id - end + user_ids << current_user.id if current_user && result.dup.where(user_id: user_ids).exists? result = result.where(user_id: user_ids) else - result = result.where('false') + result = result.where("false") end end @@ -68,7 +74,7 @@ class DirectoryItemsController < ApplicationController if user_id result = result.where(user_id: user_id) else - result = result.where('false') + result = result.where("false") end end @@ -82,7 +88,6 @@ class DirectoryItemsController < ApplicationController # Put yourself at the top of the first page if result.present? && current_user.present? && page == 0 && !params[:group].present? - position = result.index { |r| r.user_id == current_user.id } # Don't show the record unless you're not in the top positions already @@ -90,7 +95,6 @@ class DirectoryItemsController < ApplicationController your_item = DirectoryItem.where(period_type: period_type, user_id: current_user.id).first result.insert(0, your_item) if your_item end - end last_updated_at = DirectoryItem.last_updated_at(period_type) @@ -101,7 +105,9 @@ class DirectoryItemsController < ApplicationController user_field_ids = params[:user_field_ids]&.split("|")&.map(&:to_i) user_field_ids.each do |user_field_id| - serializer_opts[:user_custom_field_map]["#{User::USER_FIELD_PREFIX}#{user_field_id}"] = user_field_id + serializer_opts[:user_custom_field_map][ + "#{User::USER_FIELD_PREFIX}#{user_field_id}" + ] = user_field_id end end @@ -112,12 +118,13 @@ class DirectoryItemsController < ApplicationController serializer_opts[:attributes] = active_directory_column_names serialized = serialize_data(result, DirectoryItemSerializer, serializer_opts) - render_json_dump(directory_items: serialized, - meta: { - last_updated_at: last_updated_at, - total_rows_directory_items: result_count, - load_more_directory_items: load_more_directory_items_json - } - ) + render_json_dump( + directory_items: serialized, + meta: { + last_updated_at: last_updated_at, + total_rows_directory_items: result_count, + load_more_directory_items: load_more_directory_items_json, + }, + ) end end diff --git a/app/controllers/do_not_disturb_controller.rb b/app/controllers/do_not_disturb_controller.rb index db24c7167c6..42d32c9b709 100644 --- a/app/controllers/do_not_disturb_controller.rb +++ b/app/controllers/do_not_disturb_controller.rb @@ -6,11 +6,23 @@ class DoNotDisturbController < ApplicationController def create raise Discourse::InvalidParameters.new(:duration) if params[:duration].blank? - duration_minutes = (Integer(params[:duration]) rescue false) + duration_minutes = + ( + begin + Integer(params[:duration]) + rescue StandardError + false + end + ) - ends_at = duration_minutes ? - ends_at_from_minutes(duration_minutes) : - ends_at_from_string(params[:duration]) + ends_at = + ( + if duration_minutes + ends_at_from_minutes(duration_minutes) + else + ends_at_from_string(params[:duration]) + end + ) new_timing = current_user.do_not_disturb_timings.new(starts_at: Time.zone.now, ends_at: ends_at) @@ -37,7 +49,7 @@ class DoNotDisturbController < ApplicationController end def ends_at_from_string(string) - if string == 'tomorrow' + if string == "tomorrow" Time.now.end_of_day.utc else raise Discourse::InvalidParameters.new(:duration) diff --git a/app/controllers/drafts_controller.rb b/app/controllers/drafts_controller.rb index 86a5c254185..78d1ea4fad6 100644 --- a/app/controllers/drafts_controller.rb +++ b/app/controllers/drafts_controller.rb @@ -9,15 +9,9 @@ class DraftsController < ApplicationController params.permit(:offset) params.permit(:limit) - stream = Draft.stream( - user: current_user, - offset: params[:offset], - limit: params[:limit] - ) + stream = Draft.stream(user: current_user, offset: params[:offset], limit: params[:limit]) - render json: { - drafts: stream ? serialize_data(stream, DraftSerializer) : [] - } + render json: { drafts: stream ? serialize_data(stream, DraftSerializer) : [] } end def show @@ -38,10 +32,9 @@ class DraftsController < ApplicationController params[:sequence].to_i, params[:data], params[:owner], - force_save: params[:force_save] + force_save: params[:force_save], ) rescue Draft::OutOfSequence - begin if !Draft.exists?(user_id: current_user.id, draft_key: params[:draft_key]) Draft.set( @@ -49,18 +42,17 @@ class DraftsController < ApplicationController params[:draft_key], DraftSequence.current(current_user, params[:draft_key]), params[:data], - params[:owner] + params[:owner], ) else raise Draft::OutOfSequence end - rescue Draft::OutOfSequence - render_json_error I18n.t('draft.sequence_conflict_error.title'), - status: 409, - extras: { - description: I18n.t('draft.sequence_conflict_error.description') - } + render_json_error I18n.t("draft.sequence_conflict_error.title"), + status: 409, + extras: { + description: I18n.t("draft.sequence_conflict_error.description"), + } return end end @@ -68,7 +60,7 @@ class DraftsController < ApplicationController json = success_json.merge(draft_sequence: sequence) begin - data = JSON::parse(params[:data]) + data = JSON.parse(params[:data]) rescue JSON::ParserError raise Discourse::InvalidParameters.new(:data) end @@ -76,7 +68,8 @@ class DraftsController < ApplicationController if data.present? # this is a bit of a kludge we need to remove (all the parsing) too many special cases here # we need to catch action edit and action editSharedDraft - if data["postId"].present? && data["originalText"].present? && data["action"].to_s.start_with?("edit") + if data["postId"].present? && data["originalText"].present? && + data["action"].to_s.start_with?("edit") post = Post.find_by(id: data["postId"]) if post && post.raw != data["originalText"] conflict_user = BasicUserSerializer.new(post.last_editor, root: false) diff --git a/app/controllers/edit_directory_columns_controller.rb b/app/controllers/edit_directory_columns_controller.rb index b40d13ce66e..4b32ca9fb29 100644 --- a/app/controllers/edit_directory_columns_controller.rb +++ b/app/controllers/edit_directory_columns_controller.rb @@ -18,14 +18,20 @@ class EditDirectoryColumnsController < ApplicationController directory_column_params = params.permit(directory_columns: {}) directory_columns = DirectoryColumn.all - has_enabled_column = directory_column_params[:directory_columns].values.any? do |column_data| - column_data[:enabled].to_s == "true" + has_enabled_column = + directory_column_params[:directory_columns].values.any? do |column_data| + column_data[:enabled].to_s == "true" + end + unless has_enabled_column + raise Discourse::InvalidParameters, "Must have at least one column enabled" end - raise Discourse::InvalidParameters, "Must have at least one column enabled" unless has_enabled_column directory_column_params[:directory_columns].values.each do |column_data| existing_column = directory_columns.detect { |c| c.id == column_data[:id].to_i } - if (existing_column.enabled != column_data[:enabled] || existing_column.position != column_data[:position].to_i) + if ( + existing_column.enabled != column_data[:enabled] || + existing_column.position != column_data[:position].to_i + ) existing_column.update(enabled: column_data[:enabled], position: column_data[:position]) end end @@ -37,7 +43,8 @@ class EditDirectoryColumnsController < ApplicationController def ensure_user_fields_have_columns user_fields_without_column = - UserField.left_outer_joins(:directory_column) + UserField + .left_outer_joins(:directory_column) .where(directory_column: { user_field_id: nil }) .where("show_on_profile=? OR show_on_user_card=?", true, true) @@ -47,12 +54,14 @@ class EditDirectoryColumnsController < ApplicationController new_directory_column_attrs = [] user_fields_without_column.each do |user_field| - new_directory_column_attrs.push({ - user_field_id: user_field.id, - enabled: false, - type: DirectoryColumn.types[:user_field], - position: next_position - }) + new_directory_column_attrs.push( + { + user_field_id: user_field.id, + enabled: false, + type: DirectoryColumn.types[:user_field], + position: next_position, + }, + ) next_position += 1 end diff --git a/app/controllers/email_controller.rb b/app/controllers/email_controller.rb index b7fd4c83806..ed317022724 100644 --- a/app/controllers/email_controller.rb +++ b/app/controllers/email_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class EmailController < ApplicationController - layout 'no_ember' + layout "no_ember" skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required @@ -11,9 +11,7 @@ class EmailController < ApplicationController @key_owner_found = key&.user.present? if @found && @key_owner_found - UnsubscribeKey - .get_unsubscribe_strategy_for(key) - &.prepare_unsubscribe_options(self) + UnsubscribeKey.get_unsubscribe_strategy_for(key)&.prepare_unsubscribe_options(self) if current_user.present? && (@user != current_user) @different_user = @user.name @@ -28,17 +26,14 @@ class EmailController < ApplicationController key = UnsubscribeKey.find_by(key: params[:key]) raise Discourse::NotFound if key.nil? || key.user.nil? user = key.user - updated = UnsubscribeKey.get_unsubscribe_strategy_for(key) - &.unsubscribe(params) + updated = UnsubscribeKey.get_unsubscribe_strategy_for(key)&.unsubscribe(params) if updated cache_key = "unsub_#{SecureRandom.hex}" Discourse.cache.write cache_key, user.email, expires_in: 1.hour url = path("/email/unsubscribed?key=#{cache_key}") - if key.associated_topic - url += "&topic_id=#{key.associated_topic.id}" - end + url += "&topic_id=#{key.associated_topic.id}" if key.associated_topic redirect_to url else diff --git a/app/controllers/embed_controller.rb b/app/controllers/embed_controller.rb index 3951761c73f..8ff1f870c0c 100644 --- a/app/controllers/embed_controller.rb +++ b/app/controllers/embed_controller.rb @@ -5,10 +5,10 @@ class EmbedController < ApplicationController skip_before_action :check_xhr, :preload_json, :verify_authenticity_token - before_action :prepare_embeddable, except: [ :info ] - before_action :ensure_api_request, only: [ :info ] + before_action :prepare_embeddable, except: [:info] + before_action :ensure_api_request, only: [:info] - layout 'embed' + layout "embed" rescue_from Discourse::InvalidAccess do if current_user.try(:admin?) @@ -16,14 +16,14 @@ class EmbedController < ApplicationController @show_reason = true @hosts = EmbeddableHost.all end - render 'embed_error', status: 400 + render "embed_error", status: 400 end def topics discourse_expires_in 1.minute unless SiteSetting.embed_topics_list? - render 'embed_topics_error', status: 400 + render "embed_topics_error", status: 400 return end @@ -32,10 +32,12 @@ class EmbedController < ApplicationController end if @embed_class = params[:embed_class] - raise Discourse::InvalidParameters.new(:embed_class) unless @embed_class =~ /^[a-zA-Z0-9\-_]+$/ + unless @embed_class =~ /^[a-zA-Z0-9\-_]+$/ + raise Discourse::InvalidParameters.new(:embed_class) + end end - response.headers['X-Robots-Tag'] = 'noindex, indexifembedded' + response.headers["X-Robots-Tag"] = "noindex, indexifembedded" if params.has_key?(:template) && params[:template] == "complete" @template = "complete" @@ -46,8 +48,7 @@ class EmbedController < ApplicationController list_options = build_topic_list_options if params.has_key?(:per_page) - list_options[:per_page] = - [params[:per_page].to_i, SiteSetting.embed_topic_limit_per_page].min + list_options[:per_page] = [params[:per_page].to_i, SiteSetting.embed_topic_limit_per_page].min end if params[:allow_create] @@ -67,11 +68,12 @@ class EmbedController < ApplicationController valid_top_period = false end - @list = if valid_top_period - topic_query.list_top_for(top_period) - else - topic_query.list_latest - end + @list = + if valid_top_period + topic_query.list_top_for(top_period) + else + topic_query.list_latest + end end def comments @@ -80,7 +82,7 @@ class EmbedController < ApplicationController embed_topic_id = params[:topic_id]&.to_i unless embed_topic_id || EmbeddableHost.url_allowed?(embed_url) - raise Discourse::InvalidAccess.new('invalid embed host') + raise Discourse::InvalidAccess.new("invalid embed host") end topic_id = nil @@ -91,28 +93,33 @@ class EmbedController < ApplicationController end if topic_id - @topic_view = TopicView.new(topic_id, - current_user, - limit: SiteSetting.embed_post_limit, - only_regular: true, - exclude_first: true, - exclude_deleted_users: true, - exclude_hidden: true) + @topic_view = + TopicView.new( + topic_id, + current_user, + limit: SiteSetting.embed_post_limit, + only_regular: true, + exclude_first: true, + exclude_deleted_users: true, + exclude_hidden: true, + ) raise Discourse::NotFound if @topic_view.blank? @posts_left = 0 @second_post_url = "#{@topic_view.topic.url}/2" @reply_count = @topic_view.filtered_posts.count - 1 @reply_count = 0 if @reply_count < 0 - @posts_left = @reply_count - SiteSetting.embed_post_limit if @reply_count > SiteSetting.embed_post_limit + @posts_left = @reply_count - SiteSetting.embed_post_limit if @reply_count > + SiteSetting.embed_post_limit elsif embed_url.present? - Jobs.enqueue(:retrieve_topic, - user_id: current_user.try(:id), - embed_url: embed_url, - author_username: embed_username, - referer: request.env['HTTP_REFERER'] - ) - render 'loading' + Jobs.enqueue( + :retrieve_topic, + user_id: current_user.try(:id), + embed_url: embed_url, + author_username: embed_username, + referer: request.env["HTTP_REFERER"], + ) + render "loading" end discourse_expires_in 1.minute @@ -132,16 +139,16 @@ class EmbedController < ApplicationController by_url = {} if embed_urls.present? - urls = embed_urls.map { |u| u.sub(/#discourse-comments$/, '').sub(/\/$/, '') } + urls = embed_urls.map { |u| u.sub(/#discourse-comments$/, "").sub(%r{/$}, "") } topic_embeds = TopicEmbed.where(embed_url: urls).includes(:topic).references(:topic) topic_embeds.each do |te| url = te.embed_url url = "#{url}#discourse-comments" unless params[:embed_url].include?(url) if te.topic.present? - by_url[url] = I18n.t('embed.replies', count: te.topic.posts_count - 1) + by_url[url] = I18n.t("embed.replies", count: te.topic.posts_count - 1) else - by_url[url] = I18n.t('embed.replies', count: 0) + by_url[url] = I18n.t("embed.replies", count: 0) end end end @@ -152,16 +159,18 @@ class EmbedController < ApplicationController private def prepare_embeddable - response.headers.delete('X-Frame-Options') + response.headers.delete("X-Frame-Options") @embeddable_css_class = "" embeddable_host = EmbeddableHost.record_for_url(request.referer) - @embeddable_css_class = " class=\"#{embeddable_host.class_name}\"" if embeddable_host.present? && embeddable_host.class_name.present? + @embeddable_css_class = + " class=\"#{embeddable_host.class_name}\"" if embeddable_host.present? && + embeddable_host.class_name.present? @data_referer = request.referer - @data_referer = '*' if SiteSetting.embed_any_origin? && @data_referer.blank? + @data_referer = "*" if SiteSetting.embed_any_origin? && @data_referer.blank? end def ensure_api_request - raise Discourse::InvalidAccess.new('api key not set') if !is_api? + raise Discourse::InvalidAccess.new("api key not set") if !is_api? end end diff --git a/app/controllers/exceptions_controller.rb b/app/controllers/exceptions_controller.rb index 2cbfeeed026..2b50d7c56c1 100644 --- a/app/controllers/exceptions_controller.rb +++ b/app/controllers/exceptions_controller.rb @@ -12,5 +12,4 @@ class ExceptionsController < ApplicationController def not_found_body render html: build_not_found_page(status: 200) end - end diff --git a/app/controllers/export_csv_controller.rb b/app/controllers/export_csv_controller.rb index 93f16b91d15..833bd53b303 100644 --- a/app/controllers/export_csv_controller.rb +++ b/app/controllers/export_csv_controller.rb @@ -1,16 +1,20 @@ # frozen_string_literal: true class ExportCsvController < ApplicationController - skip_before_action :preload_json, :check_xhr, only: [:show] def export_entity guardian.ensure_can_export_entity!(export_params[:entity]) - if export_params[:entity] == 'user_archive' + if export_params[:entity] == "user_archive" Jobs.enqueue(:export_user_archive, user_id: current_user.id, args: export_params[:args]) else - Jobs.enqueue(:export_csv_file, entity: export_params[:entity], user_id: current_user.id, args: export_params[:args]) + Jobs.enqueue( + :export_csv_file, + entity: export_params[:entity], + user_id: current_user.id, + args: export_params[:args], + ) end StaffActionLogger.new(current_user).log_entity_export(export_params[:entity]) render json: success_json @@ -21,9 +25,10 @@ class ExportCsvController < ApplicationController private def export_params - @_export_params ||= begin - params.require(:entity) - params.permit(:entity, args: Report::FILTERS).to_h - end + @_export_params ||= + begin + params.require(:entity) + params.permit(:entity, args: Report::FILTERS).to_h + end end end diff --git a/app/controllers/extra_locales_controller.rb b/app/controllers/extra_locales_controller.rb index 314e432900a..63fdb5d81c9 100644 --- a/app/controllers/extra_locales_controller.rb +++ b/app/controllers/extra_locales_controller.rb @@ -4,11 +4,11 @@ class ExtraLocalesController < ApplicationController layout :false skip_before_action :check_xhr, - :preload_json, - :redirect_to_login_if_required, - :verify_authenticity_token + :preload_json, + :redirect_to_login_if_required, + :verify_authenticity_token - OVERRIDES_BUNDLE ||= 'overrides' + OVERRIDES_BUNDLE ||= "overrides" MD5_HASH_LENGTH ||= 32 def show diff --git a/app/controllers/finish_installation_controller.rb b/app/controllers/finish_installation_controller.rb index e84252c65ac..cdc6d8990f2 100644 --- a/app/controllers/finish_installation_controller.rb +++ b/app/controllers/finish_installation_controller.rb @@ -2,9 +2,9 @@ class FinishInstallationController < ApplicationController skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required - layout 'finish_installation' + layout "finish_installation" - before_action :ensure_no_admins, except: ['confirm_email', 'resend_email'] + before_action :ensure_no_admins, except: %w[confirm_email resend_email] def index end @@ -61,7 +61,9 @@ class FinishInstallationController < ApplicationController end def find_allowed_emails - return [] unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present? + unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present? + return [] + end GlobalSetting.developer_emails.split(",").map(&:strip) end diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb index ac172d54b3a..61987c111b2 100644 --- a/app/controllers/forums_controller.rb +++ b/app/controllers/forums_controller.rb @@ -6,7 +6,7 @@ class ForumsController < ActionController::Base include ReadOnlyMixin before_action :check_readonly_mode - after_action :add_readonly_header + after_action :add_readonly_header def status if params[:cluster] diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 830f3ad230f..99f3634ee6c 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,44 +1,38 @@ # frozen_string_literal: true class GroupsController < ApplicationController - requires_login only: [ - :set_notifications, - :mentionable, - :messageable, - :check_name, - :update, - :histories, - :request_membership, - :search, - :new, - :test_email_settings - ] + requires_login only: %i[ + set_notifications + mentionable + messageable + check_name + update + histories + request_membership + search + new + test_email_settings + ] - skip_before_action :preload_json, :check_xhr, only: [:posts_feed, :mentions_feed] + skip_before_action :preload_json, :check_xhr, only: %i[posts_feed mentions_feed] skip_before_action :check_xhr, only: [:show] after_action :add_noindex_header TYPE_FILTERS = { - my: Proc.new { |groups, user| - raise Discourse::NotFound unless user - Group.member_of(groups, user) - }, - owner: Proc.new { |groups, user| - raise Discourse::NotFound unless user - Group.owner_of(groups, user) - }, - public: Proc.new { |groups| - groups.where(public_admission: true, automatic: false) - }, - close: Proc.new { |groups| - groups.where(public_admission: false, automatic: false) - }, - automatic: Proc.new { |groups| - groups.where(automatic: true) - }, - non_automatic: Proc.new { |groups| - groups.where(automatic: false) - } + my: + Proc.new do |groups, user| + raise Discourse::NotFound unless user + Group.member_of(groups, user) + end, + owner: + Proc.new do |groups, user| + raise Discourse::NotFound unless user + Group.owner_of(groups, user) + end, + public: Proc.new { |groups| groups.where(public_admission: true, automatic: false) }, + close: Proc.new { |groups| groups.where(public_admission: false, automatic: false) }, + automatic: Proc.new { |groups| groups.where(automatic: true) }, + non_automatic: Proc.new { |groups| groups.where(automatic: false) }, } ADD_MEMBERS_LIMIT = 1000 @@ -47,7 +41,7 @@ class GroupsController < ApplicationController raise Discourse::InvalidAccess.new(:enable_group_directory) end - order = %w{name user_count}.delete(params[:order]) + order = %w[name user_count].delete(params[:order]) dir = params[:asc].to_s == "true" ? "ASC" : "DESC" sort = order ? "#{order} #{dir}" : nil groups = Group.visible_groups(current_user, sort) @@ -56,7 +50,7 @@ class GroupsController < ApplicationController if (username = params[:username]).present? raise Discourse::NotFound unless user = User.find_by_username(username) groups = TYPE_FILTERS[:my].call(groups.members_visible_groups(current_user, sort), user) - type_filters = type_filters - [:my, :owner] + type_filters = type_filters - %i[my owner] end if (filter = params[:filter]).present? @@ -83,7 +77,7 @@ class GroupsController < ApplicationController user_group_ids = group_users.pluck(:group_id) owner_group_ids = group_users.where(owner: true).pluck(:group_id) else - type_filters = type_filters - [:my, :owner] + type_filters = type_filters - %i[my owner] end type_filters.delete(:non_automatic) @@ -96,22 +90,19 @@ class GroupsController < ApplicationController groups = groups.offset(page * page_size).limit(page_size) render_json_dump( - groups: serialize_data(groups, - BasicGroupSerializer, - user_group_ids: user_group_ids || [], - owner_group_ids: owner_group_ids || [] - ), + groups: + serialize_data( + groups, + BasicGroupSerializer, + user_group_ids: user_group_ids || [], + owner_group_ids: owner_group_ids || [], + ), extras: { - type_filters: type_filters + type_filters: type_filters, }, total_rows_groups: total, - load_more_groups: groups_path( - page: page + 1, - type: type, - order: order, - asc: params[:asc], - filter: filter - ) + load_more_groups: + groups_path(page: page + 1, type: type, order: order, asc: params[:asc], filter: filter), ) end @@ -122,21 +113,23 @@ class GroupsController < ApplicationController format.html do @title = group.full_name.present? ? group.full_name.capitalize : group.name @full_title = "#{@title} - #{SiteSetting.title}" - @description_meta = group.bio_cooked.present? ? PrettyText.excerpt(group.bio_cooked, 300) : @title + @description_meta = + group.bio_cooked.present? ? PrettyText.excerpt(group.bio_cooked, 300) : @title render :show end format.json do groups = Group.visible_groups(current_user) if !guardian.is_staff? - groups = groups.where("automatic IS FALSE OR groups.id = ?", Group::AUTO_GROUPS[:moderators]) + groups = + groups.where("automatic IS FALSE OR groups.id = ?", Group::AUTO_GROUPS[:moderators]) end render_json_dump( group: serialize_data(group, GroupShowSerializer, root: nil), extras: { - visible_group_names: groups.pluck(:name) - } + visible_group_names: groups.pluck(:name), + }, ) end end @@ -161,7 +154,15 @@ class GroupsController < ApplicationController if params[:update_existing_users].blank? user_count = count_existing_users(group.group_users, notification_level, categories, tags) - return render status: 422, json: { user_count: user_count, errors: [I18n.t('invalid_params', message: :update_existing_users)] } if user_count > 0 + if user_count > 0 + return( + render status: 422, + json: { + user_count: user_count, + errors: [I18n.t("invalid_params", message: :update_existing_users)], + } + ) + end end end @@ -169,7 +170,9 @@ class GroupsController < ApplicationController GroupActionLogger.new(current_user, group).log_change_group_settings group.record_email_setting_changes!(current_user) group.expire_imap_mailbox_cache - update_existing_users(group.group_users, notification_level, categories, tags) if params[:update_existing_users] == "true" + if params[:update_existing_users] == "true" + update_existing_users(group.group_users, notification_level, categories, tags) + end AdminDashboardData.clear_found_problem("group_#{group.id}_email_credentials") # Redirect user to groups index page if they can no longer see the group @@ -185,10 +188,7 @@ class GroupsController < ApplicationController group = find_group(:group_id) guardian.ensure_can_see_group_members!(group) - posts = group.posts_for( - guardian, - params.permit(:before_post_id, :category_id) - ).limit(20) + posts = group.posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(20) render_serialized posts.to_a, GroupPostSerializer end @@ -196,37 +196,32 @@ class GroupsController < ApplicationController group = find_group(:group_id) guardian.ensure_can_see_group_members!(group) - @posts = group.posts_for( - guardian, - params.permit(:before_post_id, :category_id) - ).limit(50) - @title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_posts", group_name: group.name)}" + @posts = group.posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(50) + @title = + "#{SiteSetting.title} - #{I18n.t("rss_description.group_posts", group_name: group.name)}" @link = Discourse.base_url @description = I18n.t("rss_description.group_posts", group_name: group.name) - render 'posts/latest', formats: [:rss] + render "posts/latest", formats: [:rss] end def mentions raise Discourse::NotFound unless SiteSetting.enable_mentions? group = find_group(:group_id) - posts = group.mentioned_posts_for( - guardian, - params.permit(:before_post_id, :category_id) - ).limit(20) + posts = + group.mentioned_posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(20) render_serialized posts.to_a, GroupPostSerializer end def mentions_feed raise Discourse::NotFound unless SiteSetting.enable_mentions? group = find_group(:group_id) - @posts = group.mentioned_posts_for( - guardian, - params.permit(:before_post_id, :category_id) - ).limit(50) - @title = "#{SiteSetting.title} - #{I18n.t("rss_description.group_mentions", group_name: group.name)}" + @posts = + group.mentioned_posts_for(guardian, params.permit(:before_post_id, :category_id)).limit(50) + @title = + "#{SiteSetting.title} - #{I18n.t("rss_description.group_mentions", group_name: group.name)}" @link = Discourse.base_url @description = I18n.t("rss_description.group_mentions", group_name: group.name) - render 'posts/latest', formats: [:rss] + render "posts/latest", formats: [:rss] end def members @@ -240,10 +235,14 @@ class GroupsController < ApplicationController raise Discourse::InvalidParameters.new(:limit) if limit < 0 || limit > 1000 raise Discourse::InvalidParameters.new(:offset) if offset < 0 - dir = (params[:asc] && params[:asc].present?) ? 'ASC' : 'DESC' + dir = (params[:asc] && params[:asc].present?) ? "ASC" : "DESC" if params[:desc] - Discourse.deprecate(":desc is deprecated please use :asc instead", output_in_test: true, drop_from: '2.9.0') - dir = (params[:desc] && params[:desc].present?) ? 'DESC' : 'ASC' + Discourse.deprecate( + ":desc is deprecated please use :asc instead", + output_in_test: true, + drop_from: "2.9.0", + ) + dir = (params[:desc] && params[:desc].present?) ? "DESC" : "ASC" end order = "NOT group_users.owner" @@ -254,7 +253,7 @@ class GroupsController < ApplicationController total = users.count if (filter = params[:filter]).present? - filter = filter.split(',') if filter.include?(',') + filter = filter.split(",") if filter.include?(",") if current_user&.admin users = users.filter_by_username_or_email(filter) @@ -263,26 +262,29 @@ class GroupsController < ApplicationController end end - users = users - .select("users.*, group_requests.reason, group_requests.created_at requested_at") - .order(params[:order] == 'requested_at' ? "group_requests.created_at #{dir}" : "") - .order(username_lower: dir) - .limit(limit) - .offset(offset) + users = + users + .select("users.*, group_requests.reason, group_requests.created_at requested_at") + .order(params[:order] == "requested_at" ? "group_requests.created_at #{dir}" : "") + .order(username_lower: dir) + .limit(limit) + .offset(offset) - return render json: { - members: serialize_data(users, GroupRequesterSerializer), - meta: { - total: total, - limit: limit, - offset: offset - } - } + return( + render json: { + members: serialize_data(users, GroupRequesterSerializer), + meta: { + total: total, + limit: limit, + offset: offset, + }, + } + ) end - if params[:order] && %w{last_posted_at last_seen_at}.include?(params[:order]) + if params[:order] && %w[last_posted_at last_seen_at].include?(params[:order]) order = "#{params[:order]} #{dir} NULLS LAST" - elsif params[:order] == 'added_at' + elsif params[:order] == "added_at" order = "group_users.created_at #{dir}" end @@ -290,7 +292,7 @@ class GroupsController < ApplicationController total = users.count if (filter = params[:filter]).present? - filter = filter.split(',') if filter.include?(',') + filter = filter.split(",") if filter.include?(",") if current_user&.admin users = users.filter_by_username_or_email(filter) @@ -299,25 +301,26 @@ class GroupsController < ApplicationController end end - users = users - .includes(:primary_group) - .includes(:user_option) - .select('users.*, group_users.created_at as added_at') - .order(order) - .order(username_lower: dir) + users = + users + .includes(:primary_group) + .includes(:user_option) + .select("users.*, group_users.created_at as added_at") + .order(order) + .order(username_lower: dir) members = users.limit(limit).offset(offset) - owners = users.where('group_users.owner') + owners = users.where("group_users.owner") render json: { - members: serialize_data(members, GroupUserSerializer), - owners: serialize_data(owners, GroupUserSerializer), - meta: { - total: total, - limit: limit, - offset: offset - } - } + members: serialize_data(members, GroupUserSerializer), + owners: serialize_data(owners, GroupUserSerializer), + meta: { + total: total, + limit: limit, + offset: offset, + }, + } end def add_members @@ -327,10 +330,12 @@ class GroupsController < ApplicationController users = users_from_params.to_a emails = [] if params[:emails] - params[:emails].split(",").each do |email| - existing_user = User.find_by_email(email) - existing_user.present? ? users.push(existing_user) : emails.push(email) - end + params[:emails] + .split(",") + .each do |email| + existing_user = User.find_by_email(email) + existing_user.present? ? users.push(existing_user) : emails.push(email) + end end guardian.ensure_can_invite_to_forum!([group]) if emails.present? @@ -340,43 +345,43 @@ class GroupsController < ApplicationController end if users.length > ADD_MEMBERS_LIMIT - return render_json_error( - I18n.t("groups.errors.adding_too_many_users", count: ADD_MEMBERS_LIMIT) + return( + render_json_error(I18n.t("groups.errors.adding_too_many_users", count: ADD_MEMBERS_LIMIT)) ) end usernames_already_in_group = group.users.where(id: users.map(&:id)).pluck(:username) - if usernames_already_in_group.present? && - usernames_already_in_group.length == users.length && - emails.blank? - render_json_error(I18n.t( - "groups.errors.member_already_exist", - username: usernames_already_in_group.sort.join(", "), - count: usernames_already_in_group.size - )) + if usernames_already_in_group.present? && usernames_already_in_group.length == users.length && + emails.blank? + render_json_error( + I18n.t( + "groups.errors.member_already_exist", + username: usernames_already_in_group.sort.join(", "), + count: usernames_already_in_group.size, + ), + ) else notify = params[:notify_users]&.to_s == "true" uniq_users = users.uniq - uniq_users.each do |user| - add_user_to_group(group, user, notify) - end + uniq_users.each { |user| add_user_to_group(group, user, notify) } emails.each do |email| begin Invite.generate(current_user, email: email, group_ids: [group.id]) rescue RateLimiter::LimitExceeded => e - return render_json_error(I18n.t( - "invite.rate_limit", - count: SiteSetting.max_invites_per_day, - time_left: e.time_left - )) + return( + render_json_error( + I18n.t( + "invite.rate_limit", + count: SiteSetting.max_invites_per_day, + time_left: e.time_left, + ), + ) + ) end end - render json: success_json.merge!( - usernames: uniq_users.map(&:username), - emails: emails - ) + render json: success_json.merge!(usernames: uniq_users.map(&:username), emails: emails) end end @@ -412,12 +417,13 @@ class GroupsController < ApplicationController end if params[:accept] - PostCreator.new(current_user, - title: I18n.t('groups.request_accepted_pm.title', group_name: group.name), - raw: I18n.t('groups.request_accepted_pm.body', group_name: group.name), + PostCreator.new( + current_user, + title: I18n.t("groups.request_accepted_pm.title", group_name: group.name), + raw: I18n.t("groups.request_accepted_pm.body", group_name: group.name), archetype: Archetype.private_message, target_usernames: user.username, - skip_validations: true + skip_validations: true, ).create! end @@ -460,9 +466,9 @@ class GroupsController < ApplicationController params[:user_emails] = params[:user_email] if params[:user_email].present? users = users_from_params - raise Discourse::InvalidParameters.new( - 'user_ids or usernames or user_emails must be present' - ) if users.empty? + if users.empty? + raise Discourse::InvalidParameters.new("user_ids or usernames or user_emails must be present") + end removed_users = [] skipped_users = [] @@ -480,10 +486,7 @@ class GroupsController < ApplicationController end end - render json: success_json.merge!( - usernames: removed_users, - skipped_usernames: skipped_users - ) + render json: success_json.merge!(usernames: removed_users, skipped_usernames: skipped_users) end def leave @@ -511,24 +514,35 @@ class GroupsController < ApplicationController begin GroupRequest.create!(group: group, user: current_user, reason: params[:reason]) rescue ActiveRecord::RecordNotUnique - return render json: failed_json.merge(error: I18n.t("groups.errors.already_requested_membership")), status: 409 + return( + render json: failed_json.merge(error: I18n.t("groups.errors.already_requested_membership")), + status: 409 + ) end usernames = [current_user.username].concat( - group.users.where('group_users.owner') + group + .users + .where("group_users.owner") .order("users.last_seen_at DESC") .limit(MAX_NOTIFIED_OWNERS) - .pluck("users.username") + .pluck("users.username"), ) - post = PostCreator.new(current_user, - title: I18n.t('groups.request_membership_pm.title', group_name: group.name), - raw: params[:reason], - archetype: Archetype.private_message, - target_usernames: usernames.join(','), - topic_opts: { custom_fields: { requested_group_id: group.id } }, - skip_validations: true - ).create! + post = + PostCreator.new( + current_user, + title: I18n.t("groups.request_membership_pm.title", group_name: group.name), + raw: params[:reason], + archetype: Archetype.private_message, + target_usernames: usernames.join(","), + topic_opts: { + custom_fields: { + requested_group_id: group.id, + }, + }, + skip_validations: true, + ).create! render json: success_json.merge(relative_url: post.topic.relative_url) end @@ -538,11 +552,10 @@ class GroupsController < ApplicationController notification_level = params.require(:notification_level) user_id = current_user.id - if guardian.is_staff? - user_id = params[:user_id] || user_id - end + user_id = params[:user_id] || user_id if guardian.is_staff? - GroupUser.where(group_id: group.id) + GroupUser + .where(group_id: group.id) .where(user_id: user_id) .update_all(notification_level: notification_level) @@ -556,29 +569,28 @@ class GroupsController < ApplicationController page_size = 25 offset = (params[:offset] && params[:offset].to_i) || 0 - group_histories = GroupHistory.with_filters(group, params[:filters]) - .limit(page_size) - .offset(offset * page_size) + group_histories = + GroupHistory.with_filters(group, params[:filters]).limit(page_size).offset(offset * page_size) render_json_dump( logs: serialize_data(group_histories, BasicGroupHistorySerializer), - all_loaded: group_histories.count < page_size + all_loaded: group_histories.count < page_size, ) end def search - groups = Group.visible_groups(current_user) - .where("groups.id <> ?", Group::AUTO_GROUPS[:everyone]) - .includes(:flair_upload) - .order(:name) + groups = + Group + .visible_groups(current_user) + .where("groups.id <> ?", Group::AUTO_GROUPS[:everyone]) + .includes(:flair_upload) + .order(:name) if (term = params[:term]).present? groups = groups.where("name ILIKE :term OR full_name ILIKE :term", term: "%#{term}%") end - if params[:ignore_automatic].to_s == "true" - groups = groups.where(automatic: false) - end + groups = groups.where(automatic: false) if params[:ignore_automatic].to_s == "true" if Group.preloaded_custom_field_names.present? Group.preload_custom_fields(groups, Group.preloaded_custom_field_names) @@ -589,8 +601,14 @@ class GroupsController < ApplicationController def permissions group = find_group(:id) - category_groups = group.category_groups.select { |category_group| guardian.can_see_category?(category_group.category) } - render_serialized(category_groups.sort_by { |category_group| category_group.category.name }, CategoryGroupSerializer) + category_groups = + group.category_groups.select do |category_group| + guardian.can_see_category?(category_group.category) + end + render_serialized( + category_groups.sort_by { |category_group| category_group.category.name }, + CategoryGroupSerializer, + ) end def test_email_settings @@ -611,7 +629,7 @@ class GroupsController < ApplicationController enable_tls = settings[:ssl] == "true" email_host = params[:host] - if !["smtp", "imap"].include?(params[:protocol]) + if !%w[smtp imap].include?(params[:protocol]) raise Discourse::InvalidParameters.new("Valid protocols to test are smtp and imap") end @@ -622,20 +640,29 @@ class GroupsController < ApplicationController enable_starttls_auto = false settings.delete(:ssl) - final_settings = settings.merge(enable_tls: enable_tls, enable_starttls_auto: enable_starttls_auto) - .permit(:host, :port, :username, :password, :enable_tls, :enable_starttls_auto, :debug) - EmailSettingsValidator.validate_as_user(current_user, "smtp", **final_settings.to_h.symbolize_keys) + final_settings = + settings.merge( + enable_tls: enable_tls, + enable_starttls_auto: enable_starttls_auto, + ).permit(:host, :port, :username, :password, :enable_tls, :enable_starttls_auto, :debug) + EmailSettingsValidator.validate_as_user( + current_user, + "smtp", + **final_settings.to_h.symbolize_keys, + ) when "imap" - final_settings = settings.merge(ssl: enable_tls) - .permit(:host, :port, :username, :password, :ssl, :debug) - EmailSettingsValidator.validate_as_user(current_user, "imap", **final_settings.to_h.symbolize_keys) + final_settings = + settings.merge(ssl: enable_tls).permit(:host, :port, :username, :password, :ssl, :debug) + EmailSettingsValidator.validate_as_user( + current_user, + "imap", + **final_settings.to_h.symbolize_keys, + ) end render json: success_json rescue *EmailSettingsExceptionHandler::EXPECTED_EXCEPTIONS, StandardError => err - render_json_error( - EmailSettingsExceptionHandler.friendly_exception_message(err, email_host) - ) + render_json_error(EmailSettingsExceptionHandler.friendly_exception_message(err, email_host)) end end end @@ -653,7 +680,7 @@ class GroupsController < ApplicationController end def group_params(automatic: false) - attributes = %i{ + attributes = %i[ bio_raw default_notification_level messageable_level @@ -662,7 +689,7 @@ class GroupsController < ApplicationController flair_color flair_icon flair_upload_id - } + ] if automatic attributes.push(:visibility_level) @@ -673,7 +700,7 @@ class GroupsController < ApplicationController :full_name, :public_exit, :public_admission, - :membership_request_template + :membership_request_template, ) end @@ -703,7 +730,7 @@ class GroupsController < ApplicationController :grant_trust_level, :automatic_membership_email_domains, :publish_read_state, - :allow_unknown_sender_topic_replies + :allow_unknown_sender_topic_replies, ) custom_fields = DiscoursePluginRegistry.editable_group_custom_fields @@ -711,7 +738,7 @@ class GroupsController < ApplicationController end if !automatic || current_user.admin - [:muted, :regular, :tracking, :watching, :watching_first_post].each do |level| + %i[muted regular tracking watching watching_first_post].each do |level| attributes << { "#{level}_category_ids" => [] } attributes << { "#{level}_tags" => [] } end @@ -770,8 +797,10 @@ class GroupsController < ApplicationController end def user_default_notifications(group, params) - category_notifications = group.group_category_notification_defaults.pluck(:category_id, :notification_level).to_h - tag_notifications = group.group_tag_notification_defaults.pluck(:tag_id, :notification_level).to_h + category_notifications = + group.group_category_notification_defaults.pluck(:category_id, :notification_level).to_h + tag_notifications = + group.group_tag_notification_defaults.pluck(:tag_id, :notification_level).to_h categories = {} tags = {} @@ -782,10 +811,7 @@ class GroupsController < ApplicationController category_id = category_id.to_i old_value = category_notifications[category_id] - metadata = { - old_value: old_value, - new_value: value - } + metadata = { old_value: old_value, new_value: value } if old_value.blank? metadata[:action] = :create @@ -805,10 +831,7 @@ class GroupsController < ApplicationController tag_ids.each do |tag_id| old_value = tag_notifications[tag_id] - metadata = { - old_value: old_value, - new_value: value - } + metadata = { old_value: old_value, new_value: value } if old_value.blank? metadata[:action] = :create @@ -834,20 +857,18 @@ class GroupsController < ApplicationController notification_level = nil default_notification_level = params[:default_notification_level]&.to_i - if default_notification_level.present? && group.default_notification_level != default_notification_level + if default_notification_level.present? && + group.default_notification_level != default_notification_level notification_level = { old_value: group.default_notification_level, - new_value: default_notification_level + new_value: default_notification_level, } end [notification_level, categories, tags] end - %i{ - count - update - }.each do |action| + %i[count update].each do |action| define_method("#{action}_existing_users") do |group_users, notification_level, categories, tags| return 0 if notification_level.blank? && categories.blank? && tags.blank? @@ -865,7 +886,12 @@ class GroupsController < ApplicationController categories.each do |category_id, data| if data[:action] == :update || data[:action] == :delete - category_users = CategoryUser.where(category_id: category_id, notification_level: data[:old_value], user_id: group_users.select(:user_id)) + category_users = + CategoryUser.where( + category_id: category_id, + notification_level: data[:old_value], + user_id: group_users.select(:user_id), + ) if action == :update category_users.delete_all @@ -879,7 +905,12 @@ class GroupsController < ApplicationController tags.each do |tag_id, data| if data[:action] == :update || data[:action] == :delete - tag_users = TagUser.where(tag_id: tag_id, notification_level: data[:old_value], user_id: group_users.select(:user_id)) + tag_users = + TagUser.where( + tag_id: tag_id, + notification_level: data[:old_value], + user_id: group_users.select(:user_id), + ) if action == :update tag_users.delete_all @@ -892,47 +923,65 @@ class GroupsController < ApplicationController end if categories.present? || tags.present? - group_users.select(:id, :user_id).find_in_batches do |batch| - user_ids = batch.pluck(:user_id) + group_users + .select(:id, :user_id) + .find_in_batches do |batch| + user_ids = batch.pluck(:user_id) - categories.each do |category_id, data| - category_users = [] - existing_users = CategoryUser.where(category_id: category_id, user_id: user_ids).where("notification_level IS NOT NULL") - skip_user_ids = existing_users.pluck(:user_id) + categories.each do |category_id, data| + category_users = [] + existing_users = + CategoryUser.where(category_id: category_id, user_id: user_ids).where( + "notification_level IS NOT NULL", + ) + skip_user_ids = existing_users.pluck(:user_id) - batch.each do |group_user| - next if skip_user_ids.include?(group_user.user_id) - category_users << { category_id: category_id, user_id: group_user.user_id, notification_level: data[:new_value] } + batch.each do |group_user| + next if skip_user_ids.include?(group_user.user_id) + category_users << { + category_id: category_id, + user_id: group_user.user_id, + notification_level: data[:new_value], + } + end + + next if category_users.blank? + + if action == :update + CategoryUser.insert_all!(category_users) + else + ids += category_users.pluck(:user_id) + end end - next if category_users.blank? + tags.each do |tag_id, data| + tag_users = [] + existing_users = + TagUser.where(tag_id: tag_id, user_id: user_ids).where( + "notification_level IS NOT NULL", + ) + skip_user_ids = existing_users.pluck(:user_id) - if action == :update - CategoryUser.insert_all!(category_users) - else - ids += category_users.pluck(:user_id) + batch.each do |group_user| + next if skip_user_ids.include?(group_user.user_id) + tag_users << { + tag_id: tag_id, + user_id: group_user.user_id, + notification_level: data[:new_value], + created_at: Time.now, + updated_at: Time.now, + } + end + + next if tag_users.blank? + + if action == :update + TagUser.insert_all!(tag_users) + else + ids += tag_users.pluck(:user_id) + end end end - - tags.each do |tag_id, data| - tag_users = [] - existing_users = TagUser.where(tag_id: tag_id, user_id: user_ids).where("notification_level IS NOT NULL") - skip_user_ids = existing_users.pluck(:user_id) - - batch.each do |group_user| - next if skip_user_ids.include?(group_user.user_id) - tag_users << { tag_id: tag_id, user_id: group_user.user_id, notification_level: data[:new_value], created_at: Time.now, updated_at: Time.now } - end - - next if tag_users.blank? - - if action == :update - TagUser.insert_all!(tag_users) - else - ids += tag_users.pluck(:user_id) - end - end - end end ids.uniq.count diff --git a/app/controllers/highlight_js_controller.rb b/app/controllers/highlight_js_controller.rb index 5011fc9535e..b77c14e82a9 100644 --- a/app/controllers/highlight_js_controller.rb +++ b/app/controllers/highlight_js_controller.rb @@ -1,26 +1,26 @@ # frozen_string_literal: true class HighlightJsController < ApplicationController - skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show] + skip_before_action :preload_json, + :redirect_to_login_if_required, + :check_xhr, + :verify_authenticity_token, + only: [:show] before_action :apply_cdn_headers, only: [:show] def show - no_cookies RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do - current_version = HighlightJs.version(SiteSetting.highlighted_languages) - if current_version != params[:version] - return redirect_to path(HighlightJs.path) - end + return redirect_to path(HighlightJs.path) if current_version != params[:version] # note, this can be slightly optimised by caching the bundled file, it cuts down on N reads # our nginx config caches this so in practical terms it does not really matter and keeps # code simpler - languages = SiteSetting.highlighted_languages.split('|') + languages = SiteSetting.highlighted_languages.split("|") highlight_js = HighlightJs.bundle(languages) @@ -28,7 +28,7 @@ class HighlightJsController < ApplicationController response.headers["Content-Length"] = highlight_js.bytesize.to_s immutable_for 1.year - render plain: highlight_js, disposition: nil, content_type: 'application/javascript' + render plain: highlight_js, disposition: nil, content_type: "application/javascript" end end end diff --git a/app/controllers/inline_onebox_controller.rb b/app/controllers/inline_onebox_controller.rb index 871be8c3080..3b3d61c55c2 100644 --- a/app/controllers/inline_onebox_controller.rb +++ b/app/controllers/inline_onebox_controller.rb @@ -5,12 +5,13 @@ class InlineOneboxController < ApplicationController def show hijack do - oneboxes = InlineOneboxer.new( - params[:urls] || [], - user_id: current_user.id, - category_id: params[:category_id].to_i, - topic_id: params[:topic_id].to_i - ).process + oneboxes = + InlineOneboxer.new( + params[:urls] || [], + user_id: current_user.id, + category_id: params[:category_id].to_i, + topic_id: params[:topic_id].to_i, + ).process render json: { "inline-oneboxes" => oneboxes } end end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 9f9eb041901..a8483d2b46f 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -1,17 +1,24 @@ # frozen_string_literal: true -require 'csv' +require "csv" class InvitesController < ApplicationController - - requires_login only: [:create, :retrieve, :destroy, :destroy_all_expired, :resend_invite, :resend_all_invites, :upload_csv] + requires_login only: %i[ + create + retrieve + destroy + destroy_all_expired + resend_invite + resend_all_invites + upload_csv + ] skip_before_action :check_xhr, except: [:perform_accept_invitation] skip_before_action :preload_json, except: [:show] skip_before_action :redirect_to_login_if_required - before_action :ensure_invites_allowed, only: [:show, :perform_accept_invitation] - before_action :ensure_new_registrations_allowed, only: [:show, :perform_accept_invitation] + before_action :ensure_invites_allowed, only: %i[show perform_accept_invitation] + before_action :ensure_new_registrations_allowed, only: %i[show perform_accept_invitation] def show expires_now @@ -27,7 +34,7 @@ class InvitesController < ApplicationController end rescue RateLimiter::LimitExceeded => e flash.now[:error] = e.description - render layout: 'no_ember' + render layout: "no_ember" end def create @@ -45,24 +52,37 @@ class InvitesController < ApplicationController if !groups_can_see_topic?(groups, topic) editable_topic_groups = topic.category.groups.filter { |g| guardian.can_edit_group?(g) } - return render_json_error(I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", "))) + return( + render_json_error( + I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")), + ) + ) end - invite = Invite.generate(current_user, - email: params[:email], - domain: params[:domain], - skip_email: params[:skip_email], - invited_by: current_user, - custom_message: params[:custom_message], - max_redemptions_allowed: params[:max_redemptions_allowed], - topic_id: topic&.id, - group_ids: groups&.map(&:id), - expires_at: params[:expires_at], - invite_to_topic: params[:invite_to_topic] - ) + invite = + Invite.generate( + current_user, + email: params[:email], + domain: params[:domain], + skip_email: params[:skip_email], + invited_by: current_user, + custom_message: params[:custom_message], + max_redemptions_allowed: params[:max_redemptions_allowed], + topic_id: topic&.id, + group_ids: groups&.map(&:id), + expires_at: params[:expires_at], + invite_to_topic: params[:invite_to_topic], + ) if invite.present? - render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) + render_serialized( + invite, + InviteSerializer, + scope: guardian, + root: nil, + show_emails: params.has_key?(:email), + show_warnings: true, + ) else render json: failed_json, status: 422 end @@ -81,7 +101,14 @@ class InvitesController < ApplicationController guardian.ensure_can_invite_to_forum!(nil) - render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) + render_serialized( + invite, + InviteSerializer, + scope: guardian, + root: nil, + show_emails: params.has_key?(:email), + show_warnings: true, + ) end def update @@ -108,12 +135,19 @@ class InvitesController < ApplicationController if params.has_key?(:group_ids) || params.has_key?(:group_names) invite.invited_groups.destroy_all - groups.each { |group| invite.invited_groups.find_or_create_by!(group_id: group.id) } if groups.present? + if groups.present? + groups.each { |group| invite.invited_groups.find_or_create_by!(group_id: group.id) } + end end if !groups_can_see_topic?(invite.groups, invite.topics.first) - editable_topic_groups = invite.topics.first.category.groups.filter { |g| guardian.can_edit_group?(g) } - return render_json_error(I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", "))) + editable_topic_groups = + invite.topics.first.category.groups.filter { |g| guardian.can_edit_group?(g) } + return( + render_json_error( + I18n.t("invite.requires_groups", groups: editable_topic_groups.pluck(:name).join(", ")), + ) + ) end if params.has_key?(:email) @@ -121,20 +155,26 @@ class InvitesController < ApplicationController new_email = params[:email].presence if new_email - if Invite.where.not(id: invite.id).find_by(email: new_email.downcase, invited_by_id: current_user.id)&.redeemable? - return render_json_error( - I18n.t("invite.invite_exists", email: CGI.escapeHTML(new_email)), - status: 409 + if Invite + .where.not(id: invite.id) + .find_by(email: new_email.downcase, invited_by_id: current_user.id) + &.redeemable? + return( + render_json_error( + I18n.t("invite.invite_exists", email: CGI.escapeHTML(new_email)), + status: 409, + ) ) end end if old_email != new_email - invite.emailed_status = if new_email && !params[:skip_email] - Invite.emailed_status_types[:pending] - else - Invite.emailed_status_types[:not_required] - end + invite.emailed_status = + if new_email && !params[:skip_email] + Invite.emailed_status_types[:pending] + else + Invite.emailed_status_types[:not_required] + end end invite.domain = nil if invite.email.present? @@ -162,9 +202,13 @@ class InvitesController < ApplicationController end begin - invite.update!(params.permit(:email, :custom_message, :max_redemptions_allowed, :expires_at)) + invite.update!( + params.permit(:email, :custom_message, :max_redemptions_allowed, :expires_at), + ) rescue ActiveRecord::RecordInvalid => e - return render json: {}, status: 200 if SiteSetting.hide_email_address_taken? && e.record.email_already_exists? + if SiteSetting.hide_email_address_taken? && e.record.email_already_exists? + return render json: {}, status: 200 + end return render_json_error(e.record.errors.full_messages.first) end end @@ -174,7 +218,14 @@ class InvitesController < ApplicationController Jobs.enqueue(:invite_email, invite_id: invite.id, invite_to_topic: params[:invite_to_topic]) end - render_serialized(invite, InviteSerializer, scope: guardian, root: nil, show_emails: params.has_key?(:email), show_warnings: true) + render_serialized( + invite, + InviteSerializer, + scope: guardian, + root: nil, + show_emails: params.has_key?(:email), + show_warnings: true, + ) end def destroy @@ -192,17 +243,23 @@ class InvitesController < ApplicationController # via the SessionController#sso_login route def perform_accept_invitation params.require(:id) - params.permit(:email, :username, :name, :password, :timezone, :email_token, user_custom_fields: {}) + params.permit( + :email, + :username, + :name, + :password, + :timezone, + :email_token, + user_custom_fields: { + }, + ) invite = Invite.find_by(invite_key: params[:id]) redeeming_user = current_user if invite.present? begin - attrs = { - ip_address: request.remote_ip, - session: session - } + attrs = { ip_address: request.remote_ip, session: session } if redeeming_user attrs[:redeeming_user] = redeeming_user @@ -230,12 +287,10 @@ class InvitesController < ApplicationController end if user.blank? - return render json: failed_json.merge(message: I18n.t('invite.not_found_json')), status: 404 + return render json: failed_json.merge(message: I18n.t("invite.not_found_json")), status: 404 end - if !redeeming_user && user.active? && user.guardian.can_access_forum? - log_on_user(user) - end + log_on_user(user) if !redeeming_user && user.active? && user.guardian.can_access_forum? user.update_timezone_if_missing(params[:timezone]) post_process_invite(user) @@ -246,9 +301,7 @@ class InvitesController < ApplicationController if user.present? if user.active? && user.guardian.can_access_forum? - if redeeming_user - response[:message] = I18n.t("invite.existing_user_success") - end + response[:message] = I18n.t("invite.existing_user_success") if redeeming_user if user.guardian.can_see?(topic) response[:redirect_to] = path(topic.relative_url) @@ -257,20 +310,18 @@ class InvitesController < ApplicationController end else response[:message] = if user.active? - I18n.t('activation.approval_required') + I18n.t("activation.approval_required") else - I18n.t('invite.confirm_email') + I18n.t("invite.confirm_email") end - if user.guardian.can_see?(topic) - cookies[:destination_url] = path(topic.relative_url) - end + cookies[:destination_url] = path(topic.relative_url) if user.guardian.can_see?(topic) end end render json: success_json.merge(response) else - render json: failed_json.merge(message: I18n.t('invite.not_found_json')), status: 404 + render json: failed_json.merge(message: I18n.t("invite.not_found_json")), status: 404 end end @@ -279,7 +330,7 @@ class InvitesController < ApplicationController Invite .where(invited_by: current_user) - .where('expires_at < ?', Time.zone.now) + .where("expires_at < ?", Time.zone.now) .find_each { |invite| invite.trash!(current_user) } render json: success_json @@ -301,13 +352,20 @@ class InvitesController < ApplicationController guardian.ensure_can_resend_all_invites!(current_user) begin - RateLimiter.new(current_user, "bulk-reinvite-per-day", 1, 1.day, apply_limit_to_staff: true).performed! + RateLimiter.new( + current_user, + "bulk-reinvite-per-day", + 1, + 1.day, + apply_limit_to_staff: true, + ).performed! rescue RateLimiter::LimitExceeded return render_json_error(I18n.t("rate_limiter.slow_down")) end - Invite.pending(current_user) - .where('invites.email IS NOT NULL') + Invite + .pending(current_user) + .where("invites.email IS NOT NULL") .find_each { |invite| invite.resend_invite } render json: success_json @@ -326,17 +384,15 @@ class InvitesController < ApplicationController CSV.foreach(file.tempfile, encoding: "bom|utf-8") do |row| # Try to extract a CSV header, if it exists if csv_header.nil? - if row[0] == 'email' + if row[0] == "email" csv_header = row next else - csv_header = ["email", "groups", "topic_id"] + csv_header = %w[email groups topic_id] end end - if row[0].present? - invites.push(csv_header.zip(row).map.to_h.filter { |k, v| v.present? }) - end + invites.push(csv_header.zip(row).map.to_h.filter { |k, v| v.present? }) if row[0].present? break if invites.count >= SiteSetting.max_bulk_invites end @@ -345,7 +401,16 @@ class InvitesController < ApplicationController Jobs.enqueue(:bulk_invite, invites: invites, current_user_id: current_user.id) if invites.count >= SiteSetting.max_bulk_invites - render json: failed_json.merge(errors: [I18n.t("bulk_invite.max_rows", max_bulk_invites: SiteSetting.max_bulk_invites)]), status: 422 + render json: + failed_json.merge( + errors: [ + I18n.t( + "bulk_invite.max_rows", + max_bulk_invites: SiteSetting.max_bulk_invites, + ), + ], + ), + status: 422 else render json: success_json end @@ -375,9 +440,7 @@ class InvitesController < ApplicationController email_verified_by_link = invite.email_token.present? && params[:t] == invite.email_token - if email_verified_by_link - email = invite.email - end + email = invite.email if email_verified_by_link hidden_email = email != invite.email @@ -393,12 +456,10 @@ class InvitesController < ApplicationController hidden_email: hidden_email, username: username, is_invite_link: invite.is_invite_link?, - email_verified_by_link: email_verified_by_link + email_verified_by_link: email_verified_by_link, } - if different_external_email - info[:different_external_email] = true - end + info[:different_external_email] = true if different_external_email if staged_user = User.where(staged: true).with_email(invite.email).first info[:username] = staged_user.username @@ -417,36 +478,46 @@ class InvitesController < ApplicationController secure_session["invite-key"] = invite.invite_key - render layout: 'application' + render layout: "application" end def show_irredeemable_invite(invite) - flash.now[:error] = \ - if invite.blank? - I18n.t('invite.not_found', base_url: Discourse.base_url) - elsif invite.redeemed? - if invite.is_invite_link? - I18n.t('invite.not_found_template_link', site_name: SiteSetting.title, base_url: Discourse.base_url) - else - I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url) - end - elsif invite.expired? - I18n.t('invite.expired', base_url: Discourse.base_url) + flash.now[:error] = if invite.blank? + I18n.t("invite.not_found", base_url: Discourse.base_url) + elsif invite.redeemed? + if invite.is_invite_link? + I18n.t( + "invite.not_found_template_link", + site_name: SiteSetting.title, + base_url: Discourse.base_url, + ) + else + I18n.t( + "invite.not_found_template", + site_name: SiteSetting.title, + base_url: Discourse.base_url, + ) end + elsif invite.expired? + I18n.t("invite.expired", base_url: Discourse.base_url) + end - render layout: 'no_ember' + render layout: "no_ember" end def ensure_invites_allowed - if (!SiteSetting.enable_local_logins && Discourse.enabled_auth_providers.count == 0 && !SiteSetting.enable_discourse_connect) + if ( + !SiteSetting.enable_local_logins && Discourse.enabled_auth_providers.count == 0 && + !SiteSetting.enable_discourse_connect + ) raise Discourse::NotFound end end def ensure_new_registrations_allowed unless SiteSetting.allow_new_registrations - flash[:error] = I18n.t('login.new_registrations_disabled') - render layout: 'no_ember' + flash[:error] = I18n.t("login.new_registrations_disabled") + render layout: "no_ember" false end end @@ -461,13 +532,14 @@ class InvitesController < ApplicationController end def post_process_invite(user) - user.enqueue_welcome_message('welcome_invite') if user.send_welcome_message + user.enqueue_welcome_message("welcome_invite") if user.send_welcome_message Group.refresh_automatic_groups!(:admins, :moderators, :staff) if user.staff? if user.has_password? if !user.active - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) EmailToken.enqueue_signup_email(email_token) end elsif !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins @@ -478,17 +550,19 @@ class InvitesController < ApplicationController def create_topic_invite_notifications(invite, user) invite.topics.each do |topic| if user.guardian.can_see?(topic) - last_notification = user.notifications - .where(notification_type: Notification.types[:invited_to_topic]) - .where(topic_id: topic.id) - .where(post_number: 1) - .where('created_at > ?', 1.hour.ago) + last_notification = + user + .notifications + .where(notification_type: Notification.types[:invited_to_topic]) + .where(topic_id: topic.id) + .where(post_number: 1) + .where("created_at > ?", 1.hour.ago) if !last_notification.exists? topic.create_invite_notification!( user, Notification.types[:invited_to_topic], - invite.invited_by + invite.invited_by, ) end end diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb index 66d8adbcc3c..6e34b868676 100644 --- a/app/controllers/list_controller.rb +++ b/app/controllers/list_controller.rb @@ -6,45 +6,47 @@ class ListController < ApplicationController skip_before_action :check_xhr - before_action :set_category, only: [ - :category_default, - # filtered topics lists - Discourse.filters.map { |f| :"category_#{f}" }, - Discourse.filters.map { |f| :"category_none_#{f}" }, - # top summaries - :category_top, - :category_none_top, - # top pages (ie. with a period) - TopTopic.periods.map { |p| :"category_top_#{p}" }, - TopTopic.periods.map { |p| :"category_none_top_#{p}" }, - # category feeds - :category_feed, - ].flatten + before_action :set_category, + only: [ + :category_default, + # filtered topics lists + Discourse.filters.map { |f| :"category_#{f}" }, + Discourse.filters.map { |f| :"category_none_#{f}" }, + # top summaries + :category_top, + :category_none_top, + # top pages (ie. with a period) + TopTopic.periods.map { |p| :"category_top_#{p}" }, + TopTopic.periods.map { |p| :"category_none_top_#{p}" }, + # category feeds + :category_feed, + ].flatten - before_action :ensure_logged_in, except: [ - :topics_by, - # anonymous filters - Discourse.anonymous_filters, - Discourse.anonymous_filters.map { |f| "#{f}_feed" }, - # anonymous categorized filters - :category_default, - Discourse.anonymous_filters.map { |f| :"category_#{f}" }, - Discourse.anonymous_filters.map { |f| :"category_none_#{f}" }, - # category feeds - :category_feed, - # user topics feed - :user_topics_feed, - # top summaries - :top, - :category_top, - :category_none_top, - # top pages (ie. with a period) - TopTopic.periods.map { |p| :"top_#{p}" }, - TopTopic.periods.map { |p| :"top_#{p}_feed" }, - TopTopic.periods.map { |p| :"category_top_#{p}" }, - TopTopic.periods.map { |p| :"category_none_top_#{p}" }, - :group_topics - ].flatten + before_action :ensure_logged_in, + except: [ + :topics_by, + # anonymous filters + Discourse.anonymous_filters, + Discourse.anonymous_filters.map { |f| "#{f}_feed" }, + # anonymous categorized filters + :category_default, + Discourse.anonymous_filters.map { |f| :"category_#{f}" }, + Discourse.anonymous_filters.map { |f| :"category_none_#{f}" }, + # category feeds + :category_feed, + # user topics feed + :user_topics_feed, + # top summaries + :top, + :category_top, + :category_none_top, + # top pages (ie. with a period) + TopTopic.periods.map { |p| :"top_#{p}" }, + TopTopic.periods.map { |p| :"top_#{p}_feed" }, + TopTopic.periods.map { |p| :"category_top_#{p}" }, + TopTopic.periods.map { |p| :"category_none_top_#{p}" }, + :group_topics, + ].flatten # Create our filters Discourse.filters.each do |filter| @@ -52,7 +54,8 @@ class ListController < ApplicationController list_opts = build_topic_list_options list_opts.merge!(options) if options user = list_target_user - if params[:category].blank? && filter == :latest && !SiteSetting.show_category_definitions_in_topic_lists + if params[:category].blank? && filter == :latest && + !SiteSetting.show_category_definitions_in_topic_lists list_opts[:no_definitions] = true end @@ -61,17 +64,16 @@ class ListController < ApplicationController if guardian.can_create_shared_draft? && @category.present? if @category.id == SiteSetting.shared_drafts_category.to_i # On shared drafts, show the destination category - list.topics.each do |t| - t.includes_destination_category = t.shared_draft.present? - end + list.topics.each { |t| t.includes_destination_category = t.shared_draft.present? } else # When viewing a non-shared draft category, find topics whose # destination are this category - shared_drafts = TopicQuery.new( - user, - category: SiteSetting.shared_drafts_category, - destination_category_id: list_opts[:category] - ).list_latest + shared_drafts = + TopicQuery.new( + user, + category: SiteSetting.shared_drafts_category, + destination_category_id: list_opts[:category], + ).list_latest if shared_drafts.present? && shared_drafts.topics.present? list.shared_drafts = shared_drafts.topics @@ -90,12 +92,14 @@ class ListController < ApplicationController if (filter.to_s != current_homepage) && use_crawler_layout? filter_title = I18n.t("js.filters.#{filter.to_s}.title", count: 0) if list_opts[:category] && @category - @title = I18n.t('js.filters.with_category', filter: filter_title, category: @category.name) + @title = + I18n.t("js.filters.with_category", filter: filter_title, category: @category.name) else - @title = I18n.t('js.filters.with_topics', filter: filter_title) + @title = I18n.t("js.filters.with_topics", filter: filter_title) end @title << " - #{SiteSetting.title}" - elsif @category.blank? && (filter.to_s == current_homepage) && SiteSetting.short_site_description.present? + elsif @category.blank? && (filter.to_s == current_homepage) && + SiteSetting.short_site_description.present? @title = "#{SiteSetting.title} - #{SiteSetting.short_site_description}" end end @@ -116,14 +120,21 @@ class ListController < ApplicationController def category_default canonical_url "#{Discourse.base_url_no_prefix}#{@category.url}" view_method = @category.default_view - view_method = 'latest' unless %w(latest top).include?(view_method) + view_method = "latest" unless %w[latest top].include?(view_method) self.public_send(view_method, category: @category.id) end def topics_by list_opts = build_topic_list_options - target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts) }, [:user_stat, :user_option]) + target_user = + fetch_user_from_params( + { + include_inactive: + current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts), + }, + %i[user_stat user_option], + ) ensure_can_see_profile!(target_user) list = generate_list_for("topics_by", target_user, list_opts) @@ -152,14 +163,15 @@ class ListController < ApplicationController end def message_route(action) - target_user = fetch_user_from_params({ include_inactive: current_user.try(:staff?) }, [:user_stat, :user_option]) + target_user = + fetch_user_from_params( + { include_inactive: current_user.try(:staff?) }, + %i[user_stat user_option], + ) case action - when :private_messages_unread, - :private_messages_new, - :private_messages_group_new, + when :private_messages_unread, :private_messages_new, :private_messages_group_new, :private_messages_group_unread - raise Discourse::NotFound if target_user.id != current_user.id when :private_messages_tag raise Discourse::NotFound if !guardian.can_tag_pms? @@ -181,7 +193,7 @@ class ListController < ApplicationController respond_with_list(list) end - %i{ + %i[ private_messages private_messages_sent private_messages_unread @@ -193,14 +205,12 @@ class ListController < ApplicationController private_messages_group_archive private_messages_warnings private_messages_tag - }.each do |action| - generate_message_route(action) - end + ].each { |action| generate_message_route(action) } def latest_feed discourse_expires_in 1.minute - options = { order: 'created' }.merge(build_topic_list_options) + options = { order: "created" }.merge(build_topic_list_options) @title = "#{SiteSetting.title} - #{I18n.t("rss_description.latest")}" @link = "#{Discourse.base_url}/latest" @@ -208,7 +218,7 @@ class ListController < ApplicationController @description = I18n.t("rss_description.latest") @topic_list = TopicQuery.new(nil, options).list_latest - render 'list', formats: [:rss] + render "list", formats: [:rss] end def top_feed @@ -223,7 +233,7 @@ class ListController < ApplicationController @topic_list = TopicQuery.new(nil).list_top_for(period) - render 'list', formats: [:rss] + render "list", formats: [:rss] end def category_feed @@ -233,10 +243,11 @@ class ListController < ApplicationController @title = "#{@category.name} - #{SiteSetting.title}" @link = "#{Discourse.base_url_no_prefix}#{@category.url}" @atom_link = "#{Discourse.base_url_no_prefix}#{@category.url}.rss" - @description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}" + @description = + "#{I18n.t("topics_in_category", category: @category.name)} #{@category.description}" @topic_list = TopicQuery.new(current_user).list_new_in_category(@category) - render 'list', formats: [:rss] + render "list", formats: [:rss] end def user_topics_feed @@ -244,22 +255,22 @@ class ListController < ApplicationController target_user = fetch_user_from_params ensure_can_see_profile!(target_user) - @title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}" + @title = + "#{SiteSetting.title} - #{I18n.t("rss_description.user_topics", username: target_user.username)}" @link = "#{target_user.full_url}/activity/topics" @atom_link = "#{target_user.full_url}/activity/topics.rss" @description = I18n.t("rss_description.user_topics", username: target_user.username) - @topic_list = TopicQuery - .new(nil, order: 'created') - .public_send("list_topics_by", target_user) + @topic_list = TopicQuery.new(nil, order: "created").public_send("list_topics_by", target_user) - render 'list', formats: [:rss] + render "list", formats: [:rss] end def top(options = nil) options ||= {} period = params[:period] - period ||= ListController.best_period_for(current_user.try(:previous_visit_at), options[:category]) + period ||= + ListController.best_period_for(current_user.try(:previous_visit_at), options[:category]) TopTopic.validate_period(period) public_send("top_#{period}", options) end @@ -299,10 +310,7 @@ class ListController < ApplicationController end define_method("category_none_top_#{period}") do - self.public_send("top_#{period}", - category: @category.id, - no_subcategories: true - ) + self.public_send("top_#{period}", category: @category.id, no_subcategories: true) end # rss feed @@ -315,7 +323,7 @@ class ListController < ApplicationController @atom_link = "#{Discourse.base_url}/top.rss?period=#{period}" @topic_list = TopicQuery.new(nil).list_top_for(period) - render 'list', formats: [:rss] + render "list", formats: [:rss] end end @@ -337,16 +345,17 @@ class ListController < ApplicationController private def page_params - route_params = { format: 'json' } + route_params = { format: "json" } if @category.present? slug_path = @category.slug_path - route_params[:category_slug_path_with_id] = - (slug_path + [@category.id.to_s]).join("/") + route_params[:category_slug_path_with_id] = (slug_path + [@category.id.to_s]).join("/") end - route_params[:username] = UrlHelper.encode_component(params[:username]) if params[:username].present? + route_params[:username] = UrlHelper.encode_component(params[:username]) if params[ + :username + ].present? route_params[:period] = params[:period] if params[:period].present? route_params end @@ -355,9 +364,7 @@ class ListController < ApplicationController category_slug_path_with_id = params.require(:category_slug_path_with_id) @category = Category.find_by_slug_path_with_id(category_slug_path_with_id) - if @category.nil? - raise Discourse::NotFound.new("category not found", check_permalinks: true) - end + raise Discourse::NotFound.new("category not found", check_permalinks: true) if @category.nil? params[:category] = @category.id.to_s @@ -385,13 +392,14 @@ class ListController < ApplicationController return redirect_to path(url), status: 301 end - @description_meta = if @category.uncategorized? - I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) - elsif @category.description_text.present? - @category.description_text - else - SiteSetting.site_description - end + @description_meta = + if @category.uncategorized? + I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) + elsif @category.description_text.present? + @category.description_text + else + SiteSetting.site_description + end if use_crawler_layout? @subcategories = @category.subcategories.select { |c| guardian.can_see?(c) } @@ -431,7 +439,7 @@ class ListController < ApplicationController opts.delete(:category) if page_params.include?(:category_slug_path_with_id) - url = public_send(method, opts.merge(page_params)).sub('.json?', '?') + url = public_send(method, opts.merge(page_params)).sub(".json?", "?") # Unicode usernames need to be encoded when calling Rails' path helper. However, it means that the already # encoded username are encoded again which we do not want. As such, we unencode the url once when unicode usernames @@ -446,16 +454,24 @@ class ListController < ApplicationController end def self.best_period_for(previous_visit_at, category_id = nil) - default_period = ((category_id && Category.where(id: category_id).pluck_first(:default_top_period)) || - SiteSetting.top_page_default_timeframe).to_sym + default_period = + ( + (category_id && Category.where(id: category_id).pluck_first(:default_top_period)) || + SiteSetting.top_page_default_timeframe + ).to_sym best_period_with_topics_for(previous_visit_at, category_id, default_period) || default_period end - def self.best_period_with_topics_for(previous_visit_at, category_id = nil, default_period = SiteSetting.top_page_default_timeframe) + def self.best_period_with_topics_for( + previous_visit_at, + category_id = nil, + default_period = SiteSetting.top_page_default_timeframe + ) best_periods_for(previous_visit_at, default_period.to_sym).find do |period| top_topics = TopTopic.where("#{period}_score > 0") - top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id) if category_id + top_topics = + top_topics.joins(:topic).where("topics.category_id = ?", category_id) if category_id top_topics = top_topics.limit(SiteSetting.topics_per_period_in_top_page) top_topics.count == SiteSetting.topics_per_period_in_top_page end @@ -465,13 +481,12 @@ class ListController < ApplicationController return [default_period, :all].uniq unless date periods = [] - periods << :daily if date > (1.week + 1.day).ago - periods << :weekly if date > (1.month + 1.week).ago - periods << :monthly if date > (3.months + 3.weeks).ago + periods << :daily if date > (1.week + 1.day).ago + periods << :weekly if date > (1.month + 1.week).ago + periods << :monthly if date > (3.months + 3.weeks).ago periods << :quarterly if date > (1.year + 1.month).ago - periods << :yearly if date > 3.years.ago + periods << :yearly if date > 3.years.ago periods << :all periods end - end diff --git a/app/controllers/metadata_controller.rb b/app/controllers/metadata_controller.rb index eff9af7ac56..4d75df06f07 100644 --- a/app/controllers/metadata_controller.rb +++ b/app/controllers/metadata_controller.rb @@ -6,7 +6,7 @@ class MetadataController < ApplicationController def manifest expires_in 1.minutes - render json: default_manifest.to_json, content_type: 'application/manifest+json' + render json: default_manifest.to_json, content_type: "application/manifest+json" end def opensearch @@ -17,13 +17,13 @@ class MetadataController < ApplicationController def app_association_android raise Discourse::NotFound unless SiteSetting.app_association_android.present? expires_in 1.minutes - render plain: SiteSetting.app_association_android, content_type: 'application/json' + render plain: SiteSetting.app_association_android, content_type: "application/json" end def app_association_ios raise Discourse::NotFound unless SiteSetting.app_association_ios.present? expires_in 1.minutes - render plain: SiteSetting.app_association_ios, content_type: 'application/json' + render plain: SiteSetting.app_association_ios, content_type: "application/json" end private @@ -32,56 +32,56 @@ class MetadataController < ApplicationController display = "standalone" if request.user_agent regex = Regexp.new(SiteSetting.pwa_display_browser_regex) - if regex.match(request.user_agent) - display = "browser" - end + display = "browser" if regex.match(request.user_agent) end scheme_id = view_context.scheme_id - primary_color = ColorScheme.hex_for_name('primary', scheme_id) - icon_url_base = UrlHelper.absolute("/svg-sprite/#{Discourse.current_hostname}/icon/#{primary_color}") + primary_color = ColorScheme.hex_for_name("primary", scheme_id) + icon_url_base = + UrlHelper.absolute("/svg-sprite/#{Discourse.current_hostname}/icon/#{primary_color}") manifest = { name: SiteSetting.title, - short_name: SiteSetting.short_title.presence || SiteSetting.title.truncate(12, separator: ' ', omission: ''), + short_name: + SiteSetting.short_title.presence || + SiteSetting.title.truncate(12, separator: " ", omission: ""), description: SiteSetting.site_description, display: display, - start_url: Discourse.base_path.present? ? "#{Discourse.base_path}/" : '.', - background_color: "##{ColorScheme.hex_for_name('secondary', scheme_id)}", - theme_color: "##{ColorScheme.hex_for_name('header_background', scheme_id)}", - icons: [ - ], + start_url: Discourse.base_path.present? ? "#{Discourse.base_path}/" : ".", + background_color: "##{ColorScheme.hex_for_name("secondary", scheme_id)}", + theme_color: "##{ColorScheme.hex_for_name("header_background", scheme_id)}", + icons: [], share_target: { action: "#{Discourse.base_path}/new-topic", method: "GET", enctype: "application/x-www-form-urlencoded", params: { title: "title", - text: "body" - } + text: "body", + }, }, shortcuts: [ { - name: I18n.t('js.topic.create_long'), - short_name: I18n.t('js.topic.create'), + name: I18n.t("js.topic.create_long"), + short_name: I18n.t("js.topic.create"), url: "#{Discourse.base_path}/new-topic", }, { - name: I18n.t('js.user.messages.inbox'), - short_name: I18n.t('js.user.messages.inbox'), + name: I18n.t("js.user.messages.inbox"), + short_name: I18n.t("js.user.messages.inbox"), url: "#{Discourse.base_path}/my/messages", }, { - name: I18n.t('js.user.bookmarks'), - short_name: I18n.t('js.user.bookmarks'), + name: I18n.t("js.user.bookmarks"), + short_name: I18n.t("js.user.bookmarks"), url: "#{Discourse.base_path}/my/activity/bookmarks", }, { - name: I18n.t('js.filters.top.title'), - short_name: I18n.t('js.filters.top.title'), + name: I18n.t("js.filters.top.title"), + short_name: I18n.t("js.filters.top.title"), url: "#{Discourse.base_path}/top", - } - ] + }, + ], } logo = SiteSetting.site_manifest_icon_url @@ -89,41 +89,40 @@ class MetadataController < ApplicationController icon_entry = { src: UrlHelper.absolute(logo), sizes: "512x512", - type: MiniMime.lookup_by_filename(logo)&.content_type || "image/png" + type: MiniMime.lookup_by_filename(logo)&.content_type || "image/png", } manifest[:icons] << icon_entry.dup icon_entry[:purpose] = "maskable" manifest[:icons] << icon_entry end - SiteSetting.manifest_screenshots.split('|').each do |image| - next unless Discourse.store.has_been_uploaded?(image) + SiteSetting + .manifest_screenshots + .split("|") + .each do |image| + next unless Discourse.store.has_been_uploaded?(image) - upload = Upload.find_by(sha1: Upload.extract_sha1(image)) - next if upload.nil? + upload = Upload.find_by(sha1: Upload.extract_sha1(image)) + next if upload.nil? - manifest[:screenshots] = [] if manifest.dig(:screenshots).nil? + manifest[:screenshots] = [] if manifest.dig(:screenshots).nil? - manifest[:screenshots] << { - src: UrlHelper.absolute(image), - sizes: "#{upload.width}x#{upload.height}", - type: "image/#{upload.extension}" - } - end + manifest[:screenshots] << { + src: UrlHelper.absolute(image), + sizes: "#{upload.width}x#{upload.height}", + type: "image/#{upload.extension}", + } + end - if current_user && current_user.trust_level >= 1 && SiteSetting.native_app_install_banner_android - manifest = manifest.merge( - prefer_related_applications: true, - related_applications: [ - { - platform: "play", - id: SiteSetting.android_app_id - } - ] - ) + if current_user && current_user.trust_level >= 1 && + SiteSetting.native_app_install_banner_android + manifest = + manifest.merge( + prefer_related_applications: true, + related_applications: [{ platform: "play", id: SiteSetting.android_app_id }], + ) end manifest end - end diff --git a/app/controllers/new_topic_controller.rb b/app/controllers/new_topic_controller.rb index f6b90176661..2de899377b8 100644 --- a/app/controllers/new_topic_controller.rb +++ b/app/controllers/new_topic_controller.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true class NewTopicController < ApplicationController - def index; end + def index + end end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 2006ddedddb..f4955519574 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class NotificationsController < ApplicationController - requires_login - before_action :ensure_admin, only: [:create, :update, :destroy] - before_action :set_notification, only: [:update, :destroy] + before_action :ensure_admin, only: %i[create update destroy] + before_action :set_notification, only: %i[update destroy] def index user = @@ -20,9 +19,8 @@ class NotificationsController < ApplicationController if notification_types = params[:filter_by_types]&.split(",").presence notification_types.map! do |type| - Notification.types[type.to_sym] || ( - raise Discourse::InvalidParameters.new("invalid notification type: #{type}") - ) + Notification.types[type.to_sym] || + (raise Discourse::InvalidParameters.new("invalid notification type: #{type}")) end end @@ -35,7 +33,8 @@ class NotificationsController < ApplicationController if SiteSetting.legacy_navigation_menu? notifications = Notification.recent_report(current_user, limit, notification_types) else - notifications = Notification.prioritized_list(current_user, count: limit, types: notification_types) + notifications = + Notification.prioritized_list(current_user, count: limit, types: notification_types) # notification_types is blank for the "all notifications" user menu tab include_reviewables = notification_types.blank? && guardian.can_see_review_queue? end @@ -47,7 +46,8 @@ class NotificationsController < ApplicationController end end - if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode && include_reviewables + if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode && + include_reviewables current_user_id = current_user.id Scheduler::Defer.later "bump last seen reviewable for user" do # we lookup current_user again in the background thread to avoid @@ -62,13 +62,13 @@ class NotificationsController < ApplicationController json = { notifications: serialize_data(notifications, NotificationSerializer), - seen_notification_id: current_user.seen_notification_id + seen_notification_id: current_user.seen_notification_id, } if include_reviewables json[:pending_reviewables] = Reviewable.basic_serializers_for_list( Reviewable.user_menu_list_for(current_user), - current_user + current_user, ).as_json end @@ -76,10 +76,8 @@ class NotificationsController < ApplicationController else offset = params[:offset].to_i - notifications = Notification.where(user_id: user.id) - .visible - .includes(:topic) - .order(created_at: :desc) + notifications = + Notification.where(user_id: user.id).visible.includes(:topic).order(created_at: :desc) notifications = notifications.where(read: true) if params[:filter] == "read" @@ -88,12 +86,14 @@ class NotificationsController < ApplicationController total_rows = notifications.dup.count notifications = notifications.offset(offset).limit(60) notifications = filter_inaccessible_notifications(notifications) - render_json_dump(notifications: serialize_data(notifications, NotificationSerializer), - total_rows_notifications: total_rows, - seen_notification_id: user.seen_notification_id, - load_more_notifications: notifications_path(username: user.username, offset: offset + 60, filter: params[:filter])) + render_json_dump( + notifications: serialize_data(notifications, NotificationSerializer), + total_rows_notifications: total_rows, + seen_notification_id: user.seen_notification_id, + load_more_notifications: + notifications_path(username: user.username, offset: offset + 60, filter: params[:filter]), + ) end - end def mark_read @@ -144,7 +144,15 @@ class NotificationsController < ApplicationController end def notification_params - params.permit(:notification_type, :user_id, :data, :read, :topic_id, :post_number, :post_action_id) + params.permit( + :notification_type, + :user_id, + :data, + :read, + :topic_id, + :post_number, + :post_action_id, + ) end def render_notification diff --git a/app/controllers/offline_controller.rb b/app/controllers/offline_controller.rb index 03990e8a5ff..b826e3f30dc 100644 --- a/app/controllers/offline_controller.rb +++ b/app/controllers/offline_controller.rb @@ -5,6 +5,6 @@ class OfflineController < ApplicationController skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required def index - render :offline, content_type: 'text/html' + render :offline, content_type: "text/html" end end diff --git a/app/controllers/onebox_controller.rb b/app/controllers/onebox_controller.rb index dfa81c5e80a..5ccdcd0b9e0 100644 --- a/app/controllers/onebox_controller.rb +++ b/app/controllers/onebox_controller.rb @@ -4,7 +4,7 @@ class OneboxController < ApplicationController requires_login def show - unless params[:refresh] == 'true' + unless params[:refresh] == "true" preview = Oneboxer.cached_preview(params[:url]) preview = preview.strip if preview.present? return render(plain: preview) if preview.present? @@ -16,7 +16,7 @@ class OneboxController < ApplicationController user_id = current_user.id category_id = params[:category_id].to_i topic_id = params[:topic_id].to_i - invalidate = params[:refresh] == 'true' + invalidate = params[:refresh] == "true" url = params[:url] return render(body: nil, status: 404) if Oneboxer.recently_failed?(url) @@ -24,12 +24,14 @@ class OneboxController < ApplicationController hijack(info: "#{url} topic_id: #{topic_id} user_id: #{user_id}") do Oneboxer.preview_onebox!(user_id) - preview = Oneboxer.preview(url, - invalidate_oneboxes: invalidate, - user_id: user_id, - category_id: category_id, - topic_id: topic_id - ) + preview = + Oneboxer.preview( + url, + invalidate_oneboxes: invalidate, + user_id: user_id, + category_id: category_id, + topic_id: topic_id, + ) preview = preview.strip if preview.present? @@ -43,5 +45,4 @@ class OneboxController < ApplicationController end end end - end diff --git a/app/controllers/permalinks_controller.rb b/app/controllers/permalinks_controller.rb index 33f8d04c406..ee34e6bfaad 100644 --- a/app/controllers/permalinks_controller.rb +++ b/app/controllers/permalinks_controller.rb @@ -33,12 +33,8 @@ class PermalinksController < ApplicationController render json: MultiJson.dump(data) rescue Discourse::NotFound - data = { - found: false, - html: build_not_found_page(status: 200), - } + data = { found: false, html: build_not_found_page(status: 200) } render json: MultiJson.dump(data) end end - end diff --git a/app/controllers/post_action_users_controller.rb b/app/controllers/post_action_users_controller.rb index 24cac61295e..7b39cd2fabb 100644 --- a/app/controllers/post_action_users_controller.rb +++ b/app/controllers/post_action_users_controller.rb @@ -23,11 +23,14 @@ class PostActionUsersController < ApplicationController unknown_user_ids.merge(result) end - post_actions = post.post_actions.where(post_action_type_id: post_action_type_id) - .includes(:user) - .offset(page * page_size) - .order('post_actions.created_at ASC') - .limit(page_size) + post_actions = + post + .post_actions + .where(post_action_type_id: post_action_type_id) + .includes(:user) + .offset(page * page_size) + .order("post_actions.created_at ASC") + .limit(page_size) if !guardian.can_see_post_actors?(post.topic, post_action_type_id) raise Discourse::InvalidAccess unless current_user @@ -38,16 +41,15 @@ class PostActionUsersController < ApplicationController total_count = post["#{action_type}_count"].to_i data = { - post_action_users: serialize_data( - post_actions.to_a, - PostActionUserSerializer, - unknown_user_ids: unknown_user_ids - ) + post_action_users: + serialize_data( + post_actions.to_a, + PostActionUserSerializer, + unknown_user_ids: unknown_user_ids, + ), } - if total_count > page_size - data[:total_rows_post_action_users] = total_count - end + data[:total_rows_post_action_users] = total_count if total_count > page_size render_json_dump(data) end diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb index 9e757970446..966af4d4bae 100644 --- a/app/controllers/post_actions_controller.rb +++ b/app/controllers/post_actions_controller.rb @@ -9,16 +9,17 @@ class PostActionsController < ApplicationController def create raise Discourse::NotFound if @post.blank? - creator = PostActionCreator.new( - current_user, - @post, - @post_action_type_id, - is_warning: params[:is_warning], - message: params[:message], - take_action: params[:take_action] == 'true', - flag_topic: params[:flag_topic] == 'true', - queue_for_review: params[:queue_for_review] == 'true' - ) + creator = + PostActionCreator.new( + current_user, + @post, + @post_action_type_id, + is_warning: params[:is_warning], + message: params[:message], + take_action: params[:take_action] == "true", + flag_topic: params[:flag_topic] == "true", + queue_for_review: params[:queue_for_review] == "true", + ) result = creator.perform if result.failed? @@ -29,19 +30,20 @@ class PostActionsController < ApplicationController if @post_action_type_id == PostActionType.types[:like] limiter = result.post_action.post_action_rate_limiter - response.headers['Discourse-Actions-Remaining'] = limiter.remaining.to_s - response.headers['Discourse-Actions-Max'] = limiter.max.to_s + response.headers["Discourse-Actions-Remaining"] = limiter.remaining.to_s + response.headers["Discourse-Actions-Max"] = limiter.max.to_s end render_post_json(@post, add_raw: false) end end def destroy - result = PostActionDestroyer.new( - current_user, - Post.find_by(id: params[:id].to_i), - @post_action_type_id - ).perform + result = + PostActionDestroyer.new( + current_user, + Post.find_by(id: params[:id].to_i), + @post_action_type_id, + ).perform if result.failed? render_json_error(result) @@ -58,15 +60,16 @@ class PostActionsController < ApplicationController flag_topic = params[:flag_topic] flag_topic = flag_topic && (flag_topic == true || flag_topic == "true") - post_id = if flag_topic - begin - Topic.find(params[:id]).posts.first.id - rescue - raise Discourse::NotFound + post_id = + if flag_topic + begin + Topic.find(params[:id]).posts.first.id + rescue StandardError + raise Discourse::NotFound + end + else + params[:id] end - else - params[:id] - end finder = Post.where(id: post_id) diff --git a/app/controllers/post_readers_controller.rb b/app/controllers/post_readers_controller.rb index bc9c3a197b0..a6ff0a1f5e1 100644 --- a/app/controllers/post_readers_controller.rb +++ b/app/controllers/post_readers_controller.rb @@ -7,23 +7,30 @@ class PostReadersController < ApplicationController post = Post.includes(topic: %i[topic_allowed_groups topic_allowed_users]).find(params[:id]) ensure_can_see_readers!(post) - readers = User - .real - .where(staged: false) - .where.not(id: post.user_id) - .joins(:topic_users) - .where.not(topic_users: { last_read_post_number: nil }) - .where('topic_users.topic_id = ? AND topic_users.last_read_post_number >= ?', post.topic_id, post.post_number) + readers = + User + .real + .where(staged: false) + .where.not(id: post.user_id) + .joins(:topic_users) + .where.not(topic_users: { last_read_post_number: nil }) + .where( + "topic_users.topic_id = ? AND topic_users.last_read_post_number >= ?", + post.topic_id, + post.post_number, + ) - readers = readers.where('admin OR moderator') if post.whisper? + readers = readers.where("admin OR moderator") if post.whisper? - readers = readers.map do |r| - { - id: r.id, avatar_template: r.avatar_template, - username: r.username, - username_lower: r.username_lower - } - end + readers = + readers.map do |r| + { + id: r.id, + avatar_template: r.avatar_template, + username: r.username, + username_lower: r.username_lower, + } + end render_json_dump(post_readers: readers) end @@ -31,10 +38,17 @@ class PostReadersController < ApplicationController private def ensure_can_see_readers!(post) - show_readers = GroupUser - .where(user: current_user) - .joins(:group) - .where(groups: { id: post.topic.topic_allowed_groups.map(&:group_id), publish_read_state: true }).exists? + show_readers = + GroupUser + .where(user: current_user) + .joins(:group) + .where( + groups: { + id: post.topic.topic_allowed_groups.map(&:group_id), + publish_read_state: true, + }, + ) + .exists? raise Discourse::InvalidAccess unless show_readers end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index c404cb6969b..b5c9ad2e0e2 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -5,31 +5,27 @@ class PostsController < ApplicationController # see https://github.com/rails/rails/issues/44867 self._flash_types -= [:notice] - requires_login except: [ - :show, - :replies, - :by_number, - :by_date, - :short_link, - :reply_history, - :reply_ids, - :revisions, - :latest_revision, - :expand_embed, - :markdown_id, - :markdown_num, - :cooked, - :latest, - :user_posts_feed - ] + requires_login except: %i[ + show + replies + by_number + by_date + short_link + reply_history + reply_ids + revisions + latest_revision + expand_embed + markdown_id + markdown_num + cooked + latest + user_posts_feed + ] - skip_before_action :preload_json, :check_xhr, only: [ - :markdown_id, - :markdown_num, - :short_link, - :latest, - :user_posts_feed - ] + skip_before_action :preload_json, + :check_xhr, + only: %i[markdown_id markdown_num short_link latest user_posts_feed] MARKDOWN_TOPIC_PAGE_SIZE ||= 100 @@ -42,13 +38,15 @@ class PostsController < ApplicationController post_revision = find_post_revision_from_topic_id render plain: post_revision.modifications[:raw].last elsif params[:post_number].present? - markdown Post.find_by(topic_id: params[:topic_id].to_i, post_number: params[:post_number].to_i) + markdown Post.find_by( + topic_id: params[:topic_id].to_i, + post_number: params[:post_number].to_i, + ) else opts = params.slice(:page) opts[:limit] = MARKDOWN_TOPIC_PAGE_SIZE topic_view = TopicView.new(params[:topic_id], current_user, opts) - content = topic_view.posts.map do |p| - <<~MD + content = topic_view.posts.map { |p| <<~MD } #{p.user.username} | #{p.updated_at} | ##{p.post_number} #{p.raw} @@ -56,7 +54,6 @@ class PostsController < ApplicationController ------------------------- MD - end render plain: content.join end end @@ -68,26 +65,30 @@ class PostsController < ApplicationController if params[:id] == "private_posts" raise Discourse::NotFound if current_user.nil? - posts = Post.private_posts - .order(created_at: :desc) - .where('posts.id <= ?', last_post_id) - .where('posts.id > ?', last_post_id - 50) - .includes(topic: :category) - .includes(user: [:primary_group, :flair_group]) - .includes(:reply_to_user) - .limit(50) + posts = + Post + .private_posts + .order(created_at: :desc) + .where("posts.id <= ?", last_post_id) + .where("posts.id > ?", last_post_id - 50) + .includes(topic: :category) + .includes(user: %i[primary_group flair_group]) + .includes(:reply_to_user) + .limit(50) rss_description = I18n.t("rss_description.private_posts") else - posts = Post.public_posts - .visible - .where(post_type: Post.types[:regular]) - .order(created_at: :desc) - .where('posts.id <= ?', last_post_id) - .where('posts.id > ?', last_post_id - 50) - .includes(topic: :category) - .includes(user: [:primary_group, :flair_group]) - .includes(:reply_to_user) - .limit(50) + posts = + Post + .public_posts + .visible + .where(post_type: Post.types[:regular]) + .order(created_at: :desc) + .where("posts.id <= ?", last_post_id) + .where("posts.id > ?", last_post_id - 50) + .includes(topic: :category) + .includes(user: %i[primary_group flair_group]) + .includes(:reply_to_user) + .limit(50) rss_description = I18n.t("rss_description.posts") @use_canonical = true end @@ -103,17 +104,20 @@ class PostsController < ApplicationController @title = "#{SiteSetting.title} - #{rss_description}" @link = Discourse.base_url @description = rss_description - render 'posts/latest', formats: [:rss] + render "posts/latest", formats: [:rss] end format.json do - render_json_dump(serialize_data(posts, - PostSerializer, - scope: guardian, - root: params[:id], - add_raw: true, - add_title: true, - all_post_actions: counts) - ) + render_json_dump( + serialize_data( + posts, + PostSerializer, + scope: guardian, + root: params[:id], + add_raw: true, + add_title: true, + all_post_actions: counts, + ), + ) end end end @@ -123,35 +127,33 @@ class PostsController < ApplicationController user = fetch_user_from_params raise Discourse::NotFound unless guardian.can_see_profile?(user) - posts = Post.public_posts - .visible - .where(user_id: user.id) - .where(post_type: Post.types[:regular]) - .order(created_at: :desc) - .includes(:user) - .includes(topic: :category) - .limit(50) + posts = + Post + .public_posts + .visible + .where(user_id: user.id) + .where(post_type: Post.types[:regular]) + .order(created_at: :desc) + .includes(:user) + .includes(topic: :category) + .limit(50) posts = posts.reject { |post| !guardian.can_see?(post) || post.topic.blank? } respond_to do |format| format.rss do @posts = posts - @title = "#{SiteSetting.title} - #{I18n.t("rss_description.user_posts", username: user.username)}" + @title = + "#{SiteSetting.title} - #{I18n.t("rss_description.user_posts", username: user.username)}" @link = "#{user.full_url}/activity" @description = I18n.t("rss_description.user_posts", username: user.username) - render 'posts/latest', formats: [:rss] + render "posts/latest", formats: [:rss] end format.json do - render_json_dump(serialize_data(posts, - PostSerializer, - scope: guardian, - add_excerpt: true) - ) + render_json_dump(serialize_data(posts, PostSerializer, scope: guardian, add_excerpt: true)) end end - end def cooked @@ -173,7 +175,7 @@ class PostsController < ApplicationController # Stuff the user in the request object, because that's what IncomingLink wants if params[:user_id] user = User.find_by(id: params[:user_id].to_i) - request['u'] = user.username_lower if user + request["u"] = user.username_lower if user end guardian.ensure_can_see!(post) @@ -188,13 +190,14 @@ class PostsController < ApplicationController manager = NewPostManager.new(current_user, @manager_params) if is_api? - memoized_payload = DistributedMemoizer.memoize(signature_for(@manager_params), 120) do - result = manager.perform - MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false)) - end + memoized_payload = + DistributedMemoizer.memoize(signature_for(@manager_params), 120) do + result = manager.perform + MultiJson.dump(serialize_data(result, NewPostResultSerializer, root: false)) + end parsed_payload = JSON.parse(memoized_payload) - backwards_compatible_json(parsed_payload, parsed_payload['success']) + backwards_compatible_json(parsed_payload, parsed_payload["success"]) else result = manager.perform json = serialize_data(result, NewPostResultSerializer, root: false) @@ -213,27 +216,20 @@ class PostsController < ApplicationController post.image_sizes = params[:image_sizes] if params[:image_sizes].present? - if !guardian.public_send("can_edit?", post) && - post.user_id == current_user.id && - post.edit_time_limit_expired?(current_user) - - return render_json_error(I18n.t('too_late_to_edit')) + if !guardian.public_send("can_edit?", post) && post.user_id == current_user.id && + post.edit_time_limit_expired?(current_user) + return render_json_error(I18n.t("too_late_to_edit")) end guardian.ensure_can_edit!(post) - changes = { - raw: params[:post][:raw], - edit_reason: params[:post][:edit_reason] - } + changes = { raw: params[:post][:raw], edit_reason: params[:post][:edit_reason] } - Post.plugin_permitted_update_params.keys.each do |param| - changes[param] = params[:post][param] - end + Post.plugin_permitted_update_params.keys.each { |param| changes[param] = params[:post][param] } raw_old = params[:post][:raw_old] if raw_old.present? && raw_old != post.raw - return render_json_error(I18n.t('edit_conflict'), status: 409) + return render_json_error(I18n.t("edit_conflict"), status: 409) end # to stay consistent with the create api, we allow for title & category changes here @@ -246,7 +242,7 @@ class PostsController < ApplicationController if category || (changes[:category_id].to_i == 0) guardian.ensure_can_move_topic_to_category!(category) else - return render_json_error(I18n.t('category.errors.not_found')) + return render_json_error(I18n.t("category.errors.not_found")) end end end @@ -273,7 +269,11 @@ class PostsController < ApplicationController result = { post: post_serializer.as_json } if revisor.category_changed.present? - result[:category] = BasicCategorySerializer.new(revisor.category_changed, scope: guardian, root: false).as_json + result[:category] = BasicCategorySerializer.new( + revisor.category_changed, + scope: guardian, + root: false, + ).as_json end render_json_dump(result) @@ -303,11 +303,7 @@ class PostsController < ApplicationController user_custom_fields = User.custom_fields_for_ids(reply_history.pluck(:user_id), added_fields) end - render_serialized( - reply_history, - PostSerializer, - user_custom_fields: user_custom_fields - ) + render_serialized(reply_history, PostSerializer, user_custom_fields: user_custom_fields) end def reply_ids @@ -335,15 +331,25 @@ class PostsController < ApplicationController end unless guardian.can_moderate_topic?(post.topic) - RateLimiter.new(current_user, "delete_post_per_min", SiteSetting.max_post_deletions_per_minute, 1.minute).performed! - RateLimiter.new(current_user, "delete_post_per_day", SiteSetting.max_post_deletions_per_day, 1.day).performed! + RateLimiter.new( + current_user, + "delete_post_per_min", + SiteSetting.max_post_deletions_per_minute, + 1.minute, + ).performed! + RateLimiter.new( + current_user, + "delete_post_per_day", + SiteSetting.max_post_deletions_per_day, + 1.day, + ).performed! end PostDestroyer.new( current_user, post, context: params[:context], - force_destroy: force_destroy + force_destroy: force_destroy, ).destroy render body: nil @@ -351,8 +357,8 @@ class PostsController < ApplicationController def expand_embed render json: { cooked: TopicEmbed.expanded_for(find_post_from_params) } - rescue - render_json_error I18n.t('errors.embed.load_from_remote') + rescue StandardError + render_json_error I18n.t("errors.embed.load_from_remote") end def recover @@ -360,8 +366,18 @@ class PostsController < ApplicationController guardian.ensure_can_recover_post!(post) unless guardian.can_moderate_topic?(post.topic) - RateLimiter.new(current_user, "delete_post_per_min", SiteSetting.max_post_deletions_per_minute, 1.minute).performed! - RateLimiter.new(current_user, "delete_post_per_day", SiteSetting.max_post_deletions_per_day, 1.day).performed! + RateLimiter.new( + current_user, + "delete_post_per_min", + SiteSetting.max_post_deletions_per_minute, + 1.minute, + ).performed! + RateLimiter.new( + current_user, + "delete_post_per_day", + SiteSetting.max_post_deletions_per_day, + 1.day, + ).performed! end destroyer = PostDestroyer.new(current_user, post) @@ -383,7 +399,11 @@ class PostsController < ApplicationController Post.transaction do posts.each_with_index do |p, i| - PostDestroyer.new(current_user, p, defer_flags: !(agree_with_first_reply_flag && i == 0)).destroy + PostDestroyer.new( + current_user, + p, + defer_flags: !(agree_with_first_reply_flag && i == 0), + ).destroy end end @@ -418,7 +438,8 @@ class PostsController < ApplicationController raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions? post_revision = find_post_revision_from_params - post_revision_serializer = PostRevisionSerializer.new(post_revision, scope: guardian, root: false) + post_revision_serializer = + PostRevisionSerializer.new(post_revision, scope: guardian, root: false) render_json_dump(post_revision_serializer) end @@ -427,7 +448,8 @@ class PostsController < ApplicationController raise Discourse::NotFound if post.hidden && !guardian.can_view_hidden_post_revisions? post_revision = find_latest_post_revision_from_params - post_revision_serializer = PostRevisionSerializer.new(post_revision, scope: guardian, root: false) + post_revision_serializer = + PostRevisionSerializer.new(post_revision, scope: guardian, root: false) render_json_dump(post_revision_serializer) end @@ -473,17 +495,27 @@ class PostsController < ApplicationController post_revision.post = post guardian.ensure_can_see!(post_revision) guardian.ensure_can_edit!(post) - return render_json_error(I18n.t('revert_version_same')) if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? && post_revision.modifications["category_id"].blank? + if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? && + post_revision.modifications["category_id"].blank? + return render_json_error(I18n.t("revert_version_same")) + end topic = Topic.with_deleted.find(post.topic_id) changes = {} - changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications["raw"].present? && post_revision.modifications["raw"][0] != post.raw + changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications[ + "raw" + ].present? && post_revision.modifications["raw"][0] != post.raw if post.is_first_post? - changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications["title"].present? && post_revision.modifications["title"][0] != topic.title - changes[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? && post_revision.modifications["category_id"][0] != topic.category.id + changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications[ + "title" + ].present? && post_revision.modifications["title"][0] != topic.title + changes[:category_id] = post_revision.modifications["category_id"][ + 0 + ] if post_revision.modifications["category_id"].present? && + post_revision.modifications["category_id"][0] != topic.category.id end - return render_json_error(I18n.t('revert_version_same')) unless changes.length > 0 + return render_json_error(I18n.t("revert_version_same")) unless changes.length > 0 changes[:edit_reason] = "reverted to version ##{post_revision.number.to_i - 1}" revisor = PostRevisor.new(post, topic) @@ -500,8 +532,14 @@ class PostsController < ApplicationController result = { post: post_serializer.as_json } if post.is_first_post? - result[:topic] = BasicTopicSerializer.new(topic, scope: guardian, root: false).as_json if post_revision.modifications["title"].present? - result[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? + result[:topic] = BasicTopicSerializer.new( + topic, + scope: guardian, + root: false, + ).as_json if post_revision.modifications["title"].present? + result[:category_id] = post_revision.modifications["category_id"][ + 0 + ] if post_revision.modifications["category_id"].present? end render_json_dump(result) @@ -524,7 +562,7 @@ class PostsController < ApplicationController post.custom_fields[Post::NOTICE] = { type: Post.notices[:custom], raw: params[:notice], - cooked: PrettyText.cook(params[:notice], features: { onebox: false }) + cooked: PrettyText.cook(params[:notice], features: { onebox: false }), } else post.custom_fields.delete(Post::NOTICE) @@ -535,7 +573,7 @@ class PostsController < ApplicationController StaffActionLogger.new(current_user).log_post_staff_note( post, old_value: old_notice&.[]("raw"), - new_value: params[:notice] + new_value: params[:notice], ) render body: nil @@ -544,14 +582,16 @@ class PostsController < ApplicationController def destroy_bookmark params.require(:post_id) - bookmark_id = Bookmark.where( - bookmarkable_id: params[:post_id], - bookmarkable_type: "Post", - user_id: current_user.id - ).pluck_first(:id) + bookmark_id = + Bookmark.where( + bookmarkable_id: params[:post_id], + bookmarkable_type: "Post", + user_id: current_user.id, + ).pluck_first(:id) destroyed_bookmark = BookmarkManager.new(current_user).destroy(bookmark_id) - render json: success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) + render json: + success_json.merge(BookmarkManager.bookmark_metadata(destroyed_bookmark, current_user)) end def wiki @@ -596,8 +636,9 @@ class PostsController < ApplicationController def flagged_posts Discourse.deprecate( - 'PostsController#flagged_posts is deprecated. Please use /review instead.', - since: '2.8.0.beta4', drop_from: '2.9' + "PostsController#flagged_posts is deprecated. Please use /review instead.", + since: "2.8.0.beta4", + drop_from: "2.9", ) params.permit(:offset, :limit) @@ -607,10 +648,14 @@ class PostsController < ApplicationController offset = [params[:offset].to_i, 0].max limit = [(params[:limit] || 60).to_i, 100].min - posts = user_posts(guardian, user.id, offset: offset, limit: limit) - .where(id: PostAction.where(post_action_type_id: PostActionType.notify_flag_type_ids) - .where(disagreed_at: nil) - .select(:post_id)) + posts = + user_posts(guardian, user.id, offset: offset, limit: limit).where( + id: + PostAction + .where(post_action_type_id: PostActionType.notify_flag_type_ids) + .where(disagreed_at: nil) + .select(:post_id), + ) render_serialized(posts, AdminUserActionSerializer) end @@ -633,7 +678,11 @@ class PostsController < ApplicationController user = fetch_user_from_params raise Discourse::NotFound unless guardian.can_edit_user?(user) - render_serialized(user.pending_posts.order(created_at: :desc), PendingPostSerializer, root: :pending_posts) + render_serialized( + user.pending_posts.order(created_at: :desc), + PendingPostSerializer, + root: :pending_posts, + ) end protected @@ -692,7 +741,8 @@ class PostsController < ApplicationController end def find_post_revision_from_topic_id - post = Post.find_by(topic_id: params[:topic_id].to_i, post_number: (params[:post_number] || 1).to_i) + post = + Post.find_by(topic_id: params[:topic_id].to_i, post_number: (params[:post_number] || 1).to_i) raise Discourse::NotFound unless guardian.can_see?(post) revision = params[:revision].to_i @@ -711,26 +761,26 @@ class PostsController < ApplicationController def user_posts(guardian, user_id, opts) # Topic.unscoped is necessary to remove the default deleted_at: nil scope - posts = Topic.unscoped do - Post.includes(:user, :topic, :deleted_by, :user_actions) - .where(user_id: user_id) - .with_deleted - .order(created_at: :desc) - end + posts = + Topic.unscoped do + Post + .includes(:user, :topic, :deleted_by, :user_actions) + .where(user_id: user_id) + .with_deleted + .order(created_at: :desc) + end if guardian.user.moderator? - # Awful hack, but you can't seem to remove the `default_scope` when joining # So instead I grab the topics separately topic_ids = posts.dup.pluck(:topic_id) - topics = Topic.where(id: topic_ids).with_deleted.where.not(archetype: 'private_message') + topics = Topic.where(id: topic_ids).with_deleted.where.not(archetype: "private_message") topics = topics.secured(guardian) posts = posts.where(topic_id: topics.pluck(:id)) end - posts.offset(opts[:offset]) - .limit(opts[:limit]) + posts.offset(opts[:offset]).limit(opts[:limit]) end def create_params @@ -747,18 +797,18 @@ class PostsController < ApplicationController :typing_duration_msecs, :composer_open_duration_msecs, :visible, - :draft_key + :draft_key, ] Post.plugin_permitted_create_params.each do |key, value| if value[:plugin].enabled? - permitted << case value[:type] - when :string - key.to_sym - when :array - { key => [] } - when :hash - { key => {} } + permitted << case value[:type] + when :string + key.to_sym + when :array + { key => [] } + when :hash + { key => {} } end end end @@ -785,11 +835,14 @@ class PostsController < ApplicationController permitted << :external_id end - result = params.permit(*permitted).tap do |allowed| - allowed[:image_sizes] = params[:image_sizes] - # TODO this does not feel right, we should name what meta_data is allowed - allowed[:meta_data] = params[:meta_data] - end + result = + params + .permit(*permitted) + .tap do |allowed| + allowed[:image_sizes] = params[:image_sizes] + # TODO this does not feel right, we should name what meta_data is allowed + allowed[:meta_data] = params[:meta_data] + end # Staff are allowed to pass `is_warning` if current_user.staff? @@ -804,14 +857,20 @@ class PostsController < ApplicationController result[:no_bump] = true end - if params[:shared_draft] == 'true' + if params[:shared_draft] == "true" raise Discourse::InvalidParameters.new(:shared_draft) unless guardian.can_create_shared_draft? result[:shared_draft] = true end if params[:whisper] == "true" - raise Discourse::InvalidAccess.new("invalid_whisper_access", nil, custom_message: "invalid_whisper_access") unless guardian.can_create_whisper? + unless guardian.can_create_whisper? + raise Discourse::InvalidAccess.new( + "invalid_whisper_access", + nil, + custom_message: "invalid_whisper_access", + ) + end result[:post_type] = Post.types[:whisper] end @@ -827,14 +886,19 @@ class PostsController < ApplicationController result[:referrer] = request.env["HTTP_REFERER"] if recipients = result[:target_usernames] - Discourse.deprecate("`target_usernames` is deprecated, use `target_recipients` instead.", output_in_test: true, drop_from: '2.9.0') + Discourse.deprecate( + "`target_usernames` is deprecated, use `target_recipients` instead.", + output_in_test: true, + drop_from: "2.9.0", + ) else recipients = result[:target_recipients] end if recipients recipients = recipients.split(",").map(&:downcase) - groups = Group.messageable(current_user).where('lower(name) in (?)', recipients).pluck('lower(name)') + groups = + Group.messageable(current_user).where("lower(name) in (?)", recipients).pluck("lower(name)") recipients -= groups emails = recipients.select { |user| user.match(/@/) } recipients -= emails @@ -848,13 +912,14 @@ class PostsController < ApplicationController end def signature_for(args) - +"post##" << Digest::SHA1.hexdigest(args - .to_h - .to_a - .concat([["user", current_user.id]]) - .sort { |x, y| x[0] <=> y[0] }.join do |x, y| - "#{x}:#{y}" - end) + +"post##" << Digest::SHA1.hexdigest( + args + .to_h + .to_a + .concat([["user", current_user.id]]) + .sort { |x, y| x[0] <=> y[0] } + .join { |x, y| "#{x}:#{y}" }, + ) end def display_post(post) @@ -873,11 +938,13 @@ class PostsController < ApplicationController end def find_post_from_params_by_date - by_date_finder = TopicView.new(params[:topic_id], current_user) - .filtered_posts - .where("created_at >= ?", Time.zone.parse(params[:date])) - .order("created_at ASC") - .limit(1) + by_date_finder = + TopicView + .new(params[:topic_id], current_user) + .filtered_posts + .where("created_at >= ?", Time.zone.parse(params[:date])) + .order("created_at ASC") + .limit(1) find_post_using(by_date_finder) end @@ -892,15 +959,14 @@ class PostsController < ApplicationController post.topic = Topic.with_deleted.find_by(id: post.topic_id) if !post.topic || - ( - (post.deleted_at.present? || post.topic.deleted_at.present?) && - !guardian.can_moderate_topic?(post.topic) - ) + ( + (post.deleted_at.present? || post.topic.deleted_at.present?) && + !guardian.can_moderate_topic?(post.topic) + ) raise Discourse::NotFound end guardian.ensure_can_see!(post) post end - end diff --git a/app/controllers/presence_controller.rb b/app/controllers/presence_controller.rb index fe26e68fab1..ad68f801c94 100644 --- a/app/controllers/presence_controller.rb +++ b/app/controllers/presence_controller.rb @@ -9,18 +9,23 @@ class PresenceController < ApplicationController def get names = params.require(:channels) - raise Discourse::InvalidParameters.new(:channels) if !(names.is_a?(Array) && names.all? { |n| n.is_a? String }) + if !(names.is_a?(Array) && names.all? { |n| n.is_a? String }) + raise Discourse::InvalidParameters.new(:channels) + end names.uniq! - raise Discourse::InvalidParameters.new("Too many channels") if names.length > MAX_CHANNELS_PER_REQUEST - - user_group_ids = if current_user - GroupUser.where(user_id: current_user.id).pluck("group_id") - else - [] + if names.length > MAX_CHANNELS_PER_REQUEST + raise Discourse::InvalidParameters.new("Too many channels") end + user_group_ids = + if current_user + GroupUser.where(user_id: current_user.id).pluck("group_id") + else + [] + end + result = {} names.each do |name| channel = PresenceChannel.new(name) @@ -38,19 +43,23 @@ class PresenceController < ApplicationController def update client_id = params[:client_id] - raise Discourse::InvalidParameters.new(:client_id) if !client_id.is_a?(String) || client_id.blank? + if !client_id.is_a?(String) || client_id.blank? + raise Discourse::InvalidParameters.new(:client_id) + end # JS client is designed to throttle to one request per second # When no changes are being made, it makes one request every 30 seconds RateLimiter.new(nil, "update-presence-#{current_user.id}", 20, 10.seconds).performed! present_channels = params[:present_channels] - if present_channels && !(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String }) + if present_channels && + !(present_channels.is_a?(Array) && present_channels.all? { |c| c.is_a? String }) raise Discourse::InvalidParameters.new(:present_channels) end leave_channels = params[:leave_channels] - if leave_channels && !(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String }) + if leave_channels && + !(leave_channels.is_a?(Array) && leave_channels.all? { |c| c.is_a? String }) raise Discourse::InvalidParameters.new(:leave_channels) end diff --git a/app/controllers/published_pages_controller.rb b/app/controllers/published_pages_controller.rb index abf27ccda50..328e2ba408f 100644 --- a/app/controllers/published_pages_controller.rb +++ b/app/controllers/published_pages_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PublishedPagesController < ApplicationController - skip_before_action :preload_json skip_before_action :check_xhr, :verify_authenticity_token, only: [:show] before_action :ensure_publish_enabled @@ -19,12 +18,14 @@ class PublishedPagesController < ApplicationController begin guardian.ensure_can_see!(pp.topic) rescue Discourse::InvalidAccess => e - return rescue_discourse_actions( - :invalid_access, - 403, - include_ember: false, - custom_message: e.custom_message, - group: e.group + return( + rescue_discourse_actions( + :invalid_access, + 403, + include_ember: false, + custom_message: e.custom_message, + group: e.group, + ) ) end end @@ -37,18 +38,19 @@ class PublishedPagesController < ApplicationController TopicViewItem.add(pp.topic.id, request.remote_ip, current_user ? current_user.id : nil) - @body_classes = Set.new([ - 'published-page', - params[:slug], - "topic-#{@topic.id}", - @topic.tags.pluck(:name) - ].flatten.compact) + @body_classes = + Set.new( + [ + "published-page", + params[:slug], + "topic-#{@topic.id}", + @topic.tags.pluck(:name), + ].flatten.compact, + ) - if @topic.category - @body_classes << @topic.category.slug - end + @body_classes << @topic.category.slug if @topic.category - render layout: 'publish' + render layout: "publish" end def details @@ -60,12 +62,13 @@ class PublishedPagesController < ApplicationController def upsert pp_params = params.require(:published_page) - result, pp = PublishedPage.publish!( - current_user, - fetch_topic, - pp_params[:slug].strip, - pp_params.permit(:public) - ) + result, pp = + PublishedPage.publish!( + current_user, + fetch_topic, + pp_params[:slug].strip, + pp_params.permit(:public), + ) json_result(pp, serializer: PublishedPageSerializer) { result } end @@ -85,7 +88,7 @@ class PublishedPagesController < ApplicationController end end -private + private def fetch_topic topic = Topic.find_by(id: params[:topic_id]) @@ -94,18 +97,13 @@ private end def ensure_publish_enabled - if !SiteSetting.enable_page_publishing? || SiteSetting.secure_uploads - raise Discourse::NotFound - end + raise Discourse::NotFound if !SiteSetting.enable_page_publishing? || SiteSetting.secure_uploads end def enforce_login_required! - if SiteSetting.login_required? && - !current_user && - !SiteSetting.show_published_pages_login_required? && - redirect_to_login + if SiteSetting.login_required? && !current_user && + !SiteSetting.show_published_pages_login_required? && redirect_to_login true end end - end diff --git a/app/controllers/push_notification_controller.rb b/app/controllers/push_notification_controller.rb index 0b0a75b39d2..a1cd3b07659 100644 --- a/app/controllers/push_notification_controller.rb +++ b/app/controllers/push_notification_controller.rb @@ -18,6 +18,6 @@ class PushNotificationController < ApplicationController private def push_params - params.require(:subscription).permit(:endpoint, keys: [:p256dh, :auth]) + params.require(:subscription).permit(:endpoint, keys: %i[p256dh auth]) end end diff --git a/app/controllers/qunit_controller.rb b/app/controllers/qunit_controller.rb index 4ee3392d507..31c411688d8 100644 --- a/app/controllers/qunit_controller.rb +++ b/app/controllers/qunit_controller.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class QunitController < ApplicationController - skip_before_action *%i{ - check_xhr - preload_json - redirect_to_login_if_required - } + skip_before_action *%i[check_xhr preload_json redirect_to_login_if_required] layout false def theme @@ -25,16 +21,20 @@ class QunitController < ApplicationController end if param_key && theme.blank? - return render plain: "Can't find theme with #{param_key} #{get_param(param_key).inspect}", status: :not_found + return( + render plain: "Can't find theme with #{param_key} #{get_param(param_key).inspect}", + status: :not_found + ) end if !param_key - @suggested_themes = Theme - .where( - id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id) - ) - .order(updated_at: :desc) - .pluck(:id, :name) + @suggested_themes = + Theme + .where( + id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id), + ) + .order(updated_at: :desc) + .pluck(:id, :name) return end diff --git a/app/controllers/reviewable_claimed_topics_controller.rb b/app/controllers/reviewable_claimed_topics_controller.rb index 2a6033118ba..8d5cc4a25dd 100644 --- a/app/controllers/reviewable_claimed_topics_controller.rb +++ b/app/controllers/reviewable_claimed_topics_controller.rb @@ -10,12 +10,10 @@ class ReviewableClaimedTopicsController < ApplicationController begin ReviewableClaimedTopic.create!(user_id: current_user.id, topic_id: topic.id) rescue ActiveRecord::RecordInvalid - return render_json_error(I18n.t('reviewables.conflict'), status: 409) + return render_json_error(I18n.t("reviewables.conflict"), status: 409) end - topic.reviewables.find_each do |reviewable| - reviewable.log_history(:claimed, current_user) - end + topic.reviewables.find_each { |reviewable| reviewable.log_history(:claimed, current_user) } notify_users(topic, current_user) render json: success_json @@ -27,9 +25,7 @@ class ReviewableClaimedTopicsController < ApplicationController guardian.ensure_can_claim_reviewable_topic!(topic) ReviewableClaimedTopic.where(topic_id: topic.id).delete_all - topic.reviewables.find_each do |reviewable| - reviewable.log_history(:unclaimed, current_user) - end + topic.reviewables.find_each { |reviewable| reviewable.log_history(:unclaimed, current_user) } notify_users(topic, nil) render json: success_json @@ -40,7 +36,8 @@ class ReviewableClaimedTopicsController < ApplicationController def notify_users(topic, claimed_by) group_ids = Set.new([Group::AUTO_GROUPS[:staff]]) - if SiteSetting.enable_category_group_moderation? && group_id = topic.category&.reviewable_by_group_id.presence + if SiteSetting.enable_category_group_moderation? && + group_id = topic.category&.reviewable_by_group_id.presence group_ids.add(group_id) end diff --git a/app/controllers/reviewables_controller.rb b/app/controllers/reviewables_controller.rb index 4d3ebf143af..61af8f0e3ec 100644 --- a/app/controllers/reviewables_controller.rb +++ b/app/controllers/reviewables_controller.rb @@ -5,7 +5,7 @@ class ReviewablesController < ApplicationController PER_PAGE = 10 - before_action :version_required, only: [:update, :perform] + before_action :version_required, only: %i[update perform] before_action :ensure_can_see, except: [:destroy] def index @@ -15,20 +15,21 @@ class ReviewablesController < ApplicationController raise Discourse::InvalidParameters.new(:type) unless Reviewable.valid_type?(params[:type]) end - status = (params[:status] || 'pending').to_sym + status = (params[:status] || "pending").to_sym raise Discourse::InvalidParameters.new(:status) unless allowed_statuses.include?(status) topic_id = params[:topic_id] ? params[:topic_id].to_i : nil category_id = params[:category_id] ? params[:category_id].to_i : nil custom_keys = Reviewable.custom_filters.map(&:first) - additional_filters = JSON.parse(params.fetch(:additional_filters, {}), symbolize_names: true).slice(*custom_keys) + additional_filters = + JSON.parse(params.fetch(:additional_filters, {}), symbolize_names: true).slice(*custom_keys) filters = { ids: params[:ids], status: status, category_id: category_id, topic_id: topic_id, - additional_filters: additional_filters.reject { |_, v| v.blank? } + additional_filters: additional_filters.reject { |_, v| v.blank? }, } %i[priority username reviewed_by from_date to_date type sort_order].each do |filter_key| @@ -36,7 +37,8 @@ class ReviewablesController < ApplicationController end total_rows = Reviewable.list_for(current_user, **filters).count - reviewables = Reviewable.list_for(current_user, **filters.merge(limit: PER_PAGE, offset: offset)).to_a + reviewables = + Reviewable.list_for(current_user, **filters.merge(limit: PER_PAGE, offset: offset)).to_a claimed_topics = ReviewableClaimedTopic.claimed_hash(reviewables.map { |r| r.topic_id }.uniq) @@ -44,23 +46,25 @@ class ReviewablesController < ApplicationController # is mutated by the serializer and contains the side loaded records which must be merged in the end. hash = {} json = { - reviewables: reviewables.map! do |r| - result = r.serializer.new( - r, - root: nil, - hash: hash, - scope: guardian, - claimed_topics: claimed_topics - ).as_json - hash[:bundled_actions].uniq! - (hash['actions'] || []).uniq! - result - end, - meta: filters.merge( - total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types, - reviewable_count: current_user.reviewable_count, - unseen_reviewable_count: Reviewable.unseen_reviewable_count(current_user) - ) + reviewables: + reviewables.map! do |r| + result = + r + .serializer + .new(r, root: nil, hash: hash, scope: guardian, claimed_topics: claimed_topics) + .as_json + hash[:bundled_actions].uniq! + (hash["actions"] || []).uniq! + result + end, + meta: + filters.merge( + total_rows_reviewables: total_rows, + types: meta_types, + reviewable_types: Reviewable.types, + reviewable_count: current_user.reviewable_count, + unseen_reviewable_count: Reviewable.unseen_reviewable_count(current_user), + ), } if (offset + PER_PAGE) < total_rows json[:meta][:load_more_reviewables] = review_path(filters.merge(offset: offset + PER_PAGE)) @@ -72,10 +76,11 @@ class ReviewablesController < ApplicationController def user_menu_list json = { - reviewables: Reviewable.basic_serializers_for_list( - Reviewable.user_menu_list_for(current_user), - current_user - ).as_json + reviewables: + Reviewable.basic_serializers_for_list( + Reviewable.user_menu_list_for(current_user), + current_user, + ).as_json, } render_json_dump(json, rest_serializer: true) end @@ -108,17 +113,17 @@ class ReviewablesController < ApplicationController meta[:unique_users] = users.size end - topics = Topic.where(id: topic_ids).order('reviewable_score DESC') + topics = Topic.where(id: topic_ids).order("reviewable_score DESC") render_serialized( topics, ReviewableTopicSerializer, - root: 'reviewable_topics', + root: "reviewable_topics", stats: stats, claimed_topics: ReviewableClaimedTopic.claimed_hash(topic_ids), rest_serializer: true, meta: { - types: meta_types - } + types: meta_types, + }, ) end @@ -129,7 +134,7 @@ class ReviewablesController < ApplicationController { reviewable: reviewable, scores: reviewable.explain_score }, ReviewableExplanationSerializer, rest_serializer: true, - root: 'reviewable_explanation' + root: "reviewable_explanation", ) end @@ -141,10 +146,10 @@ class ReviewablesController < ApplicationController reviewable.serializer, rest_serializer: true, claimed_topics: ReviewableClaimedTopic.claimed_hash([reviewable.topic_id]), - root: 'reviewable', + root: "reviewable", meta: { - types: meta_types - } + types: meta_types, + }, ) end @@ -186,7 +191,7 @@ class ReviewablesController < ApplicationController render_json_error(reviewable.errors) end rescue Reviewable::UpdateConflict - render_json_error(I18n.t('reviewables.conflict'), status: 409) + render_json_error(I18n.t("reviewables.conflict"), status: 409) end end @@ -201,23 +206,32 @@ class ReviewablesController < ApplicationController return render_json_error(error) end - args.merge!(reject_reason: params[:reject_reason], send_email: params[:send_email] != "false") if reviewable.type == 'ReviewableUser' - - plugin_params = DiscoursePluginRegistry.reviewable_params.select do |reviewable_param| - reviewable.type == reviewable_param[:type].to_s.classify + if reviewable.type == "ReviewableUser" + args.merge!( + reject_reason: params[:reject_reason], + send_email: params[:send_email] != "false", + ) end + + plugin_params = + DiscoursePluginRegistry.reviewable_params.select do |reviewable_param| + reviewable.type == reviewable_param[:type].to_s.classify + end args.merge!(params.slice(*plugin_params.map { |pp| pp[:param] }).permit!) result = reviewable.perform(current_user, params[:action_id].to_sym, args) rescue Reviewable::InvalidAction => e - if reviewable.type == 'ReviewableUser' && !reviewable.pending? && reviewable.target.blank? - raise Discourse::NotFound.new(e.message, custom_message: "reviewables.already_handled_and_user_not_exist") + if reviewable.type == "ReviewableUser" && !reviewable.pending? && reviewable.target.blank? + raise Discourse::NotFound.new( + e.message, + custom_message: "reviewables.already_handled_and_user_not_exist", + ) else # Consider InvalidAction an InvalidAccess raise Discourse::InvalidAccess.new(e.message) end rescue Reviewable::UpdateConflict - return render_json_error(I18n.t('reviewables.conflict'), status: 409) + return render_json_error(I18n.t("reviewables.conflict"), status: 409) end if result.success? @@ -230,7 +244,7 @@ class ReviewablesController < ApplicationController def settings raise Discourse::InvalidAccess.new unless current_user.admin? - post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order('id') + post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order("id") if request.put? params[:reviewable_priorities].each do |id, priority| @@ -239,7 +253,7 @@ class ReviewablesController < ApplicationController # to calculate it a different way. PostActionType.where(id: id).update_all( reviewable_priority: priority.to_i, - score_bonus: priority.to_f + score_bonus: priority.to_f, ) end end @@ -249,7 +263,7 @@ class ReviewablesController < ApplicationController render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true) end -protected + protected def claim_error?(reviewable) return if SiteSetting.reviewable_claiming == "disabled" || reviewable.topic_id.blank? @@ -257,9 +271,9 @@ protected claimed_by_id = ReviewableClaimedTopic.where(topic_id: reviewable.topic_id).pluck(:user_id)[0] if SiteSetting.reviewable_claiming == "required" && claimed_by_id.blank? - I18n.t('reviewables.must_claim') + I18n.t("reviewables.must_claim") elsif claimed_by_id.present? && claimed_by_id != current_user.id - I18n.t('reviewables.user_claimed') + I18n.t("reviewables.user_claimed") end end @@ -274,18 +288,11 @@ protected end def version_required - if params[:version].blank? - render_json_error(I18n.t('reviewables.missing_version'), status: 422) - end + render_json_error(I18n.t("reviewables.missing_version"), status: 422) if params[:version].blank? end def meta_types - { - created_by: 'user', - target_created_by: 'user', - reviewed_by: 'user', - claimed_by: 'user' - } + { created_by: "user", target_created_by: "user", reviewed_by: "user", claimed_by: "user" } end def ensure_can_see diff --git a/app/controllers/robots_txt_controller.rb b/app/controllers/robots_txt_controller.rb index 706a67be772..d9e4ea2a73c 100644 --- a/app/controllers/robots_txt_controller.rb +++ b/app/controllers/robots_txt_controller.rb @@ -7,7 +7,7 @@ class RobotsTxtController < ApplicationController OVERRIDDEN_HEADER = "# This robots.txt file has been customized at /admin/customize/robots\n" # NOTE: order is important! - DISALLOWED_PATHS ||= %w{ + DISALLOWED_PATHS ||= %w[ /admin/ /auth/ /assets/browser-update*.js @@ -16,18 +16,9 @@ class RobotsTxtController < ApplicationController /user-api-key /*?api_key* /*?*api_key* - } + ] - DISALLOWED_WITH_HEADER_PATHS ||= %w{ - /badges - /u/ - /my - /search - /tag/*/l - /g - /t/*/*.rss - /c/*.rss - } + DISALLOWED_WITH_HEADER_PATHS ||= %w[/badges /u/ /my /search /tag/*/l /g /t/*/*.rss /c/*.rss] def index if (overridden = SiteSetting.overridden_robots_txt.dup).present? @@ -37,9 +28,9 @@ class RobotsTxtController < ApplicationController end if SiteSetting.allow_index_in_robots_txt? @robots_info = self.class.fetch_default_robots_info - render :index, content_type: 'text/plain' + render :index, content_type: "text/plain" else - render :no_index, content_type: 'text/plain' + render :no_index, content_type: "text/plain" end end @@ -56,32 +47,37 @@ class RobotsTxtController < ApplicationController def self.fetch_default_robots_info deny_paths_googlebot = DISALLOWED_PATHS.map { |p| Discourse.base_path + p } - deny_paths = deny_paths_googlebot + DISALLOWED_WITH_HEADER_PATHS.map { |p| Discourse.base_path + p } - deny_all = [ "#{Discourse.base_path}/" ] + deny_paths = + deny_paths_googlebot + DISALLOWED_WITH_HEADER_PATHS.map { |p| Discourse.base_path + p } + deny_all = ["#{Discourse.base_path}/"] result = { - header: "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file", - agents: [] + header: + "# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file", + agents: [], } if SiteSetting.allowed_crawler_user_agents.present? - SiteSetting.allowed_crawler_user_agents.split('|').each do |agent| - paths = agent == "Googlebot" ? deny_paths_googlebot : deny_paths - result[:agents] << { name: agent, disallow: paths } - end - - result[:agents] << { name: '*', disallow: deny_all } - else - - if SiteSetting.blocked_crawler_user_agents.present? - SiteSetting.blocked_crawler_user_agents.split('|').each do |agent| - result[:agents] << { name: agent, disallow: deny_all } + SiteSetting + .allowed_crawler_user_agents + .split("|") + .each do |agent| + paths = agent == "Googlebot" ? deny_paths_googlebot : deny_paths + result[:agents] << { name: agent, disallow: paths } end + + result[:agents] << { name: "*", disallow: deny_all } + else + if SiteSetting.blocked_crawler_user_agents.present? + SiteSetting + .blocked_crawler_user_agents + .split("|") + .each { |agent| result[:agents] << { name: agent, disallow: deny_all } } end - result[:agents] << { name: '*', disallow: deny_paths } + result[:agents] << { name: "*", disallow: deny_paths } - result[:agents] << { name: 'Googlebot', disallow: deny_paths_googlebot } + result[:agents] << { name: "Googlebot", disallow: deny_paths_googlebot } end DiscourseEvent.trigger(:robots_info, result) diff --git a/app/controllers/safe_mode_controller.rb b/app/controllers/safe_mode_controller.rb index 3ffe891d173..53cc0cff32b 100644 --- a/app/controllers/safe_mode_controller.rb +++ b/app/controllers/safe_mode_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class SafeModeController < ApplicationController - layout 'no_ember' + layout "no_ember" before_action :ensure_safe_mode_enabled before_action :force_safe_mode_for_route @@ -39,5 +39,4 @@ class SafeModeController < ApplicationController request.env[ApplicationController::NO_THEMES] = true request.env[ApplicationController::NO_PLUGINS] = true end - end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 95c49bd75df..f626e620f28 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true class SearchController < ApplicationController - before_action :cancel_overloaded_search, only: [:query] skip_before_action :check_xhr, only: :show after_action :add_noindex_header def self.valid_context_types - %w{user topic category private_messages tag} + %w[user topic category private_messages tag] end def show @@ -16,12 +15,9 @@ class SearchController < ApplicationController # a q param has been given but it's not in the correct format # eg: ?q[foo]=bar - if params[:q].present? && !@search_term.present? - raise Discourse::InvalidParameters.new(:q) - end + raise Discourse::InvalidParameters.new(:q) if params[:q].present? && !@search_term.present? - if @search_term.present? && - @search_term.length < SiteSetting.min_search_term_length + if @search_term.present? && @search_term.length < SiteSetting.min_search_term_length raise Discourse::InvalidParameters.new(:q) end @@ -31,21 +27,17 @@ class SearchController < ApplicationController page = permitted_params[:page] # check for a malformed page parameter - if page && (!page.is_a?(String) || page.to_i.to_s != page) - raise Discourse::InvalidParameters - end + raise Discourse::InvalidParameters if page && (!page.is_a?(String) || page.to_i.to_s != page) rate_limit_errors = rate_limit_search discourse_expires_in 1.minute search_args = { - type_filter: 'topic', + type_filter: "topic", guardian: guardian, blurb_length: 300, - page: if page.to_i <= 10 - [page.to_i, 1].max - end + page: ([page.to_i, 1].max if page.to_i <= 10), } context, type = lookup_search_context @@ -59,19 +51,21 @@ class SearchController < ApplicationController search_args[:user_id] = current_user.id if current_user.present? if rate_limit_errors - result = Search::GroupedSearchResults.new( - type_filter: search_args[:type_filter], - term: @search_term, - search_context: context - ) + result = + Search::GroupedSearchResults.new( + type_filter: search_args[:type_filter], + term: @search_term, + search_context: context, + ) result.error = I18n.t("rate_limiter.slow_down") elsif site_overloaded? - result = Search::GroupedSearchResults.new( - type_filter: search_args[:type_filter], - term: @search_term, - search_context: context - ) + result = + Search::GroupedSearchResults.new( + type_filter: search_args[:type_filter], + term: @search_term, + search_context: context, + ) result.error = I18n.t("search.extreme_load_error") else @@ -83,12 +77,8 @@ class SearchController < ApplicationController serializer = serialize_data(result, GroupedSearchResultSerializer, result: result) respond_to do |format| - format.html do - store_preloaded("search", MultiJson.dump(serializer)) - end - format.json do - render_json_dump(serializer) - end + format.html { store_preloaded("search", MultiJson.dump(serializer)) } + format.json { render_json_dump(serializer) } end end @@ -105,8 +95,8 @@ class SearchController < ApplicationController search_args = { guardian: guardian } - search_args[:type_filter] = params[:type_filter] if params[:type_filter].present? - search_args[:search_for_id] = true if params[:search_for_id].present? + search_args[:type_filter] = params[:type_filter] if params[:type_filter].present? + search_args[:search_for_id] = true if params[:search_for_id].present? context, type = lookup_search_context @@ -118,22 +108,26 @@ class SearchController < ApplicationController search_args[:search_type] = :header search_args[:ip_address] = request.remote_ip search_args[:user_id] = current_user.id if current_user.present? - search_args[:restrict_to_archetype] = params[:restrict_to_archetype] if params[:restrict_to_archetype].present? + search_args[:restrict_to_archetype] = params[:restrict_to_archetype] if params[ + :restrict_to_archetype + ].present? if rate_limit_errors - result = Search::GroupedSearchResults.new( - type_filter: search_args[:type_filter], - term: params[:term], - search_context: context - ) + result = + Search::GroupedSearchResults.new( + type_filter: search_args[:type_filter], + term: params[:term], + search_context: context, + ) result.error = I18n.t("rate_limiter.slow_down") elsif site_overloaded? - result = GroupedSearchResults.new( - type_filter: search_args[:type_filter], - term: params[:term], - search_context: context - ) + result = + GroupedSearchResults.new( + type_filter: search_args[:type_filter], + term: params[:term], + search_context: context, + ) else search = Search.new(params[:term], search_args) result = search.execute(readonly_mode: @readonly_mode) @@ -163,7 +157,7 @@ class SearchController < ApplicationController SearchLog.where(attributes).update_all( search_result_type: SearchLog.search_result_types[search_result_type], - search_result_id: search_result_id + search_result_id: search_result_id, ) end @@ -173,7 +167,7 @@ class SearchController < ApplicationController protected def site_overloaded? - queue_time = request.env['REQUEST_QUEUE_SECONDS'] + queue_time = request.env["REQUEST_QUEUE_SECONDS"] if queue_time threshold = GlobalSetting.disable_search_queue_threshold.to_f threshold > 0 && queue_time > threshold @@ -185,10 +179,25 @@ class SearchController < ApplicationController def rate_limit_search begin if current_user.present? - RateLimiter.new(current_user, "search-min", SiteSetting.rate_limit_search_user, 1.minute).performed! + RateLimiter.new( + current_user, + "search-min", + SiteSetting.rate_limit_search_user, + 1.minute, + ).performed! else - RateLimiter.new(nil, "search-min-#{request.remote_ip}", SiteSetting.rate_limit_search_anon_user, 1.minute).performed! - RateLimiter.new(nil, "search-min-anon-global", SiteSetting.rate_limit_search_anon_global, 1.minute).performed! + RateLimiter.new( + nil, + "search-min-#{request.remote_ip}", + SiteSetting.rate_limit_search_anon_user, + 1.minute, + ).performed! + RateLimiter.new( + nil, + "search-min-anon-global", + SiteSetting.rate_limit_search_anon_global, + 1.minute, + ).performed! end rescue RateLimiter::LimitExceeded => e return e @@ -197,13 +206,10 @@ class SearchController < ApplicationController end def cancel_overloaded_search - if site_overloaded? - render_json_error I18n.t("search.extreme_load_error"), status: 409 - end + render_json_error I18n.t("search.extreme_load_error"), status: 409 if site_overloaded? end def lookup_search_context - return if params[:skip_context] == "true" search_context = params[:search_context] @@ -214,30 +220,29 @@ class SearchController < ApplicationController end if search_context.present? - raise Discourse::InvalidParameters.new(:search_context) unless SearchController.valid_context_types.include?(search_context[:type]) + unless SearchController.valid_context_types.include?(search_context[:type]) + raise Discourse::InvalidParameters.new(:search_context) + end raise Discourse::InvalidParameters.new(:search_context) if search_context[:id].blank? # A user is found by username context_obj = nil - if ['user', 'private_messages'].include? search_context[:type] + if %w[user private_messages].include? search_context[:type] context_obj = User.find_by(username_lower: search_context[:id].downcase) - elsif 'category' == search_context[:type] + elsif "category" == search_context[:type] context_obj = Category.find_by(id: search_context[:id].to_i) - elsif 'topic' == search_context[:type] + elsif "topic" == search_context[:type] context_obj = Topic.find_by(id: search_context[:id].to_i) - elsif 'tag' == search_context[:type] + elsif "tag" == search_context[:type] context_obj = Tag.where_name(search_context[:name]).first end type_filter = nil - if search_context[:type] == 'private_messages' - type_filter = 'private_messages' - end + type_filter = "private_messages" if search_context[:type] == "private_messages" guardian.ensure_can_see!(context_obj) [context_obj, type_filter] end end - end diff --git a/app/controllers/session_controller.rb b/app/controllers/session_controller.rb index ecc653c88d6..9c8216522ce 100644 --- a/app/controllers/session_controller.rb +++ b/app/controllers/session_controller.rb @@ -1,14 +1,16 @@ # frozen_string_literal: true class SessionController < ApplicationController - before_action :check_local_login_allowed, only: %i(create forgot_password) - before_action :rate_limit_login, only: %i(create email_login) + before_action :check_local_login_allowed, only: %i[create forgot_password] + before_action :rate_limit_login, only: %i[create email_login] skip_before_action :redirect_to_login_if_required - skip_before_action :preload_json, :check_xhr, only: %i(sso sso_login sso_provider destroy one_time_password) + skip_before_action :preload_json, + :check_xhr, + only: %i[sso sso_login sso_provider destroy one_time_password] - skip_before_action :check_xhr, only: %i(second_factor_auth_show) + skip_before_action :check_xhr, only: %i[second_factor_auth_show] - requires_login only: [:second_factor_auth_show, :second_factor_auth_perform] + requires_login only: %i[second_factor_auth_show second_factor_auth_perform] allow_in_staff_writes_only_mode :create allow_in_staff_writes_only_mode :email_login @@ -23,10 +25,10 @@ class SessionController < ApplicationController raise Discourse::NotFound unless SiteSetting.enable_discourse_connect? destination_url = cookies[:destination_url] || session[:destination_url] - return_path = params[:return_path] || path('/') + return_path = params[:return_path] || path("/") - if destination_url && return_path == path('/') - uri = URI::parse(destination_url) + if destination_url && return_path == path("/") + uri = URI.parse(destination_url) return_path = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}" end @@ -41,11 +43,12 @@ class SessionController < ApplicationController def sso_provider(payload = nil, confirmed_2fa_during_login = false) raise Discourse::NotFound unless SiteSetting.enable_discourse_connect_provider - result = run_second_factor!( - SecondFactor::Actions::DiscourseConnectProvider, - payload: payload, - confirmed_2fa_during_login: confirmed_2fa_during_login - ) + result = + run_second_factor!( + SecondFactor::Actions::DiscourseConnectProvider, + payload: payload, + confirmed_2fa_during_login: confirmed_2fa_during_login, + ) if result.second_factor_auth_skipped? data = result.data @@ -57,7 +60,7 @@ class SessionController < ApplicationController if data[:no_current_user] cookies[:sso_payload] = payload || request.query_string - redirect_to path('/login') + redirect_to path("/login") return end @@ -93,12 +96,11 @@ class SessionController < ApplicationController skip_before_action :check_xhr, only: [:become] def become - raise Discourse::InvalidAccess if Rails.env.production? raise Discourse::ReadOnly if @readonly_mode - if ENV['DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE'] != "1" - render(content_type: 'text/plain', inline: <<~TEXT) + if ENV["DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE"] != "1" + render(content_type: "text/plain", inline: <<~TEXT) To enable impersonating any user without typing passwords set the following ENV var export DISCOURSE_DEV_ALLOW_ANON_TO_IMPERSONATE=1 @@ -132,7 +134,9 @@ class SessionController < ApplicationController begin sso = DiscourseConnect.parse(request.query_string, secure_session: secure_session) rescue DiscourseConnect::ParseError => e - connect_verbose_warn { "Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso&.diagnostics}" } + connect_verbose_warn do + "Verbose SSO log: Signature parse error\n\n#{e.message}\n\n#{sso&.diagnostics}" + end # Do NOT pass the error text to the client, it would give them the correct signature return render_sso_error(text: I18n.t("discourse_connect.login_error"), status: 422) @@ -144,7 +148,9 @@ class SessionController < ApplicationController end if ScreenedIpAddress.should_block?(request.remote_ip) - connect_verbose_warn { "Verbose SSO log: IP address is blocked #{request.remote_ip}\n\n#{sso.diagnostics}" } + connect_verbose_warn do + "Verbose SSO log: IP address is blocked #{request.remote_ip}\n\n#{sso.diagnostics}" + end return render_sso_error(text: I18n.t("discourse_connect.unknown_error"), status: 500) end @@ -163,9 +169,7 @@ class SessionController < ApplicationController end if SiteSetting.must_approve_users? && !user.approved? - if invite.present? && user.invited_user.blank? - redeem_invitation(invite, sso, user) - end + redeem_invitation(invite, sso, user) if invite.present? && user.invited_user.blank? if SiteSetting.discourse_connect_not_approved_url.present? redirect_to SiteSetting.discourse_connect_not_approved_url, allow_other_host: true @@ -174,9 +178,9 @@ class SessionController < ApplicationController end return - # we only want to redeem the invite if - # the user has not already redeemed an invite - # (covers the same SSO user visiting an invite link) + # we only want to redeem the invite if + # the user has not already redeemed an invite + # (covers the same SSO user visiting an invite link) elsif invite.present? && user.invited_user.blank? redeem_invitation(invite, sso, user) @@ -199,7 +203,7 @@ class SessionController < ApplicationController end # If it's not a relative URL check the host - if return_path !~ /^\/[^\/]/ + if return_path !~ %r{^/[^/]} begin uri = URI(return_path) if (uri.hostname == Discourse.current_hostname) @@ -207,7 +211,7 @@ class SessionController < ApplicationController elsif !SiteSetting.discourse_connect_allows_all_return_paths return_path = path("/") end - rescue + rescue StandardError return_path = path("/") end end @@ -215,16 +219,13 @@ class SessionController < ApplicationController # this can be done more surgically with a regex # but it the edge case of never supporting redirects back to # any url with `/session/sso` in it anywhere is reasonable - if return_path.include?(path("/session/sso")) - return_path = path("/") - end + return_path = path("/") if return_path.include?(path("/session/sso")) redirect_to return_path, allow_other_host: true else render_sso_error(text: I18n.t("discourse_connect.not_found"), status: 500) end rescue ActiveRecord::RecordInvalid => e - connect_verbose_warn { <<~TEXT } Verbose SSO log: Record was invalid: #{e.record.class.name} #{e.record.id} #{e.record.errors.to_h} @@ -243,7 +244,8 @@ class SessionController < ApplicationController if e.record.email.blank? text = I18n.t("discourse_connect.no_email") else - text = I18n.t("discourse_connect.email_error", email: ERB::Util.html_escape(e.record.email)) + text = + I18n.t("discourse_connect.email_error", email: ERB::Util.html_escape(e.record.email)) end end @@ -270,7 +272,9 @@ class SessionController < ApplicationController end def login_sso_user(sso, user) - connect_verbose_warn { "Verbose SSO log: User was logged on #{user.username}\n\n#{sso.diagnostics}" } + connect_verbose_warn do + "Verbose SSO log: User was logged on #{user.username}\n\n#{sso.diagnostics}" + end log_on_user(user) if user.id != current_user&.id end @@ -287,7 +291,6 @@ class SessionController < ApplicationController rate_limit_second_factor!(user) if user.present? - # If their password is correct unless user.confirm_password?(params[:password]) invalid_credentials @@ -313,9 +316,7 @@ class SessionController < ApplicationController end second_factor_auth_result = authenticate_second_factor(user) - if !second_factor_auth_result.ok - return render(json: @second_factor_failure_payload) - end + return render(json: @second_factor_failure_payload) if !second_factor_auth_result.ok if user.active && user.email_confirmed? login(user, second_factor_auth_result) @@ -332,33 +333,31 @@ class SessionController < ApplicationController check_local_login_allowed(user: user, check_login_via_email: true) if matched_token - response = { - can_login: true, - token: token, - token_email: matched_token.email - } + response = { can_login: true, token: token, token_email: matched_token.email } matched_user = matched_token.user if matched_user&.totp_enabled? response.merge!( second_factor_required: true, - backup_codes_enabled: matched_user&.backup_codes_enabled? + backup_codes_enabled: matched_user&.backup_codes_enabled?, ) end if matched_user&.security_keys_enabled? Webauthn.stage_challenge(matched_user, secure_session) response.merge!( - Webauthn.allowed_credentials(matched_user, secure_session).merge(security_key_required: true) + Webauthn.allowed_credentials(matched_user, secure_session).merge( + security_key_required: true, + ), ) end render json: response else render json: { - can_login: false, - error: I18n.t('email_login.invalid_token', base_url: Discourse.base_url) - } + can_login: false, + error: I18n.t("email_login.invalid_token", base_url: Discourse.base_url), + } end end @@ -388,7 +387,7 @@ class SessionController < ApplicationController end end - render json: { error: I18n.t('email_login.invalid_token', base_url: Discourse.base_url) } + render json: { error: I18n.t("email_login.invalid_token", base_url: Discourse.base_url) } end def one_time_password @@ -406,10 +405,10 @@ class SessionController < ApplicationController # Display the form end else - @error = I18n.t('user_api_key.invalid_token') + @error = I18n.t("user_api_key.invalid_token") end - render layout: 'no_ember', locals: { hide_auth_buttons: true } + render layout: "no_ember", locals: { hide_auth_buttons: true } end def second_factor_auth_show @@ -431,7 +430,7 @@ class SessionController < ApplicationController json.merge!( totp_enabled: user.totp_enabled?, backup_enabled: user.backup_codes_enabled?, - allowed_methods: challenge[:allowed_methods] + allowed_methods: challenge[:allowed_methods], ) if user.security_keys_enabled? Webauthn.stage_challenge(user, secure_session) @@ -440,9 +439,7 @@ class SessionController < ApplicationController else json[:security_keys_enabled] = false end - if challenge[:description] - json[:description] = challenge[:description] - end + json[:description] = challenge[:description] if challenge[:description] else json[:error] = I18n.t(error_key) end @@ -453,9 +450,7 @@ class SessionController < ApplicationController raise ApplicationController::RenderEmpty.new end - format.json do - render json: json, status: status_code - end + format.json { render json: json, status: status_code } end end @@ -472,11 +467,12 @@ class SessionController < ApplicationController end if error_key - json = failed_json.merge( - ok: false, - error: I18n.t(error_key), - reason: "challenge_not_found_or_expired" - ) + json = + failed_json.merge( + ok: false, + error: I18n.t(error_key), + reason: "challenge_not_found_or_expired", + ) render json: failed_json.merge(json), status: status_code return end @@ -505,21 +501,23 @@ class SessionController < ApplicationController challenge[:generated_at] += 1.minute.to_i secure_session["current_second_factor_auth_challenge"] = challenge.to_json else - error_json = second_factor_auth_result - .to_h - .deep_symbolize_keys - .slice(:ok, :error, :reason) - .merge(failed_json) + error_json = + second_factor_auth_result + .to_h + .deep_symbolize_keys + .slice(:ok, :error, :reason) + .merge(failed_json) render json: error_json, status: 400 return end end render json: { - ok: true, - callback_method: challenge[:callback_method], - callback_path: challenge[:callback_path], - redirect_url: challenge[:redirect_url] - }, status: 200 + ok: true, + callback_method: challenge[:callback_method], + callback_path: challenge[:callback_path], + redirect_url: challenge[:redirect_url], + }, + status: 200 end def forgot_password @@ -532,19 +530,33 @@ class SessionController < ApplicationController RateLimiter.new(nil, "forgot-password-hr-#{request.remote_ip}", 6, 1.hour).performed! RateLimiter.new(nil, "forgot-password-min-#{request.remote_ip}", 3, 1.minute).performed! - user = if SiteSetting.hide_email_address_taken && !current_user&.staff? - raise Discourse::InvalidParameters.new(:login) if !EmailAddressValidator.valid_value?(normalized_login_param) - User.real.where(staged: false).find_by_email(Email.downcase(normalized_login_param)) - else - User.real.where(staged: false).find_by_username_or_email(normalized_login_param) - end + user = + if SiteSetting.hide_email_address_taken && !current_user&.staff? + if !EmailAddressValidator.valid_value?(normalized_login_param) + raise Discourse::InvalidParameters.new(:login) + end + User.real.where(staged: false).find_by_email(Email.downcase(normalized_login_param)) + else + User.real.where(staged: false).find_by_username_or_email(normalized_login_param) + end if user RateLimiter.new(nil, "forgot-password-login-day-#{user.username}", 6, 1.day).performed! - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset]) - Jobs.enqueue(:critical_user_email, type: "forgot_password", user_id: user.id, email_token: email_token.token) + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset]) + Jobs.enqueue( + :critical_user_email, + type: "forgot_password", + user_id: user.id, + email_token: email_token.token, + ) else - RateLimiter.new(nil, "forgot-password-login-hour-#{normalized_login_param}", 5, 1.hour).performed! + RateLimiter.new( + nil, + "forgot-password-login-hour-#{normalized_login_param}", + 5, + 1.hour, + ).performed! end json = success_json @@ -566,7 +578,8 @@ class SessionController < ApplicationController redirect_url = params[:return_url].presence || SiteSetting.logout_redirect.presence sso = SiteSetting.enable_discourse_connect - only_one_authenticator = !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1 + only_one_authenticator = + !SiteSetting.enable_local_logins && Discourse.enabled_authenticators.length == 1 if SiteSetting.login_required && (sso || only_one_authenticator) # In this situation visiting most URLs will start the auth process again # Go to the `/login` page to avoid an immediate redirect @@ -575,16 +588,19 @@ class SessionController < ApplicationController redirect_url ||= path("/") - event_data = { redirect_url: redirect_url, user: current_user, client_ip: request&.ip, user_agent: request&.user_agent } + event_data = { + redirect_url: redirect_url, + user: current_user, + client_ip: request&.ip, + user_agent: request&.user_agent, + } DiscourseEvent.trigger(:before_session_destroy, event_data) redirect_url = event_data[:redirect_url] reset_session log_off_user if request.xhr? - render json: { - redirect_url: redirect_url - } + render json: { redirect_url: redirect_url } else redirect_to redirect_url, allow_other_host: true end @@ -595,17 +611,17 @@ class SessionController < ApplicationController secure_session.set(CHALLENGE_KEY, challenge_value, expires: 1.hour) render json: { - value: honeypot_value, - challenge: challenge_value, - expires_in: SecureSession.expiry - } + value: honeypot_value, + challenge: challenge_value, + expires_in: SecureSession.expiry, + } end def scopes if is_api? key = request.env[Auth::DefaultCurrentUserProvider::HEADER_API_KEY] api_key = ApiKey.active.with_key(key).first - render_serialized(api_key.api_key_scopes, ApiKeyScopeSerializer, root: 'scopes') + render_serialized(api_key.api_key_scopes, ApiKeyScopeSerializer, root: "scopes") else render body: nil, status: 404 end @@ -628,8 +644,7 @@ class SessionController < ApplicationController return if user&.admin? if (check_login_via_email && !SiteSetting.enable_local_logins_via_email) || - SiteSetting.enable_discourse_connect || - !SiteSetting.enable_local_logins + SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins raise Discourse::InvalidAccess, "SSO takes over local login or the local login is disallowed." end end @@ -637,9 +652,7 @@ class SessionController < ApplicationController private def connect_verbose_warn(&blk) - if SiteSetting.verbose_discourse_connect_logging - Rails.logger.warn(blk.call) - end + Rails.logger.warn(blk.call) if SiteSetting.verbose_discourse_connect_logging end def authenticate_second_factor(user) @@ -660,9 +673,7 @@ class SessionController < ApplicationController def login_error_check(user) return failed_to_login(user) if user.suspended? - if ScreenedIpAddress.should_block?(request.remote_ip) - return not_allowed_from_ip_address(user) - end + return not_allowed_from_ip_address(user) if ScreenedIpAddress.should_block?(request.remote_ip) if ScreenedIpAddress.block_admin_login?(user, request.remote_ip) admin_not_allowed_from_ip_address(user) @@ -684,11 +695,11 @@ class SessionController < ApplicationController def not_activated(user) session[ACTIVATE_USER_KEY] = user.id render json: { - error: I18n.t("login.not_activated"), - reason: 'not_activated', - sent_to_email: user.find_email || user.email, - current_email: user.email - } + error: I18n.t("login.not_activated"), + reason: "not_activated", + sent_to_email: user.find_email || user.email, + current_email: user.email, + } end def not_allowed_from_ip_address(user) @@ -700,10 +711,7 @@ class SessionController < ApplicationController end def failed_to_login(user) - { - error: user.suspended_message, - reason: 'suspended' - } + { error: user.suspended_message, reason: "suspended" } end def login(user, second_factor_auth_result) @@ -712,11 +720,11 @@ class SessionController < ApplicationController log_on_user(user) if payload = cookies.delete(:sso_payload) - confirmed_2fa_during_login = ( - second_factor_auth_result&.ok && - second_factor_auth_result.used_2fa_method.present? && - second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes] - ) + confirmed_2fa_during_login = + ( + second_factor_auth_result&.ok && second_factor_auth_result.used_2fa_method.present? && + second_factor_auth_result.used_2fa_method != UserSecondFactor.methods[:backup_codes] + ) sso_provider(payload, confirmed_2fa_during_login) else render_serialized(user, UserSerializer) @@ -728,20 +736,20 @@ class SessionController < ApplicationController nil, "login-hr-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_hour, - 1.hour + 1.hour, ).performed! RateLimiter.new( nil, "login-min-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_minute, - 1.minute + 1.minute, ).performed! end def render_sso_error(status:, text:) @sso_error = text - render status: status, layout: 'no_ember' + render status: status, layout: "no_ember" end # extension to allow plugins to customize the SSO URL @@ -769,9 +777,15 @@ class SessionController < ApplicationController raise Invite::ValidationFailed.new(I18n.t("invite.not_matching_email")) end elsif invite.expired? - raise Invite::ValidationFailed.new(I18n.t('invite.expired', base_url: Discourse.base_url)) + raise Invite::ValidationFailed.new(I18n.t("invite.expired", base_url: Discourse.base_url)) elsif invite.redeemed? - raise Invite::ValidationFailed.new(I18n.t('invite.not_found_template', site_name: SiteSetting.title, base_url: Discourse.base_url)) + raise Invite::ValidationFailed.new( + I18n.t( + "invite.not_found_template", + site_name: SiteSetting.title, + base_url: Discourse.base_url, + ), + ) end invite @@ -785,11 +799,11 @@ class SessionController < ApplicationController ip_address: request.remote_ip, session: session, email: sso.email, - redeeming_user: redeeming_user + redeeming_user: redeeming_user, ).redeem secure_session["invite-key"] = nil - # note - more specific errors are handled in the sso_login method + # note - more specific errors are handled in the sso_login method rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e Rails.logger.warn("SSO invite redemption failed: #{e}") raise Invite::RedemptionFailed diff --git a/app/controllers/similar_topics_controller.rb b/app/controllers/similar_topics_controller.rb index e2f33146576..05a87e14196 100644 --- a/app/controllers/similar_topics_controller.rb +++ b/app/controllers/similar_topics_controller.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class SimilarTopicsController < ApplicationController - class SimilarTopic def initialize(topic) @topic = topic @@ -26,5 +25,4 @@ class SimilarTopicsController < ApplicationController topics.map! { |t| SimilarTopic.new(t) } render_serialized(topics, SimilarTopicSerializer, root: :similar_topics, rest_serializer: true) end - end diff --git a/app/controllers/site_controller.rb b/app/controllers/site_controller.rb index 734036cd69e..a8cc7ea2130 100644 --- a/app/controllers/site_controller.rb +++ b/app/controllers/site_controller.rb @@ -3,7 +3,7 @@ class SiteController < ApplicationController layout false skip_before_action :preload_json, :check_xhr - skip_before_action :redirect_to_login_if_required, only: ['basic_info', 'statistics'] + skip_before_action :redirect_to_login_if_required, only: %w[basic_info statistics] def site render json: Site.json_for(guardian) @@ -33,9 +33,9 @@ class SiteController < ApplicationController favicon_url: UrlHelper.absolute(SiteSetting.site_favicon_url), title: SiteSetting.title, description: SiteSetting.site_description, - header_primary_color: ColorScheme.hex_for_name('header_primary') || '333333', - header_background_color: ColorScheme.hex_for_name('header_background') || 'ffffff', - login_required: SiteSetting.login_required + header_primary_color: ColorScheme.hex_for_name("header_primary") || "333333", + header_background_color: ColorScheme.hex_for_name("header_background") || "ffffff", + login_required: SiteSetting.login_required, } if mobile_logo_url = SiteSetting.site_mobile_logo_url.presence @@ -49,7 +49,7 @@ class SiteController < ApplicationController end def statistics - return redirect_to path('/') unless SiteSetting.share_anonymized_statistics? + return redirect_to path("/") unless SiteSetting.share_anonymized_statistics? render json: About.fetch_cached_stats end end diff --git a/app/controllers/sitemap_controller.rb b/app/controllers/sitemap_controller.rb index 3ae7efcde04..30fed7190f2 100644 --- a/app/controllers/sitemap_controller.rb +++ b/app/controllers/sitemap_controller.rb @@ -6,9 +6,7 @@ class SitemapController < ApplicationController before_action :check_sitemap_enabled def index - @sitemaps = Sitemap - .where(enabled: true) - .where.not(name: Sitemap::NEWS_SITEMAP_NAME) + @sitemaps = Sitemap.where(enabled: true).where.not(name: Sitemap::NEWS_SITEMAP_NAME) render :index end @@ -18,37 +16,46 @@ class SitemapController < ApplicationController sitemap = Sitemap.find_by(enabled: true, name: index.to_s) raise Discourse::NotFound if sitemap.nil? - @output = Rails.cache.fetch("sitemap/#{sitemap.name}/#{sitemap.max_page_size}", expires_in: 24.hours) do - @topics = sitemap.topics - render :page, content_type: 'text/xml; charset=UTF-8' - end + @output = + Rails + .cache + .fetch("sitemap/#{sitemap.name}/#{sitemap.max_page_size}", expires_in: 24.hours) do + @topics = sitemap.topics + render :page, content_type: "text/xml; charset=UTF-8" + end - render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? + render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed? end def recent sitemap = Sitemap.touch(Sitemap::RECENT_SITEMAP_NAME) - @output = Rails.cache.fetch("sitemap/recent/#{sitemap.last_posted_at.to_i}", expires_in: 1.hour) do - @topics = sitemap.topics - render :page, content_type: 'text/xml; charset=UTF-8' - end + @output = + Rails + .cache + .fetch("sitemap/recent/#{sitemap.last_posted_at.to_i}", expires_in: 1.hour) do + @topics = sitemap.topics + render :page, content_type: "text/xml; charset=UTF-8" + end - render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? + render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed? end def news sitemap = Sitemap.touch(Sitemap::NEWS_SITEMAP_NAME) - @output = Rails.cache.fetch("sitemap/news", expires_in: 5.minutes) do - dlocale = SiteSetting.default_locale.downcase - @locale = dlocale.gsub(/_.*/, '') - @locale = dlocale.sub('_', '-') if @locale === "zh" - @topics = sitemap.topics - render :news, content_type: 'text/xml; charset=UTF-8' - end + @output = + Rails + .cache + .fetch("sitemap/news", expires_in: 5.minutes) do + dlocale = SiteSetting.default_locale.downcase + @locale = dlocale.gsub(/_.*/, "") + @locale = dlocale.sub("_", "-") if @locale === "zh" + @topics = sitemap.topics + render :news, content_type: "text/xml; charset=UTF-8" + end - render plain: @output, content_type: 'text/xml; charset=UTF-8' unless performed? + render plain: @output, content_type: "text/xml; charset=UTF-8" unless performed? end private @@ -58,7 +65,7 @@ class SitemapController < ApplicationController end def build_sitemap_topic_url(slug, id, posts_count = nil) - base_url = [Discourse.base_url, 't', slug, id].join('/') + base_url = [Discourse.base_url, "t", slug, id].join("/") return base_url if posts_count.nil? page, mod = posts_count.divmod(TopicView.chunk_size) @@ -67,5 +74,4 @@ class SitemapController < ApplicationController page > 1 ? "#{base_url}?page=#{page}" : base_url end helper_method :build_sitemap_topic_url - end diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index b0eaaa2f566..fde841a46e8 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -2,26 +2,41 @@ class StaticController < ApplicationController skip_before_action :check_xhr, :redirect_to_login_if_required - skip_before_action :verify_authenticity_token, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] - skip_before_action :preload_json, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] - skip_before_action :handle_theme, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] + skip_before_action :verify_authenticity_token, + only: %i[brotli_asset cdn_asset enter favicon service_worker_asset] + skip_before_action :preload_json, + only: %i[brotli_asset cdn_asset enter favicon service_worker_asset] + skip_before_action :handle_theme, + only: %i[brotli_asset cdn_asset enter favicon service_worker_asset] - before_action :apply_cdn_headers, only: [:brotli_asset, :cdn_asset, :enter, :favicon, :service_worker_asset] + before_action :apply_cdn_headers, + only: %i[brotli_asset cdn_asset enter favicon service_worker_asset] - PAGES_WITH_EMAIL_PARAM = ['login', 'password_reset', 'signup'] - MODAL_PAGES = ['password_reset', 'signup'] + PAGES_WITH_EMAIL_PARAM = %w[login password_reset signup] + MODAL_PAGES = %w[password_reset signup] DEFAULT_PAGES = { - "faq" => { redirect: "faq_url", topic_id: "guidelines_topic_id" }, - "tos" => { redirect: "tos_url", topic_id: "tos_topic_id" }, - "privacy" => { redirect: "privacy_policy_url", topic_id: "privacy_topic_id" }, + "faq" => { + redirect: "faq_url", + topic_id: "guidelines_topic_id", + }, + "tos" => { + redirect: "tos_url", + topic_id: "tos_topic_id", + }, + "privacy" => { + redirect: "privacy_policy_url", + topic_id: "privacy_topic_id", + }, } CUSTOM_PAGES = {} # Add via `#add_topic_static_page` in plugin API def show - return redirect_to(path '/') if current_user && (params[:id] == 'login' || params[:id] == 'signup') + if current_user && (params[:id] == "login" || params[:id] == "signup") + return redirect_to(path "/") + end - if SiteSetting.login_required? && current_user.nil? && ['faq', 'guidelines'].include?(params[:id]) - return redirect_to path('/login') + if SiteSetting.login_required? && current_user.nil? && %w[faq guidelines].include?(params[:id]) + return redirect_to path("/login") end map = DEFAULT_PAGES.deep_merge(CUSTOM_PAGES) @@ -34,10 +49,10 @@ class StaticController < ApplicationController end # The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting. - @page = 'faq' if @page == 'guidelines' + @page = "faq" if @page == "guidelines" # Don't allow paths like ".." or "/" or anything hacky like that - @page = @page.gsub(/[^a-z0-9\_\-]/, '') + @page = @page.gsub(/[^a-z0-9\_\-]/, "") if map.has_key?(@page) topic_id = map[@page][:topic_id] @@ -46,11 +61,12 @@ class StaticController < ApplicationController @topic = Topic.find_by_id(SiteSetting.get(topic_id)) raise Discourse::NotFound unless @topic - title_prefix = if I18n.exists?("js.#{@page}") - I18n.t("js.#{@page}") - else - @topic.title - end + title_prefix = + if I18n.exists?("js.#{@page}") + I18n.t("js.#{@page}") + else + @topic.title + end @title = "#{title_prefix} - #{SiteSetting.title}" @body = @topic.posts.first.cooked @faq_overridden = !SiteSetting.faq_url.blank? @@ -104,10 +120,7 @@ class StaticController < ApplicationController forum_uri = URI(Discourse.base_url) uri = URI(redirect_location) - if uri.path.present? && - (uri.host.blank? || uri.host == forum_uri.host) && - uri.path !~ /\./ - + if uri.path.present? && (uri.host.blank? || uri.host == forum_uri.host) && uri.path !~ /\./ destination = "#{uri.path}#{uri.query ? "?#{uri.query}" : ""}" end rescue URI::Error @@ -135,31 +148,33 @@ class StaticController < ApplicationController is_asset_path hijack do - data = DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do - favicon = SiteIconManager.favicon - next "" unless favicon + data = + DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do + favicon = SiteIconManager.favicon + next "" unless favicon - if Discourse.store.external? - begin - file = FileHelper.download( - Discourse.store.cdn_url(favicon.url), - max_file_size: favicon.filesize, - tmp_file_name: FAVICON, - follow_redirect: true - ) + if Discourse.store.external? + begin + file = + FileHelper.download( + Discourse.store.cdn_url(favicon.url), + max_file_size: favicon.filesize, + tmp_file_name: FAVICON, + follow_redirect: true, + ) - file&.read || "" - rescue => e - AdminDashboardData.add_problem_message('dashboard.bad_favicon_url', 1800) - Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}") - "" - ensure - file&.unlink + file&.read || "" + rescue => e + AdminDashboardData.add_problem_message("dashboard.bad_favicon_url", 1800) + Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}") + "" + ensure + file&.unlink + end + else + File.read(Rails.root.join("public", favicon.url[1..-1])) end - else - File.read(Rails.root.join("public", favicon.url[1..-1])) end - end if data.bytesize == 0 @@default_favicon ||= File.read(Rails.root + "public/images/default-favicon.png") @@ -178,9 +193,7 @@ class StaticController < ApplicationController def brotli_asset is_asset_path - serve_asset(".br") do - response.headers["Content-Encoding"] = 'br' - end + serve_asset(".br") { response.headers["Content-Encoding"] = "br" } end def cdn_asset @@ -199,20 +212,22 @@ class StaticController < ApplicationController # However, ensure that these may be cached and served for longer on servers. immutable_for 1.year - if Rails.application.assets_manifest.assets['service-worker.js'] - path = File.expand_path(Rails.root + "public/assets/#{Rails.application.assets_manifest.assets['service-worker.js']}") + if Rails.application.assets_manifest.assets["service-worker.js"] + path = + File.expand_path( + Rails.root + + "public/assets/#{Rails.application.assets_manifest.assets["service-worker.js"]}", + ) response.headers["Last-Modified"] = File.ctime(path).httpdate end - content = Rails.application.assets_manifest.find_sources('service-worker.js').first + content = Rails.application.assets_manifest.find_sources("service-worker.js").first - base_url = File.dirname(helpers.script_asset_path('service-worker')) - content = content.sub( - /^\/\/# sourceMappingURL=(service-worker-.+\.map)$/ - ) { "//# sourceMappingURL=#{base_url}/#{Regexp.last_match(1)}" } - render( - plain: content, - content_type: 'application/javascript' - ) + base_url = File.dirname(helpers.script_asset_path("service-worker")) + content = + content.sub(%r{^//# sourceMappingURL=(service-worker-.+\.map)$}) do + "//# sourceMappingURL=#{base_url}/#{Regexp.last_match(1)}" + end + render(plain: content, content_type: "application/javascript") end end end @@ -220,7 +235,6 @@ class StaticController < ApplicationController protected def serve_asset(suffix = nil) - path = File.expand_path(Rails.root + "public/assets/#{params[:path]}#{suffix}") # SECURITY what if path has /../ @@ -254,12 +268,10 @@ class StaticController < ApplicationController immutable_for 1.year # disable NGINX mucking with transfer - request.env['sendfile.type'] = '' + request.env["sendfile.type"] = "" opts = { disposition: nil } opts[:type] = "application/javascript" if params[:path] =~ /\.js$/ send_file(path, opts) - end - end diff --git a/app/controllers/steps_controller.rb b/app/controllers/steps_controller.rb index 1f17ae6af99..153a2cccfd3 100644 --- a/app/controllers/steps_controller.rb +++ b/app/controllers/steps_controller.rb @@ -12,7 +12,7 @@ class StepsController < ApplicationController updater.update if updater.success? - result = { success: 'OK' } + result = { success: "OK" } result[:refresh_required] = true if updater.refresh_required? render json: result else @@ -23,5 +23,4 @@ class StepsController < ApplicationController render json: { errors: errors }, status: 422 end end - end diff --git a/app/controllers/stylesheets_controller.rb b/app/controllers/stylesheets_controller.rb index ee78f307650..97edb80b576 100644 --- a/app/controllers/stylesheets_controller.rb +++ b/app/controllers/stylesheets_controller.rb @@ -1,9 +1,13 @@ # frozen_string_literal: true class StylesheetsController < ApplicationController - skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_source_map, :color_scheme] + skip_before_action :preload_json, + :redirect_to_login_if_required, + :check_xhr, + :verify_authenticity_token, + only: %i[show show_source_map color_scheme] - before_action :apply_cdn_headers, only: [:show, :show_source_map, :color_scheme] + before_action :apply_cdn_headers, only: %i[show show_source_map color_scheme] def show_source_map show_resource(source_map: true) @@ -20,14 +24,13 @@ class StylesheetsController < ApplicationController params.permit("theme_id") manager = Stylesheet::Manager.new(theme_id: params[:theme_id]) - stylesheet = manager.color_scheme_stylesheet_details(params[:id], 'all') + stylesheet = manager.color_scheme_stylesheet_details(params[:id], "all") render json: stylesheet end protected def show_resource(source_map: false) - extension = source_map ? ".css.map" : ".css" no_cookies @@ -47,7 +50,7 @@ class StylesheetsController < ApplicationController if digest query = query.where(digest: digest) else - query = query.order('id desc') + query = query.order("id desc") end # Security note, safe due to route constraint @@ -58,9 +61,7 @@ class StylesheetsController < ApplicationController stylesheet_time = query.pluck_first(:created_at) - if !stylesheet_time - handle_missing_cache(location, target, digest) - end + handle_missing_cache(location, target, digest) if !stylesheet_time if cache_time && stylesheet_time && stylesheet_time <= cache_time return render body: nil, status: 304 @@ -76,10 +77,10 @@ class StylesheetsController < ApplicationController end if Rails.env == "development" - response.headers['Last-Modified'] = Time.zone.now.httpdate + response.headers["Last-Modified"] = Time.zone.now.httpdate immutable_for(1.second) else - response.headers['Last-Modified'] = stylesheet_time.httpdate if stylesheet_time + response.headers["Last-Modified"] = stylesheet_time.httpdate if stylesheet_time immutable_for(1.year) end send_file(location, disposition: :inline) @@ -104,5 +105,4 @@ class StylesheetsController < ApplicationController rescue Errno::ENOENT end end - end diff --git a/app/controllers/svg_sprite_controller.rb b/app/controllers/svg_sprite_controller.rb index 5366654d9df..f073314e200 100644 --- a/app/controllers/svg_sprite_controller.rb +++ b/app/controllers/svg_sprite_controller.rb @@ -1,14 +1,17 @@ # frozen_string_literal: true class SvgSpriteController < ApplicationController - skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :search, :svg_icon] + skip_before_action :preload_json, + :redirect_to_login_if_required, + :check_xhr, + :verify_authenticity_token, + only: %i[show search svg_icon] - before_action :apply_cdn_headers, only: [:show, :search, :svg_icon] + before_action :apply_cdn_headers, only: %i[show search svg_icon] - requires_login except: [:show, :svg_icon] + requires_login except: %i[show svg_icon] def show - no_cookies RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do @@ -24,20 +27,19 @@ class SvgSpriteController < ApplicationController response.headers["Content-Length"] = svg_sprite.bytesize.to_s immutable_for 1.year - render plain: svg_sprite, disposition: nil, content_type: 'application/javascript' + render plain: svg_sprite, disposition: nil, content_type: "application/javascript" end end def search RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do - keyword = params.require(:keyword) data = SvgSprite.search(keyword) if data.blank? render body: nil, status: 404 else - render plain: data.inspect, disposition: nil, content_type: 'text/plain' + render plain: data.inspect, disposition: nil, content_type: "text/plain" end end end @@ -65,14 +67,14 @@ class SvgSpriteController < ApplicationController else doc = Nokogiri.XML(icon) doc.at_xpath("symbol").name = "svg" - doc.at_xpath("svg")['xmlns'] = "http://www.w3.org/2000/svg" - doc.at_xpath("svg")['fill'] = adjust_hex(params[:color]) if params[:color] + doc.at_xpath("svg")["xmlns"] = "http://www.w3.org/2000/svg" + doc.at_xpath("svg")["fill"] = adjust_hex(params[:color]) if params[:color] response.headers["Last-Modified"] = 1.years.ago.httpdate response.headers["Content-Length"] = doc.to_s.bytesize.to_s immutable_for 1.day - render plain: doc, disposition: nil, content_type: 'image/svg+xml' + render plain: doc, disposition: nil, content_type: "image/svg+xml" end end end diff --git a/app/controllers/tag_groups_controller.rb b/app/controllers/tag_groups_controller.rb index 9921d7af6dd..fd6dae701d3 100644 --- a/app/controllers/tag_groups_controller.rb +++ b/app/controllers/tag_groups_controller.rb @@ -4,12 +4,17 @@ class TagGroupsController < ApplicationController requires_login except: [:search] before_action :ensure_staff, except: [:search] - skip_before_action :check_xhr, only: [:index, :show, :new] - before_action :fetch_tag_group, only: [:show, :update, :destroy] + skip_before_action :check_xhr, only: %i[index show new] + before_action :fetch_tag_group, only: %i[show update destroy] def index - tag_groups = TagGroup.order('name ASC').includes(:parent_tag).preload(:tags).all - serializer = ActiveModel::ArraySerializer.new(tag_groups, each_serializer: TagGroupSerializer, root: 'tag_groups') + tag_groups = TagGroup.order("name ASC").includes(:parent_tag).preload(:tags).all + serializer = + ActiveModel::ArraySerializer.new( + tag_groups, + each_serializer: TagGroupSerializer, + root: "tag_groups", + ) respond_to do |format| format.html do store_preloaded "tagGroups", MultiJson.dump(serializer) @@ -31,8 +36,13 @@ class TagGroupsController < ApplicationController end def new - tag_groups = TagGroup.order('name ASC').includes(:parent_tag).preload(:tags).all - serializer = ActiveModel::ArraySerializer.new(tag_groups, each_serializer: TagGroupSerializer, root: 'tag_groups') + tag_groups = TagGroup.order("name ASC").includes(:parent_tag).preload(:tags).all + serializer = + ActiveModel::ArraySerializer.new( + tag_groups, + each_serializer: TagGroupSerializer, + root: "tag_groups", + ) store_preloaded "tagGroup", MultiJson.dump(serializer) render "default/empty" end @@ -63,19 +73,18 @@ class TagGroupsController < ApplicationController def search matches = TagGroup.includes(:tags).visible(guardian).all - if params[:q].present? - matches = matches.where('lower(name) ILIKE ?', "%#{params[:q].strip}%") - end + matches = matches.where("lower(name) ILIKE ?", "%#{params[:q].strip}%") if params[:q].present? if params[:names].present? - matches = matches.where('lower(NAME) in (?)', params[:names].map(&:downcase)) + matches = matches.where("lower(NAME) in (?)", params[:names].map(&:downcase)) end - matches = matches.order('name').limit(params[:limit] || 5) + matches = matches.order("name").limit(params[:limit] || 5) render json: { - results: matches.map { |x| { name: x.name, tag_names: x.tags.base_tags.pluck(:name).sort } } - } + results: + matches.map { |x| { name: x.name, tag_names: x.tags.base_tags.pluck(:name).sort } }, + } end private @@ -88,14 +97,8 @@ class TagGroupsController < ApplicationController tag_group = params.delete(:tag_group) params.merge!(tag_group.permit!) if tag_group - result = params.permit( - :id, - :name, - :one_per_topic, - tag_names: [], - parent_tag_name: [], - permissions: {} - ) + result = + params.permit(:id, :name, :one_per_topic, tag_names: [], parent_tag_name: [], permissions: {}) result[:tag_names] ||= [] result[:parent_tag_name] ||= [] diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 662d8da5029..9b27b69e6a4 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -5,29 +5,32 @@ class TagsController < ::ApplicationController include TopicQueryParams before_action :ensure_tags_enabled - before_action :ensure_visible, only: [:show, :info] + before_action :ensure_visible, only: %i[show info] def self.show_methods Discourse.anonymous_filters.map { |f| :"show_#{f}" } end - requires_login except: [ - :index, - :show, - :tag_feed, - :search, - :info, - *show_methods - ] + requires_login except: [:index, :show, :tag_feed, :search, :info, *show_methods] skip_before_action :check_xhr, only: [:tag_feed, :show, :index, *show_methods] - before_action :set_category, except: [:index, :update, :destroy, - :tag_feed, :search, :notifications, :update_notifications, :personal_messages, :info] + before_action :set_category, + except: %i[ + index + update + destroy + tag_feed + search + notifications + update_notifications + personal_messages + info + ] - before_action :fetch_tag, only: [:info, :create_synonyms, :destroy_synonym] + before_action :fetch_tag, only: %i[info create_synonyms destroy_synonym] - after_action :add_noindex_header, except: [:index, :show] + after_action :add_noindex_header, except: %i[index show] def index @description_meta = I18n.t("tags.title") @@ -39,9 +42,22 @@ class TagsController < ::ApplicationController ungrouped_tags = Tag.where("tags.id NOT IN (SELECT tag_id FROM tag_group_memberships)") ungrouped_tags = ungrouped_tags.where("tags.topic_count > 0") unless show_all_tags - grouped_tag_counts = TagGroup.visible(guardian).order('name ASC').includes(:tags).map do |tag_group| - { id: tag_group.id, name: tag_group.name, tags: self.class.tag_counts_json(tag_group.tags.where(target_tag_id: nil), show_pm_tags: guardian.can_tag_pms?) } - end + grouped_tag_counts = + TagGroup + .visible(guardian) + .order("name ASC") + .includes(:tags) + .map do |tag_group| + { + id: tag_group.id, + name: tag_group.name, + tags: + self.class.tag_counts_json( + tag_group.tags.where(target_tag_id: nil), + show_pm_tags: guardian.can_tag_pms?, + ), + } + end @tags = self.class.tag_counts_json(ungrouped_tags, show_pm_tags: guardian.can_tag_pms?) @extras = { tag_groups: grouped_tag_counts } @@ -49,41 +65,40 @@ class TagsController < ::ApplicationController tags = show_all_tags ? Tag.all : Tag.where("tags.topic_count > 0") unrestricted_tags = DiscourseTagging.filter_visible(tags.where(target_tag_id: nil), guardian) - categories = Category.where("id IN (SELECT category_id FROM category_tags)") - .where("id IN (?)", guardian.allowed_category_ids) - .includes(:tags) + categories = + Category + .where("id IN (SELECT category_id FROM category_tags)") + .where("id IN (?)", guardian.allowed_category_ids) + .includes(:tags) - category_tag_counts = categories.map do |c| - category_tags = self.class.tag_counts_json( - DiscourseTagging.filter_visible(c.tags.where(target_tag_id: nil), guardian) - ) - next if category_tags.empty? - { id: c.id, tags: category_tags } - end.compact + category_tag_counts = + categories + .map do |c| + category_tags = + self.class.tag_counts_json( + DiscourseTagging.filter_visible(c.tags.where(target_tag_id: nil), guardian), + ) + next if category_tags.empty? + { id: c.id, tags: category_tags } + end + .compact @tags = self.class.tag_counts_json(unrestricted_tags, show_pm_tags: guardian.can_tag_pms?) @extras = { categories: category_tag_counts } end respond_to do |format| + format.html { render :index } - format.html do - render :index - end - - format.json do - render json: { - tags: @tags, - extras: @extras - } - end + format.json { render json: { tags: @tags, extras: @extras } } end end Discourse.filters.each do |filter| define_method("show_#{filter}") do @tag_id = params[:tag_id].force_encoding("UTF-8") - @additional_tags = params[:additional_tag_ids].to_s.split('/').map { |t| t.force_encoding("UTF-8") } + @additional_tags = + params[:additional_tag_ids].to_s.split("/").map { |t| t.force_encoding("UTF-8") } list_opts = build_topic_list_options @list = nil @@ -101,14 +116,14 @@ class TagsController < ::ApplicationController @list.more_topics_url = construct_url_with(:next, list_opts) @list.prev_topics_url = construct_url_with(:prev, list_opts) @rss = "tag" - @description_meta = I18n.t("rss_by_tag", tag: tag_params.join(' & ')) + @description_meta = I18n.t("rss_by_tag", tag: tag_params.join(" & ")) @title = @description_meta canonical_params = params.slice(:category_slug_path_with_id, :tag_id) canonical_method = url_method(canonical_params) canonical_url "#{Discourse.base_url_no_prefix}#{public_send(canonical_method, *(canonical_params.values.map { |t| t.force_encoding("UTF-8") }))}" - if @list.topics.size == 0 && params[:tag_id] != 'none' && !Tag.where_name(@tag_id).exists? + if @list.topics.size == 0 && params[:tag_id] != "none" && !Tag.where_name(@tag_id).exists? raise Discourse::NotFound.new("tag not found", check_permalinks: true) else respond_with_list(@list) @@ -121,12 +136,7 @@ class TagsController < ::ApplicationController end def info - render_serialized( - @tag, - DetailedTagSerializer, - rest_serializer: true, - root: :tag_info - ) + render_serialized(@tag, DetailedTagSerializer, rest_serializer: true, root: :tag_info) end def update @@ -141,7 +151,11 @@ class TagsController < ::ApplicationController end tag.description = params[:tag][:description] if params[:tag]&.has_key?(:description) if tag.save - StaffActionLogger.new(current_user).log_custom('renamed_tag', previous_value: params[:tag_id], new_value: new_tag_name) + StaffActionLogger.new(current_user).log_custom( + "renamed_tag", + previous_value: params[:tag_id], + new_value: new_tag_name, + ) render json: { tag: { id: tag.name, description: tag.description } } else render_json_error tag.errors.full_messages @@ -149,7 +163,7 @@ class TagsController < ::ApplicationController end def upload - require 'csv' + require "csv" guardian.ensure_can_admin_tags! @@ -159,7 +173,9 @@ class TagsController < ::ApplicationController begin Tag.transaction do CSV.foreach(file.tempfile) do |row| - raise Discourse::InvalidParameters.new(I18n.t("tags.upload_row_too_long")) if row.length > 2 + if row.length > 2 + raise Discourse::InvalidParameters.new(I18n.t("tags.upload_row_too_long")) + end tag_name = DiscourseTagging.clean_tag(row[0]) tag_group_name = row[1] || nil @@ -167,7 +183,8 @@ class TagsController < ::ApplicationController tag = Tag.find_by_name(tag_name) || Tag.create!(name: tag_name) if tag_group_name - tag_group = TagGroup.find_by(name: tag_group_name) || TagGroup.create!(name: tag_group_name) + tag_group = + TagGroup.find_by(name: tag_group_name) || TagGroup.create!(name: tag_group_name) tag.tag_groups << tag_group unless tag.tag_groups.include?(tag_group) end end @@ -187,7 +204,7 @@ class TagsController < ::ApplicationController def destroy_unused guardian.ensure_can_admin_tags! tags = Tag.unused - StaffActionLogger.new(current_user).log_custom('deleted_unused_tags', tags: tags.pluck(:name)) + StaffActionLogger.new(current_user).log_custom("deleted_unused_tags", tags: tags.pluck(:name)) tags.destroy_all render json: success_json end @@ -200,7 +217,7 @@ class TagsController < ::ApplicationController TopicCustomField.transaction do tag.destroy - StaffActionLogger.new(current_user).log_custom('deleted_tag', subject: tag_name) + StaffActionLogger.new(current_user).log_custom("deleted_tag", subject: tag_name) end render json: success_json end @@ -218,7 +235,7 @@ class TagsController < ::ApplicationController latest_results = query.latest_results @topic_list = query.create_list(:by_tag, {}, latest_results) - render 'list/list', formats: [:rss] + render "list/list", formats: [:rss] end def search @@ -227,16 +244,14 @@ class TagsController < ::ApplicationController selected_tags: params[:selected_tags], limit: params[:limit], exclude_synonyms: params[:excludeSynonyms], - exclude_has_synonyms: params[:excludeHasSynonyms] + exclude_has_synonyms: params[:excludeHasSynonyms], } if filter_params[:limit] && filter_params[:limit].to_i < 0 raise Discourse::InvalidParameters.new(:limit) end - if params[:categoryId] - filter_params[:category] = Category.find_by_id(params[:categoryId]) - end + filter_params[:category] = Category.find_by_id(params[:categoryId]) if params[:categoryId] if !params[:q].blank? clean_name = DiscourseTagging.clean_tag(params[:q]) @@ -246,27 +261,35 @@ class TagsController < ::ApplicationController filter_params[:order_popularity] = true end - tags_with_counts, filter_result_context = DiscourseTagging.filter_allowed_tags( - guardian, - **filter_params, - with_context: true - ) + tags_with_counts, filter_result_context = + DiscourseTagging.filter_allowed_tags(guardian, **filter_params, with_context: true) tags = self.class.tag_counts_json(tags_with_counts, show_pm_tags: guardian.can_tag_pms?) json_response = { results: tags } - if clean_name && !tags.find { |h| h[:id].downcase == clean_name.downcase } && tag = Tag.where_name(clean_name).first + if clean_name && !tags.find { |h| h[:id].downcase == clean_name.downcase } && + tag = Tag.where_name(clean_name).first # filter_allowed_tags determined that the tag entered is not allowed json_response[:forbidden] = params[:q] if filter_params[:exclude_synonyms] && tag.synonym? - json_response[:forbidden_message] = I18n.t("tags.forbidden.synonym", tag_name: tag.target_tag.name) + json_response[:forbidden_message] = I18n.t( + "tags.forbidden.synonym", + tag_name: tag.target_tag.name, + ) elsif filter_params[:exclude_has_synonyms] && tag.synonyms.exists? - json_response[:forbidden_message] = I18n.t("tags.forbidden.has_synonyms", tag_name: tag.name) + json_response[:forbidden_message] = I18n.t( + "tags.forbidden.has_synonyms", + tag_name: tag.name, + ) else category_names = tag.categories.where(id: guardian.allowed_category_ids).pluck(:name) - category_names += Category.joins(tag_groups: :tags).where(id: guardian.allowed_category_ids, "tags.id": tag.id).pluck(:name) + category_names += + Category + .joins(tag_groups: :tags) + .where(id: guardian.allowed_category_ids, "tags.id": tag.id) + .pluck(:name) if category_names.present? category_names.uniq! @@ -274,10 +297,13 @@ class TagsController < ::ApplicationController "tags.forbidden.restricted_to", count: category_names.count, tag_name: tag.name, - category_names: category_names.join(", ") + category_names: category_names.join(", "), ) else - json_response[:forbidden_message] = I18n.t("tags.forbidden.in_this_category", tag_name: tag.name) + json_response[:forbidden_message] = I18n.t( + "tags.forbidden.in_this_category", + tag_name: tag.name, + ) end end end @@ -292,7 +318,9 @@ class TagsController < ::ApplicationController def notifications tag = Tag.where_name(params[:tag_id]).first raise Discourse::NotFound unless tag - level = tag.tag_users.where(user: current_user).first.try(:notification_level) || TagUser.notification_levels[:regular] + level = + tag.tag_users.where(user: current_user).first.try(:notification_level) || + TagUser.notification_levels[:regular] render json: { tag_notification: { id: tag.name, notification_level: level.to_i } } end @@ -318,9 +346,14 @@ class TagsController < ::ApplicationController guardian.ensure_can_admin_tags! value = DiscourseTagging.add_or_create_synonyms_by_name(@tag, params[:synonyms]) if value.is_a?(Array) - render json: failed_json.merge( - failed_tags: value.inject({}) { |h, t| h[t.name] = t.errors.full_messages.first; h } - ) + render json: + failed_json.merge( + failed_tags: + value.inject({}) do |h, t| + h[t.name] = t.errors.full_messages.first + h + end, + ) else render json: success_json end @@ -350,24 +383,29 @@ class TagsController < ::ApplicationController end def ensure_visible - raise Discourse::NotFound if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id]) + if DiscourseTagging.hidden_tag_names(guardian).include?(params[:tag_id]) + raise Discourse::NotFound + end end def self.tag_counts_json(tags, show_pm_tags: true) target_tags = Tag.where(id: tags.map(&:target_tag_id).compact.uniq).select(:id, :name) - tags.map do |t| - next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags + tags + .map do |t| + next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags - { - id: t.name, - text: t.name, - name: t.name, - description: t.description, - count: t.topic_count, - pm_count: show_pm_tags ? t.pm_topic_count : 0, - target_tag: t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil - } - end.compact + { + id: t.name, + text: t.name, + name: t.name, + description: t.description, + count: t.topic_count, + pm_count: show_pm_tags ? t.pm_topic_count : 0, + target_tag: + t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil, + } + end + .compact end def set_category @@ -383,7 +421,10 @@ class TagsController < ::ApplicationController if !@filter_on_category permalink = Permalink.find_by_url("c/#{params[:category_slug_path_with_id]}") if permalink.present? && permalink.category_id - return redirect_to "#{Discourse::base_path}/tags#{permalink.target_url}/#{params[:tag_id]}", status: :moved_permanently + return( + redirect_to "#{Discourse.base_path}/tags#{permalink.target_url}/#{params[:tag_id]}", + status: :moved_permanently + ) end end @@ -394,14 +435,15 @@ class TagsController < ::ApplicationController end def page_params - route_params = { format: 'json' } + route_params = { format: "json" } if @filter_on_category if request.path_parameters.include?(:category_slug_path_with_id) slug_path = @filter_on_category.slug_path - route_params[:category_slug_path_with_id] = - (slug_path + [@filter_on_category.id.to_s]).join("/") + route_params[:category_slug_path_with_id] = ( + slug_path + [@filter_on_category.id.to_s] + ).join("/") else route_params[:category] = @filter_on_category.slug_for_url end @@ -453,28 +495,30 @@ class TagsController < ::ApplicationController raise Discourse::NotFound end - url.sub('.json?', '?') + url.sub(".json?", "?") end def build_topic_list_options - options = super.merge( - page: params[:page], - topic_ids: param_to_integer_list(:topic_ids), - category: @filter_on_category ? @filter_on_category.id : params[:category], - order: params[:order], - ascending: params[:ascending], - min_posts: params[:min_posts], - max_posts: params[:max_posts], - status: params[:status], - filter: params[:filter], - state: params[:state], - search: params[:search], - q: params[:q] - ) - options[:no_subcategories] = true if params[:no_subcategories] == true || params[:no_subcategories] == 'true' + options = + super.merge( + page: params[:page], + topic_ids: param_to_integer_list(:topic_ids), + category: @filter_on_category ? @filter_on_category.id : params[:category], + order: params[:order], + ascending: params[:ascending], + min_posts: params[:min_posts], + max_posts: params[:max_posts], + status: params[:status], + filter: params[:filter], + state: params[:state], + search: params[:search], + q: params[:q], + ) + options[:no_subcategories] = true if params[:no_subcategories] == true || + params[:no_subcategories] == "true" options[:per_page] = params[:per_page].to_i.clamp(1, 30) if params[:per_page].present? - if params[:tag_id] == 'none' + if params[:tag_id] == "none" options.delete(:tags) options[:no_tags] = true else diff --git a/app/controllers/theme_javascripts_controller.rb b/app/controllers/theme_javascripts_controller.rb index 418a076b825..63d6484d712 100644 --- a/app/controllers/theme_javascripts_controller.rb +++ b/app/controllers/theme_javascripts_controller.rb @@ -9,10 +9,10 @@ class ThemeJavascriptsController < ApplicationController :preload_json, :redirect_to_login_if_required, :verify_authenticity_token, - only: [:show, :show_map, :show_tests] + only: %i[show show_map show_tests], ) - before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: [:show, :show_map, :show_tests] + before_action :is_asset_path, :no_cookies, :apply_cdn_headers, only: %i[show show_map show_tests] def show raise Discourse::NotFound unless last_modified.present? @@ -24,7 +24,8 @@ class ThemeJavascriptsController < ApplicationController write_if_not_cached(cache_file) do content, has_source_map = query.pluck_first(:content, "source_map IS NOT NULL") if has_source_map - content += "\n//# sourceMappingURL=#{params[:digest]}.map?__ws=#{Discourse.current_hostname}\n" + content += + "\n//# sourceMappingURL=#{params[:digest]}.map?__ws=#{Discourse.current_hostname}\n" end content end @@ -39,9 +40,7 @@ class ThemeJavascriptsController < ApplicationController # Security: safe due to route constraint cache_file = "#{DISK_CACHE_PATH}/#{params[:digest]}.map" - write_if_not_cached(cache_file) do - query.pluck_first(:source_map) - end + write_if_not_cached(cache_file) { query.pluck_first(:source_map) } serve_file(cache_file) end @@ -59,9 +58,7 @@ class ThemeJavascriptsController < ApplicationController @cache_file = "#{TESTS_DISK_CACHE_PATH}/#{digest}.js" return render body: nil, status: 304 if not_modified? - write_if_not_cached(@cache_file) do - content - end + write_if_not_cached(@cache_file) { content } serve_file @cache_file end @@ -73,13 +70,14 @@ class ThemeJavascriptsController < ApplicationController end def last_modified - @last_modified ||= begin - if params[:action].to_s == "show_tests" - File.exist?(@cache_file) ? File.ctime(@cache_file) : nil - else - query.pluck_first(:updated_at) + @last_modified ||= + begin + if params[:action].to_s == "show_tests" + File.exist?(@cache_file) ? File.ctime(@cache_file) : nil + else + query.pluck_first(:updated_at) + end end - end end def not_modified? @@ -95,10 +93,10 @@ class ThemeJavascriptsController < ApplicationController def set_cache_control_headers if Rails.env.development? - response.headers['Last-Modified'] = Time.zone.now.httpdate + response.headers["Last-Modified"] = Time.zone.now.httpdate immutable_for(1.second) else - response.headers['Last-Modified'] = last_modified.httpdate if last_modified + response.headers["Last-Modified"] = last_modified.httpdate if last_modified immutable_for(1.year) end end diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index d2b3caeb243..4ad8b879117 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -1,40 +1,40 @@ # frozen_string_literal: true class TopicsController < ApplicationController - requires_login only: [ - :timings, - :destroy_timings, - :update, - :update_shared_draft, - :destroy, - :recover, - :status, - :invite, - :mute, - :unmute, - :set_notifications, - :move_posts, - :merge_topic, - :clear_pin, - :re_pin, - :status_update, - :timer, - :bulk, - :reset_new, - :change_post_owners, - :change_timestamps, - :archive_message, - :move_to_inbox, - :convert_topic, - :bookmark, - :publish, - :reset_bump_date, - :set_slow_mode - ] + requires_login only: %i[ + timings + destroy_timings + update + update_shared_draft + destroy + recover + status + invite + mute + unmute + set_notifications + move_posts + merge_topic + clear_pin + re_pin + status_update + timer + bulk + reset_new + change_post_owners + change_timestamps + archive_message + move_to_inbox + convert_topic + bookmark + publish + reset_bump_date + set_slow_mode + ] before_action :consider_user_for_promotion, only: :show - skip_before_action :check_xhr, only: [:show, :feed] + skip_before_action :check_xhr, only: %i[show feed] def id_for_slug topic = Topic.find_by_slug(params[:slug]) @@ -51,9 +51,7 @@ class TopicsController < ApplicationController end def show - if request.referer - flash["referer"] ||= request.referer[0..255] - end + flash["referer"] ||= request.referer[0..255] if request.referer # We'd like to migrate the wordpress feed to another url. This keeps up backwards compatibility with # existing installs. @@ -61,13 +59,27 @@ class TopicsController < ApplicationController # work around people somehow sending in arrays, # arrays are not supported - params[:page] = params[:page].to_i rescue 1 + params[:page] = begin + params[:page].to_i + rescue StandardError + 1 + end - opts = params.slice(:username_filters, :filter, :page, :post_number, :show_deleted, :replies_to_post_number, :filter_upwards_post_id, :filter_top_level_replies) + opts = + params.slice( + :username_filters, + :filter, + :page, + :post_number, + :show_deleted, + :replies_to_post_number, + :filter_upwards_post_id, + :filter_top_level_replies, + ) username_filters = opts[:username_filters] - opts[:print] = true if params[:print] == 'true' - opts[:username_filters] = username_filters.split(',') if username_filters.is_a?(String) + opts[:print] = true if params[:print] == "true" + opts[:username_filters] = username_filters.split(",") if username_filters.is_a?(String) # Special case: a slug with a number in front should look by slug first before looking # up that particular number @@ -79,7 +91,14 @@ class TopicsController < ApplicationController if opts[:print] raise Discourse::InvalidAccess unless SiteSetting.max_prints_per_hour_per_user > 0 begin - RateLimiter.new(current_user, "print-topic-per-hour", SiteSetting.max_prints_per_hour_per_user, 1.hour).performed! unless @guardian.is_admin? + unless @guardian.is_admin? + RateLimiter.new( + current_user, + "print-topic-per-hour", + SiteSetting.max_prints_per_hour_per_user, + 1.hour, + ).performed! + end rescue RateLimiter::LimitExceeded return render_json_error I18n.t("rate_limiter.slow_down") end @@ -100,37 +119,38 @@ class TopicsController < ApplicationController # If the user can't see the topic, clean up notifications for it. Notification.remove_for(current_user.id, params[:topic_id]) if current_user - deleted = guardian.can_see_topic?(ex.obj, false) || - (!guardian.can_see_topic?(ex.obj) && - ex.obj&.access_topic_via_group && - ex.obj.deleted_at) + deleted = + guardian.can_see_topic?(ex.obj, false) || + (!guardian.can_see_topic?(ex.obj) && ex.obj&.access_topic_via_group && ex.obj.deleted_at) if SiteSetting.detailed_404 if deleted raise Discourse::NotFound.new( - 'deleted topic', - custom_message: 'deleted_topic', - status: 410, - check_permalinks: true, - original_path: ex.obj.relative_url - ) + "deleted topic", + custom_message: "deleted_topic", + status: 410, + check_permalinks: true, + original_path: ex.obj.relative_url, + ) elsif !guardian.can_see_topic?(ex.obj) && group = ex.obj&.access_topic_via_group raise Discourse::InvalidAccess.new( - 'not in group', - ex.obj, - custom_message: 'not_in_group.title_topic', - custom_message_params: { group: group.name }, - group: group - ) + "not in group", + ex.obj, + custom_message: "not_in_group.title_topic", + custom_message_params: { + group: group.name, + }, + group: group, + ) end raise ex else raise Discourse::NotFound.new( - nil, - check_permalinks: deleted, - original_path: ex.obj.relative_url - ) + nil, + check_permalinks: deleted, + original_path: ex.obj.relative_url, + ) end end @@ -152,9 +172,7 @@ class TopicsController < ApplicationController @topic_view.draft = Draft.get(current_user, @topic_view.draft_key, @topic_view.draft_sequence) end - unless @topic_view.topic.visible - response.headers['X-Robots-Tag'] = 'noindex' - end + response.headers["X-Robots-Tag"] = "noindex" unless @topic_view.topic.visible canonical_url UrlHelper.absolute_without_cdn(@topic_view.canonical_path) @@ -162,7 +180,7 @@ class TopicsController < ApplicationController # we would like to give them a bit more signal about age of data if use_crawler_layout? if last_modified = @topic_view.posts&.map { |p| p.updated_at }&.max&.httpdate - response.headers['Last-Modified'] = last_modified + response.headers["Last-Modified"] = last_modified end end @@ -186,7 +204,13 @@ class TopicsController < ApplicationController def wordpress params.require(:best) params.require(:topic_id) - params.permit(:min_trust_level, :min_score, :min_replies, :bypass_trust_level_score, :only_moderator_liked) + params.permit( + :min_trust_level, + :min_score, + :min_replies, + :bypass_trust_level_score, + :only_moderator_liked, + ) opts = { best: params[:best].to_i, @@ -195,13 +219,14 @@ class TopicsController < ApplicationController min_replies: params[:min_replies].to_i, bypass_trust_level_score: params[:bypass_trust_level_score].to_i, # safe cause 0 means ignore only_moderator_liked: params[:only_moderator_liked].to_s == "true", - exclude_hidden: true + exclude_hidden: true, } @topic_view = TopicView.new(params[:topic_id], current_user, opts) discourse_expires_in 1.minute - wordpress_serializer = TopicViewWordpressSerializer.new(@topic_view, scope: guardian, root: false) + wordpress_serializer = + TopicViewWordpressSerializer.new(@topic_view, scope: guardian, root: false) render_json_dump(wordpress_serializer) end @@ -214,7 +239,7 @@ class TopicsController < ApplicationController filter: params[:filter], skip_limit: true, asc: true, - skip_custom_fields: true + skip_custom_fields: true, } fetch_topic_view(options) @@ -243,8 +268,8 @@ class TopicsController < ApplicationController @topic_view, scope: guardian, root: false, - include_raw: !!params[:include_raw] - ) + include_raw: !!params[:include_raw], + ), ) end @@ -266,25 +291,27 @@ class TopicsController < ApplicationController @topic = Topic.with_deleted.where(id: params[:topic_id]).first guardian.ensure_can_see!(@topic) - @posts = Post.where(hidden: false, deleted_at: nil, topic_id: @topic.id) - .where('posts.id in (?)', post_ids) - .joins("LEFT JOIN users u on u.id = posts.user_id") - .pluck(:id, :cooked, :username, :action_code, :created_at) - .map do |post_id, cooked, username, action_code, created_at| - attrs = { - post_id: post_id, - username: username, - excerpt: PrettyText.excerpt(cooked, 800, keep_emoji_images: true), - } + @posts = + Post + .where(hidden: false, deleted_at: nil, topic_id: @topic.id) + .where("posts.id in (?)", post_ids) + .joins("LEFT JOIN users u on u.id = posts.user_id") + .pluck(:id, :cooked, :username, :action_code, :created_at) + .map do |post_id, cooked, username, action_code, created_at| + attrs = { + post_id: post_id, + username: username, + excerpt: PrettyText.excerpt(cooked, 800, keep_emoji_images: true), + } - if action_code - attrs[:action_code] = action_code - attrs[:created_at] = created_at + if action_code + attrs[:action_code] = action_code + attrs[:created_at] = created_at + end + + attrs end - attrs - end - render json: @posts.to_json end @@ -297,18 +324,14 @@ class TopicsController < ApplicationController PostTiming.destroy_for(current_user.id, [topic_id]) end - last_notification = Notification - .where( - user_id: current_user.id, - topic_id: topic_id - ) - .order(created_at: :desc) - .limit(1) - .first + last_notification = + Notification + .where(user_id: current_user.id, topic_id: topic_id) + .order(created_at: :desc) + .limit(1) + .first - if last_notification - last_notification.update!(read: false) - end + last_notification.update!(read: false) if last_notification render body: nil end @@ -321,9 +344,7 @@ class TopicsController < ApplicationController guardian.ensure_can_publish_topic!(topic, category) row_count = SharedDraft.where(topic_id: topic.id).update_all(category_id: category.id) - if row_count == 0 - SharedDraft.create(topic_id: topic.id, category_id: category.id) - end + SharedDraft.create(topic_id: topic.id, category_id: category.id) if row_count == 0 render json: success_json end @@ -342,15 +363,14 @@ class TopicsController < ApplicationController if category || (params[:category_id].to_i == 0) guardian.ensure_can_move_topic_to_category!(category) else - return render_json_error(I18n.t('category.errors.not_found')) + return render_json_error(I18n.t("category.errors.not_found")) end - if category && topic_tags = (params[:tags] || topic.tags.pluck(:name)).reject { |c| c.empty? } + if category && + topic_tags = (params[:tags] || topic.tags.pluck(:name)).reject { |c| c.empty? } if topic_tags.present? - allowed_tags = DiscourseTagging.filter_allowed_tags( - guardian, - category: category - ).map(&:name) + allowed_tags = + DiscourseTagging.filter_allowed_tags(guardian, category: category).map(&:name) invalid_tags = topic_tags - allowed_tags @@ -367,9 +387,13 @@ class TopicsController < ApplicationController if !invalid_tags.empty? if (invalid_tags & DiscourseTagging.hidden_tag_names).present? - return render_json_error(I18n.t('category.errors.disallowed_tags_generic')) + return render_json_error(I18n.t("category.errors.disallowed_tags_generic")) else - return render_json_error(I18n.t('category.errors.disallowed_topic_tags', tags: invalid_tags.join(", "))) + return( + render_json_error( + I18n.t("category.errors.disallowed_topic_tags", tags: invalid_tags.join(", ")), + ) + ) end end end @@ -379,9 +403,7 @@ class TopicsController < ApplicationController changes = {} - PostRevisor.tracked_topic_fields.each_key do |f| - changes[f] = params[f] if params.has_key?(f) - end + PostRevisor.tracked_topic_fields.each_key { |f| changes[f] = params[f] if params.has_key?(f) } changes.delete(:title) if topic.title == changes[:title] changes.delete(:category_id) if topic.category_id.to_i == changes[:category_id].to_i @@ -397,17 +419,16 @@ class TopicsController < ApplicationController bypass_bump = should_bypass_bump?(changes) first_post = topic.ordered_posts.first - success = PostRevisor.new(first_post, topic).revise!( - current_user, - changes, - validate_post: false, - bypass_bump: bypass_bump, - keep_existing_draft: params[:keep_existing_draft].to_s == "true" - ) + success = + PostRevisor.new(first_post, topic).revise!( + current_user, + changes, + validate_post: false, + bypass_bump: bypass_bump, + keep_existing_draft: params[:keep_existing_draft].to_s == "true", + ) - if !success && topic.errors.blank? - topic.errors.add(:base, :unable_to_update) - end + topic.errors.add(:base, :unable_to_update) if !success && topic.errors.blank? end # this is used to return the title to the client as it may have been changed by "TextCleaner" @@ -419,7 +440,12 @@ class TopicsController < ApplicationController topic = Topic.find_by(id: params[:topic_id]) guardian.ensure_can_edit_tags!(topic) - success = PostRevisor.new(topic.first_post, topic).revise!(current_user, { tags: params[:tags] }, validate_post: false) + success = + PostRevisor.new(topic.first_post, topic).revise!( + current_user, + { tags: params[:tags] }, + validate_post: false, + ) success ? render_serialized(topic, BasicTopicSerializer) : render_json_error(topic) end @@ -431,10 +457,16 @@ class TopicsController < ApplicationController visible_topics = Topic.listable_topics.visible render json: { - pinned_in_category_count: visible_topics.where(category_id: category_id).where(pinned_globally: false).where.not(pinned_at: nil).count, - pinned_globally_count: visible_topics.where(pinned_globally: true).where.not(pinned_at: nil).count, - banner_count: Topic.listable_topics.where(archetype: Archetype.banner).count, - } + pinned_in_category_count: + visible_topics + .where(category_id: category_id) + .where(pinned_globally: false) + .where.not(pinned_at: nil) + .count, + pinned_globally_count: + visible_topics.where(pinned_globally: true).where.not(pinned_at: nil).count, + banner_count: Topic.listable_topics.where(archetype: Archetype.banner).count, + } end def status @@ -444,33 +476,33 @@ class TopicsController < ApplicationController status = params[:status] topic_id = params[:topic_id].to_i - enabled = params[:enabled] == 'true' + enabled = params[:enabled] == "true" check_for_status_presence(:status, status) @topic = Topic.find_by(id: topic_id) case status - when 'closed' + when "closed" guardian.ensure_can_close_topic!(@topic) - when 'archived' + when "archived" guardian.ensure_can_archive_topic!(@topic) - when 'visible' + when "visible" guardian.ensure_can_toggle_topic_visibility!(@topic) - when 'pinned' + when "pinned" guardian.ensure_can_pin_unpin_topic!(@topic) else guardian.ensure_can_moderate!(@topic) end - params[:until] === '' ? params[:until] = nil : params[:until] + params[:until] === "" ? params[:until] = nil : params[:until] @topic.update_status(status, enabled, current_user, until: params[:until]) - render json: success_json.merge!( - topic_status_update: TopicTimerSerializer.new( - TopicTimer.find_by(topic: @topic), root: false - ) - ) + render json: + success_json.merge!( + topic_status_update: + TopicTimerSerializer.new(TopicTimer.find_by(topic: @topic), root: false), + ) end def mute @@ -488,7 +520,7 @@ class TopicsController < ApplicationController status_type = begin TopicTimer.types.fetch(params[:status_type].to_sym) - rescue + rescue StandardError invalid_param(:status_type) end based_on_last_post = params[:based_on_last_post] @@ -497,37 +529,31 @@ class TopicsController < ApplicationController topic = Topic.find_by(id: params[:topic_id]) guardian.ensure_can_moderate!(topic) - if TopicTimer.destructive_types.values.include?(status_type) - guardian.ensure_can_delete!(topic) - end + guardian.ensure_can_delete!(topic) if TopicTimer.destructive_types.values.include?(status_type) - options = { - by_user: current_user, - based_on_last_post: based_on_last_post - } + options = { by_user: current_user, based_on_last_post: based_on_last_post } options.merge!(category_id: params[:category_id]) if !params[:category_id].blank? - options.merge!(duration_minutes: params[:duration_minutes].to_i) if params[:duration_minutes].present? + if params[:duration_minutes].present? + options.merge!(duration_minutes: params[:duration_minutes].to_i) + end options.merge!(duration: params[:duration].to_i) if params[:duration].present? begin - topic_timer = topic.set_or_create_timer( - status_type, - params[:time], - **options - ) + topic_timer = topic.set_or_create_timer(status_type, params[:time], **options) rescue ActiveRecord::RecordInvalid => e return render_json_error(e.message) end if topic.save - render json: success_json.merge!( - execute_at: topic_timer&.execute_at, - duration_minutes: topic_timer&.duration_minutes, - based_on_last_post: topic_timer&.based_on_last_post, - closed: topic.closed, - category_id: topic_timer&.category_id - ) + render json: + success_json.merge!( + execute_at: topic_timer&.execute_at, + duration_minutes: topic_timer&.duration_minutes, + based_on_last_post: topic_timer&.based_on_last_post, + closed: topic.closed, + category_id: topic_timer&.category_id, + ) else render_json_error(topic) end @@ -572,24 +598,16 @@ class TopicsController < ApplicationController group_ids = current_user.groups.pluck(:id) if group_ids.present? - allowed_groups = topic.allowed_groups - .where('topic_allowed_groups.group_id IN (?)', group_ids).pluck(:id) + allowed_groups = + topic.allowed_groups.where("topic_allowed_groups.group_id IN (?)", group_ids).pluck(:id) allowed_groups.each do |id| if archive - GroupArchivedMessage.archive!( - id, - topic, - acting_user_id: current_user.id - ) + GroupArchivedMessage.archive!(id, topic, acting_user_id: current_user.id) group_id = id else - GroupArchivedMessage.move_to_inbox!( - id, - topic, - acting_user_id: current_user.id - ) + GroupArchivedMessage.move_to_inbox!(id, topic, acting_user_id: current_user.id) end end end @@ -616,9 +634,7 @@ class TopicsController < ApplicationController bookmark_manager = BookmarkManager.new(current_user) bookmark_manager.create_for(bookmarkable_id: topic.id, bookmarkable_type: "Topic") - if bookmark_manager.errors.any? - return render_json_error(bookmark_manager, status: 400) - end + return render_json_error(bookmark_manager, status: 400) if bookmark_manager.errors.any? render body: nil end @@ -639,7 +655,7 @@ class TopicsController < ApplicationController current_user, topic.ordered_posts.with_deleted.first, context: params[:context], - force_destroy: force_destroy + force_destroy: force_destroy, ).destroy render body: nil @@ -697,15 +713,20 @@ class TopicsController < ApplicationController raise Discourse::NotFound if !topic if !pm_has_slots?(topic) - return render_json_error( - I18n.t("pm_reached_recipients_limit", recipients_limit: SiteSetting.max_allowed_message_recipients) + return( + render_json_error( + I18n.t( + "pm_reached_recipients_limit", + recipients_limit: SiteSetting.max_allowed_message_recipients, + ), + ) ) end if topic.private_message? guardian.ensure_can_invite_group_to_private_message!(group, topic) topic.invite_group(current_user, group) - render_json_dump BasicGroupSerializer.new(group, scope: guardian, root: 'group') + render_json_dump BasicGroupSerializer.new(group, scope: guardian, root: "group") else render json: failed_json, status: 422 end @@ -715,28 +736,31 @@ class TopicsController < ApplicationController topic = Topic.find_by(id: params[:topic_id]) raise Discourse::NotFound if !topic - if !topic.private_message? - return render_json_error(I18n.t("topic_invite.not_pm")) - end + return render_json_error(I18n.t("topic_invite.not_pm")) if !topic.private_message? if !pm_has_slots?(topic) - return render_json_error( - I18n.t("pm_reached_recipients_limit", recipients_limit: SiteSetting.max_allowed_message_recipients) + return( + render_json_error( + I18n.t( + "pm_reached_recipients_limit", + recipients_limit: SiteSetting.max_allowed_message_recipients, + ), + ) ) end guardian.ensure_can_invite_to!(topic) username_or_email = params[:user] ? fetch_username : fetch_email - group_ids = Group.lookup_groups( - group_ids: params[:group_ids], - group_names: params[:group_names] - ).pluck(:id) + group_ids = + Group.lookup_groups(group_ids: params[:group_ids], group_names: params[:group_names]).pluck( + :id, + ) begin if topic.invite(current_user, username_or_email, group_ids, params[:custom_message]) if user = User.find_by_username_or_email(username_or_email) - render_json_dump BasicUserSerializer.new(user, scope: guardian, root: 'user') + render_json_dump BasicUserSerializer.new(user, scope: guardian, root: "user") else render json: success_json end @@ -744,19 +768,16 @@ class TopicsController < ApplicationController json = failed_json unless topic.private_message? - group_names = topic.category - .visible_group_names(current_user) - .where(automatic: false) - .pluck(:name) - .join(", ") + group_names = + topic + .category + .visible_group_names(current_user) + .where(automatic: false) + .pluck(:name) + .join(", ") if group_names.present? - json.merge!(errors: [ - I18n.t( - "topic_invite.failed_to_invite", - group_names: group_names - ) - ]) + json.merge!(errors: [I18n.t("topic_invite.failed_to_invite", group_names: group_names)]) end end @@ -792,7 +813,8 @@ class TopicsController < ApplicationController if params[:archetype].present? args[:archetype] = params[:archetype] - args[:participants] = params[:participants] if params[:participants].present? && params[:archetype] == "private_message" + args[:participants] = params[:participants] if params[:participants].present? && + params[:archetype] == "private_message" end destination_topic = topic.move_posts(current_user, topic.posts.pluck(:id), args) @@ -814,8 +836,13 @@ class TopicsController < ApplicationController if params[:title].present? # when creating a new topic, ensure the 1st post is a regular post - if Post.where(topic: topic, id: post_ids).order(:post_number).pluck_first(:post_type) != Post.types[:regular] - return render_json_error("When moving posts to a new topic, the first post must be a regular post.") + if Post.where(topic: topic, id: post_ids).order(:post_number).pluck_first(:post_type) != + Post.types[:regular] + return( + render_json_error( + "When moving posts to a new topic, the first post must be a regular post.", + ) + ) end if params[:category_id].present? @@ -837,10 +864,12 @@ class TopicsController < ApplicationController guardian.ensure_can_change_post_owner! begin - PostOwnerChanger.new(post_ids: params[:post_ids].to_a, - topic_id: params[:topic_id].to_i, - new_owner: User.find_by(username: params[:username]), - acting_user: current_user).change_owner! + PostOwnerChanger.new( + post_ids: params[:post_ids].to_a, + topic_id: params[:topic_id].to_i, + new_owner: User.find_by(username: params[:username]), + acting_user: current_user, + ).change_owner! render json: success_json rescue ArgumentError render json: failed_json, status: 422 @@ -857,12 +886,13 @@ class TopicsController < ApplicationController previous_timestamp = topic.first_post.created_at begin - TopicTimestampChanger.new( - topic: topic, - timestamp: timestamp - ).change! + TopicTimestampChanger.new(topic: topic, timestamp: timestamp).change! - StaffActionLogger.new(current_user).log_topic_timestamps_changed(topic, Time.zone.at(timestamp), previous_timestamp) + StaffActionLogger.new(current_user).log_topic_timestamps_changed( + topic, + Time.zone.at(timestamp), + previous_timestamp, + ) render json: success_json rescue ActiveRecord::RecordInvalid, TopicTimestampChanger::InvalidTimestampError @@ -900,7 +930,7 @@ class TopicsController < ApplicationController topic_id, topic_time, timings.map { |post_number, t| [post_number.to_i, t.to_i] }, - mobile: view_context.mobile_view? + mobile: view_context.mobile_view?, ) render body: nil end @@ -914,43 +944,48 @@ class TopicsController < ApplicationController rescue Discourse::NotLoggedIn raise Discourse::NotFound rescue Discourse::InvalidAccess => ex - - deleted = guardian.can_see_topic?(ex.obj, false) || - (!guardian.can_see_topic?(ex.obj) && - ex.obj&.access_topic_via_group && - ex.obj.deleted_at) + deleted = + guardian.can_see_topic?(ex.obj, false) || + (!guardian.can_see_topic?(ex.obj) && ex.obj&.access_topic_via_group && ex.obj.deleted_at) raise Discourse::NotFound.new( - nil, - check_permalinks: deleted, - original_path: ex.obj.relative_url - ) + nil, + check_permalinks: deleted, + original_path: ex.obj.relative_url, + ) end discourse_expires_in 1.minute - response.headers['X-Robots-Tag'] = 'noindex, nofollow' - render 'topics/show', formats: [:rss] + response.headers["X-Robots-Tag"] = "noindex, nofollow" + render "topics/show", formats: [:rss] end def bulk if params[:topic_ids].present? unless Array === params[:topic_ids] - raise Discourse::InvalidParameters.new( - "Expecting topic_ids to contain a list of topic ids" - ) + raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids") end topic_ids = params[:topic_ids].map { |t| t.to_i } - elsif params[:filter] == 'unread' + elsif params[:filter] == "unread" topic_ids = bulk_unread_topic_ids else raise ActionController::ParameterMissing.new(:topic_ids) end - operation = params - .require(:operation) - .permit(:type, :group, :category_id, :notification_level_id, *DiscoursePluginRegistry.permitted_bulk_action_parameters, tags: []) - .to_h.symbolize_keys + operation = + params + .require(:operation) + .permit( + :type, + :group, + :category_id, + :notification_level_id, + *DiscoursePluginRegistry.permitted_bulk_action_parameters, + tags: [], + ) + .to_h + .symbolize_keys raise ActionController::ParameterMissing.new(:operation_type) if operation[:type].blank? operator = TopicsBulkAction.new(current_user, topic_ids, operation, group: operation[:group]) @@ -963,14 +998,14 @@ class TopicsController < ApplicationController if params[:topic_ids].present? unless Array === params[:topic_ids] - raise Discourse::InvalidParameters.new( - "Expecting topic_ids to contain a list of topic ids" - ) + raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids") end - topic_scope = topic_query - .private_messages_for(current_user, :all) - .where("topics.id IN (?)", params[:topic_ids].map(&:to_i)) + topic_scope = + topic_query.private_messages_for(current_user, :all).where( + "topics.id IN (?)", + params[:topic_ids].map(&:to_i), + ) else params.require(:inbox) inbox = params[:inbox].to_s @@ -978,11 +1013,8 @@ class TopicsController < ApplicationController topic_scope = topic_query.filter_private_message_new(current_user, filter) end - topic_ids = TopicsBulkAction.new( - current_user, - topic_scope.pluck(:id), - type: "dismiss_topics" - ).perform! + topic_ids = + TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform! render json: success_json.merge(topic_ids: topic_ids) end @@ -991,8 +1023,9 @@ class TopicsController < ApplicationController topic_scope = if params[:category_id].present? category_ids = [params[:category_id]] - if params[:include_subcategories] == 'true' - category_ids = category_ids.concat(Category.where(parent_category_id: params[:category_id]).pluck(:id)) + if params[:include_subcategories] == "true" + category_ids = + category_ids.concat(Category.where(parent_category_id: params[:category_id]).pluck(:id)) end scope = Topic.where(category_id: category_ids) @@ -1012,16 +1045,15 @@ class TopicsController < ApplicationController if params[:topic_ids].present? unless Array === params[:topic_ids] - raise Discourse::InvalidParameters.new( - "Expecting topic_ids to contain a list of topic ids" - ) + raise Discourse::InvalidParameters.new("Expecting topic_ids to contain a list of topic ids") end topic_ids = params[:topic_ids].map { |t| t.to_i } topic_scope = topic_scope.where(id: topic_ids) end - dismissed_topic_ids = TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform! + dismissed_topic_ids = + TopicsBulkAction.new(current_user, topic_scope.pluck(:id), type: "dismiss_topics").perform! TopicTrackingState.publish_dismiss_new(current_user.id, topic_ids: dismissed_topic_ids) render body: nil @@ -1034,7 +1066,8 @@ class TopicsController < ApplicationController guardian.ensure_can_convert_topic!(topic) if params[:type] == "public" - converted_topic = topic.convert_to_public_topic(current_user, category_id: params[:category_id]) + converted_topic = + topic.convert_to_public_topic(current_user, category_id: params[:category_id]) else converted_topic = topic.convert_to_private_message(current_user) end @@ -1065,11 +1098,7 @@ class TopicsController < ApplicationController time = enabled && params[:enabled_until].present? ? params[:enabled_until] : nil - topic.set_or_create_timer( - slow_mode_type, - time, - by_user: timer&.user - ) + topic.set_or_create_timer(slow_mode_type, time, by_user: timer&.user) head :ok end @@ -1077,16 +1106,12 @@ class TopicsController < ApplicationController private def topic_params - params.permit( - :topic_id, - :topic_time, - timings: {} - ) + params.permit(:topic_id, :topic_time, timings: {}) end def fetch_topic_view(options) if (username_filters = params[:username_filters]).present? - options[:username_filters] = username_filters.split(',') + options[:username_filters] = username_filters.split(",") end @topic_view = TopicView.new(params[:topic_id], current_user, options) @@ -1132,7 +1157,7 @@ class TopicsController < ApplicationController url << ".json" if request.format.json? opts.each do |k, v| - s = url.include?('?') ? '&' : '?' + s = url.include?("?") ? "&" : "?" url << "#{s}#{k}=#{v}" end @@ -1140,7 +1165,7 @@ class TopicsController < ApplicationController end def track_visit_to_topic - topic_id = @topic_view.topic.id + topic_id = @topic_view.topic.id ip = request.remote_ip user_id = (current_user.id if current_user) track_visit = should_track_visit_to_topic? @@ -1152,8 +1177,8 @@ class TopicsController < ApplicationController current_user: current_user, topic_id: @topic_view.topic.id, post_number: @topic_view.current_post_number, - username: request['u'], - ip_address: request.remote_ip + username: request["u"], + ip_address: request.remote_ip, } # defer this way so we do not capture the whole controller # in the closure @@ -1181,32 +1206,33 @@ class TopicsController < ApplicationController end def perform_show_response - if request.head? head :ok return end - topic_view_serializer = TopicViewSerializer.new( - @topic_view, - scope: guardian, - root: false, - include_raw: !!params[:include_raw], - exclude_suggested_and_related: !!params[:replies_to_post_number] || !!params[:filter_upwards_post_id] || !!params[:filter_top_level_replies] - ) + topic_view_serializer = + TopicViewSerializer.new( + @topic_view, + scope: guardian, + root: false, + include_raw: !!params[:include_raw], + exclude_suggested_and_related: + !!params[:replies_to_post_number] || !!params[:filter_upwards_post_id] || + !!params[:filter_top_level_replies], + ) respond_to do |format| format.html do @tags = SiteSetting.tagging_enabled ? @topic_view.topic.tags : [] @breadcrumbs = helpers.categories_breadcrumb(@topic_view.topic) || [] - @description_meta = @topic_view.topic.excerpt.present? ? @topic_view.topic.excerpt : @topic_view.summary + @description_meta = + @topic_view.topic.excerpt.present? ? @topic_view.topic.excerpt : @topic_view.summary store_preloaded("topic_#{@topic_view.topic.id}", MultiJson.dump(topic_view_serializer)) render :show end - format.json do - render_json_dump(topic_view_serializer) - end + format.json { render_json_dump(topic_view_serializer) } end end @@ -1221,12 +1247,15 @@ class TopicsController < ApplicationController def move_posts_to_destination(topic) args = {} args[:title] = params[:title] if params[:title].present? - args[:destination_topic_id] = params[:destination_topic_id].to_i if params[:destination_topic_id].present? + args[:destination_topic_id] = params[:destination_topic_id].to_i if params[ + :destination_topic_id + ].present? args[:tags] = params[:tags] if params[:tags].present? if params[:archetype].present? args[:archetype] = params[:archetype] - args[:participants] = params[:participants] if params[:participants].present? && params[:archetype] == "private_message" + args[:participants] = params[:participants] if params[:participants].present? && + params[:archetype] == "private_message" else args[:category_id] = params[:category_id].to_i if params[:category_id].present? end @@ -1235,7 +1264,7 @@ class TopicsController < ApplicationController end def check_for_status_presence(key, attr) - invalid_param(key) unless %w(pinned pinned_globally visible closed archived).include?(attr) + invalid_param(key) unless %w[pinned pinned_globally visible closed archived].include?(attr) end def invalid_param(key) @@ -1264,7 +1293,11 @@ class TopicsController < ApplicationController topic_query.options[:limit] = false topics = topic_query.filter_private_messages_unread(current_user, filter) else - topics = TopicQuery.unread_filter(topic_query.joined_topic_user, whisperer: guardian.is_whisperer?).listable_topics + topics = + TopicQuery.unread_filter( + topic_query.joined_topic_user, + whisperer: guardian.is_whisperer?, + ).listable_topics topics = TopicQuery.tracked_filter(topics, current_user.id) if params[:tracked].to_s == "true" if params[:category_id] @@ -1274,7 +1307,7 @@ class TopicsController < ApplicationController category_id = :category_id SQL else - topics = topics.where('category_id = ?', params[:category_id]) + topics = topics.where("category_id = ?", params[:category_id]) end end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb index f1041f57bc6..218b5ae88dd 100644 --- a/app/controllers/uploads_controller.rb +++ b/app/controllers/uploads_controller.rb @@ -5,13 +5,18 @@ require "mini_mime" class UploadsController < ApplicationController include ExternalUploadHelpers - requires_login except: [:show, :show_short, :_show_secure_deprecated, :show_secure] + requires_login except: %i[show show_short _show_secure_deprecated show_secure] - skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required, only: [:show, :show_short, :_show_secure_deprecated, :show_secure] + skip_before_action :preload_json, + :check_xhr, + :redirect_to_login_if_required, + only: %i[show show_short _show_secure_deprecated show_secure] protect_from_forgery except: :show - before_action :is_asset_path, :apply_cdn_headers, only: [:show, :show_short, :_show_secure_deprecated, :show_secure] - before_action :external_store_check, only: [:_show_secure_deprecated, :show_secure] + before_action :is_asset_path, + :apply_cdn_headers, + only: %i[show show_short _show_secure_deprecated show_secure] + before_action :external_store_check, only: %i[_show_secure_deprecated show_secure] SECURE_REDIRECT_GRACE_SECONDS = 5 @@ -20,18 +25,21 @@ class UploadsController < ApplicationController me = current_user params.permit(:type, :upload_type) - if params[:type].blank? && params[:upload_type].blank? - raise Discourse::InvalidParameters - end + raise Discourse::InvalidParameters if params[:type].blank? && params[:upload_type].blank? # 50 characters ought to be enough for the upload type - type = (params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50] + type = + (params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50] - if type == "avatar" && !me.admin? && (SiteSetting.discourse_connect_overrides_avatar || !TrustLevelAndStaffAndDisabledSetting.matches?(SiteSetting.allow_uploaded_avatars, me)) + if type == "avatar" && !me.admin? && + ( + SiteSetting.discourse_connect_overrides_avatar || + !TrustLevelAndStaffAndDisabledSetting.matches?(SiteSetting.allow_uploaded_avatars, me) + ) return render json: failed_json, status: 422 end - url = params[:url] - file = params[:file] || params[:files]&.first + url = params[:url] + file = params[:file] || params[:files]&.first pasted = params[:pasted] == "true" for_private_message = params[:for_private_message] == "true" for_site_setting = params[:for_site_setting] == "true" @@ -42,17 +50,18 @@ class UploadsController < ApplicationController # longer term we may change this hijack do begin - info = UploadsController.create_upload( - current_user: me, - file: file, - url: url, - type: type, - for_private_message: for_private_message, - for_site_setting: for_site_setting, - pasted: pasted, - is_api: is_api, - retain_hours: retain_hours - ) + info = + UploadsController.create_upload( + current_user: me, + file: file, + url: url, + type: type, + for_private_message: for_private_message, + for_site_setting: for_site_setting, + pasted: pasted, + is_api: is_api, + retain_hours: retain_hours, + ) rescue => e render json: failed_json.merge(message: e.message&.split("\n")&.first), status: 422 else @@ -66,13 +75,11 @@ class UploadsController < ApplicationController uploads = [] if (params[:short_urls] && params[:short_urls].length > 0) - PrettyText::Helpers.lookup_upload_urls(params[:short_urls]).each do |short_url, paths| - uploads << { - short_url: short_url, - url: paths[:url], - short_path: paths[:short_path] - } - end + PrettyText::Helpers + .lookup_upload_urls(params[:short_urls]) + .each do |short_url, paths| + uploads << { short_url: short_url, url: paths[:url], short_path: paths[:short_path] } + end end render json: uploads.to_json @@ -87,7 +94,9 @@ class UploadsController < ApplicationController RailsMultisite::ConnectionManagement.with_connection(params[:site]) do |db| return render_404 if SiteSetting.prevent_anons_from_downloading_files && current_user.nil? - if upload = Upload.find_by(sha1: params[:sha]) || Upload.find_by(id: params[:id], url: request.env["PATH_INFO"]) + if upload = + Upload.find_by(sha1: params[:sha]) || + Upload.find_by(id: params[:id], url: request.env["PATH_INFO"]) unless Discourse.store.internal? local_store = FileStore::LocalStore.new return render_404 unless local_store.has_been_uploaded?(upload.url) @@ -104,21 +113,18 @@ class UploadsController < ApplicationController # do not serve uploads requested via XHR to prevent XSS return xhr_not_allowed if request.xhr? - if SiteSetting.prevent_anons_from_downloading_files && current_user.nil? - return render_404 - end + return render_404 if SiteSetting.prevent_anons_from_downloading_files && current_user.nil? sha1 = Upload.sha1_from_base62_encoded(params[:base62]) if upload = Upload.find_by(sha1: sha1) - if upload.secure? && SiteSetting.secure_uploads? - return handle_secure_upload_request(upload) - end + return handle_secure_upload_request(upload) if upload.secure? && SiteSetting.secure_uploads? if Discourse.store.internal? send_file_local_upload(upload) else - redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true + redirect_to Discourse.store.url_for(upload, force_download: force_download?), + allow_other_host: true end else render_404 @@ -156,7 +162,8 @@ class UploadsController < ApplicationController # private, so we don't want to go to the CDN url just yet otherwise we # will get a 403. if the upload is not secure we assume the ACL is public signed_secure_url = Discourse.store.signed_url_for_path(path_with_ext) - redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url), allow_other_host: true + redirect_to upload.secure? ? signed_secure_url : Discourse.store.cdn_url(upload.url), + allow_other_host: true end def handle_secure_upload_request(upload, path_with_ext = nil) @@ -167,20 +174,25 @@ class UploadsController < ApplicationController end # defaults to public: false, so only cached by the client browser - cache_seconds = SiteSetting.s3_presigned_get_url_expires_after_seconds - SECURE_REDIRECT_GRACE_SECONDS + cache_seconds = + SiteSetting.s3_presigned_get_url_expires_after_seconds - SECURE_REDIRECT_GRACE_SECONDS expires_in cache_seconds.seconds # url_for figures out the full URL, handling multisite DBs, # and will return a presigned URL for the upload if path_with_ext.blank? - return redirect_to Discourse.store.url_for(upload, force_download: force_download?), allow_other_host: true + return( + redirect_to Discourse.store.url_for(upload, force_download: force_download?), + allow_other_host: true + ) end redirect_to Discourse.store.signed_url_for_path( - path_with_ext, - expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds, - force_download: force_download? - ), allow_other_host: true + path_with_ext, + expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds, + force_download: force_download?, + ), + allow_other_host: true end def metadata @@ -189,11 +201,11 @@ class UploadsController < ApplicationController raise Discourse::NotFound unless upload render json: { - original_filename: upload.original_filename, - width: upload.width, - height: upload.height, - human_filesize: upload.human_filesize - } + original_filename: upload.original_filename, + width: upload.width, + height: upload.height, + human_filesize: upload.human_filesize, + } end protected @@ -207,17 +219,18 @@ class UploadsController < ApplicationController end def validate_file_size(file_name:, file_size:) - if file_size.zero? - raise ExternalUploadValidationError.new(I18n.t("upload.size_zero_failure")) - end + raise ExternalUploadValidationError.new(I18n.t("upload.size_zero_failure")) if file_size.zero? if file_size_too_big?(file_name, file_size) raise ExternalUploadValidationError.new( - I18n.t( - "upload.attachments.too_large_humanized", - max_size: ActiveSupport::NumberHelper.number_to_human_size(SiteSetting.max_attachment_size_kb.kilobytes) - ) - ) + I18n.t( + "upload.attachments.too_large_humanized", + max_size: + ActiveSupport::NumberHelper.number_to_human_size( + SiteSetting.max_attachment_size_kb.kilobytes, + ), + ), + ) end end @@ -236,25 +249,34 @@ class UploadsController < ApplicationController serialized ||= (data || {}).as_json end - def self.create_upload(current_user:, - file:, - url:, - type:, - for_private_message:, - for_site_setting:, - pasted:, - is_api:, - retain_hours:) - + def self.create_upload( + current_user:, + file:, + url:, + type:, + for_private_message:, + for_site_setting:, + pasted:, + is_api:, + retain_hours: + ) if file.nil? if url.present? && is_api - maximum_upload_size = [SiteSetting.max_image_size_kb, SiteSetting.max_attachment_size_kb].max.kilobytes - tempfile = FileHelper.download( - url, - follow_redirect: true, - max_file_size: maximum_upload_size, - tmp_file_name: "discourse-upload-#{type}" - ) rescue nil + maximum_upload_size = [ + SiteSetting.max_image_size_kb, + SiteSetting.max_attachment_size_kb, + ].max.kilobytes + tempfile = + begin + FileHelper.download( + url, + follow_redirect: true, + max_file_size: maximum_upload_size, + tmp_file_name: "discourse-upload-#{type}", + ) + rescue StandardError + nil + end filename = File.basename(URI.parse(url).path) end else @@ -288,13 +310,14 @@ class UploadsController < ApplicationController # as they may be further reduced in size by UploadCreator (at this point # they may have already been reduced in size by preprocessors) def file_size_too_big?(file_name, file_size) - !FileHelper.is_supported_image?(file_name) && file_size >= SiteSetting.max_attachment_size_kb.kilobytes + !FileHelper.is_supported_image?(file_name) && + file_size >= SiteSetting.max_attachment_size_kb.kilobytes end def send_file_local_upload(upload) opts = { filename: upload.original_filename, - content_type: MiniMime.lookup_by_filename(upload.original_filename)&.content_type + content_type: MiniMime.lookup_by_filename(upload.original_filename)&.content_type, } if !FileHelper.is_inline_image?(upload.original_filename) @@ -313,7 +336,11 @@ class UploadsController < ApplicationController begin yield rescue Aws::S3::Errors::ServiceError => err - message = debug_upload_error(err, I18n.t("upload.create_multipart_failure", additional_detail: err.message)) + message = + debug_upload_error( + err, + I18n.t("upload.create_multipart_failure", additional_detail: err.message), + ) raise ExternalUploadHelpers::ExternalUploadValidationError.new(message) end end diff --git a/app/controllers/user_actions_controller.rb b/app/controllers/user_actions_controller.rb index 18efefa7079..24326698bb9 100644 --- a/app/controllers/user_actions_controller.rb +++ b/app/controllers/user_actions_controller.rb @@ -4,7 +4,11 @@ class UserActionsController < ApplicationController def index user_actions_params.require(:username) - user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + user = + fetch_user_from_params( + include_inactive: + current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts), + ) offset = [0, user_actions_params[:offset].to_i].max action_types = (user_actions_params[:filter] || "").split(",").map(&:to_i) limit = user_actions_params.fetch(:limit, 30).to_i @@ -20,11 +24,11 @@ class UserActionsController < ApplicationController action_types: action_types, guardian: guardian, ignore_private_messages: params[:filter].blank?, - acting_username: params[:acting_username] + acting_username: params[:acting_username], } stream = UserAction.stream(opts).to_a - render_serialized(stream, UserActionSerializer, root: 'user_actions') + render_serialized(stream, UserActionSerializer, root: "user_actions") end def show diff --git a/app/controllers/user_api_keys_controller.rb b/app/controllers/user_api_keys_controller.rb index 0a34a33d65f..2c38f7e8fd2 100644 --- a/app/controllers/user_api_keys_controller.rb +++ b/app/controllers/user_api_keys_controller.rb @@ -1,17 +1,15 @@ # frozen_string_literal: true class UserApiKeysController < ApplicationController + layout "no_ember" - layout 'no_ember' - - requires_login only: [:create, :create_otp, :revoke, :undo_revoke] - skip_before_action :redirect_to_login_if_required, only: [:new, :otp] + requires_login only: %i[create create_otp revoke undo_revoke] + skip_before_action :redirect_to_login_if_required, only: %i[new otp] skip_before_action :check_xhr, :preload_json AUTH_API_VERSION ||= 4 def new - if request.head? head :ok, auth_api_version: AUTH_API_VERSION return @@ -24,9 +22,9 @@ class UserApiKeysController < ApplicationController cookies[:destination_url] = request.fullpath if SiteSetting.enable_discourse_connect? - redirect_to path('/session/sso') + redirect_to path("/session/sso") else - redirect_to path('/login') + redirect_to path("/login") end return end @@ -44,13 +42,11 @@ class UserApiKeysController < ApplicationController @push_url = params[:push_url] @localized_scopes = params[:scopes].split(",").map { |s| I18n.t("user_api_key.scopes.#{s}") } @scopes = params[:scopes] - rescue Discourse::InvalidAccess @generic_error = true end def create - require_params if params.key?(:auth_redirect) @@ -66,13 +62,14 @@ class UserApiKeysController < ApplicationController # destroy any old keys we had UserApiKey.where(user_id: current_user.id, client_id: params[:client_id]).destroy_all - key = UserApiKey.create!( - application_name: @application_name, - client_id: params[:client_id], - user_id: current_user.id, - push_url: params[:push_url], - scopes: scopes.map { |name| UserApiKeyScope.new(name: name) } - ) + key = + UserApiKey.create!( + application_name: @application_name, + client_id: params[:client_id], + user_id: current_user.id, + push_url: params[:push_url], + scopes: scopes.map { |name| UserApiKeyScope.new(name: name) }, + ) # we keep the payload short so it encrypts easily with public key # it is often restricted to 128 chars @@ -80,7 +77,7 @@ class UserApiKeysController < ApplicationController key: key.key, nonce: params[:nonce], push: key.has_push?, - api: AUTH_API_VERSION + api: AUTH_API_VERSION, }.to_json public_key = OpenSSL::PKey::RSA.new(params[:public_key]) @@ -94,8 +91,10 @@ class UserApiKeysController < ApplicationController if params[:auth_redirect] uri = URI.parse(params[:auth_redirect]) query_attributes = [uri.query, "payload=#{CGI.escape(@payload)}"] - query_attributes << "oneTimePassword=#{CGI.escape(otp_payload)}" if scopes.include?("one_time_password") - uri.query = query_attributes.compact.join('&') + if scopes.include?("one_time_password") + query_attributes << "oneTimePassword=#{CGI.escape(otp_payload)}" + end + uri.query = query_attributes.compact.join("&") redirect_to(uri.to_s, allow_other_host: true) else @@ -116,9 +115,9 @@ class UserApiKeysController < ApplicationController cookies[:destination_url] = request.fullpath if SiteSetting.enable_discourse_connect? - redirect_to path('/session/sso') + redirect_to path("/session/sso") else - redirect_to path('/login') + redirect_to path("/login") end return end @@ -144,7 +143,7 @@ class UserApiKeysController < ApplicationController def revoke revoke_key = find_key if params[:id] - if current_key = request.env['HTTP_USER_API_KEY'] + if current_key = request.env["HTTP_USER_API_KEY"] request_key = UserApiKey.with_key(current_key).first revoke_key ||= request_key end @@ -168,13 +167,7 @@ class UserApiKeysController < ApplicationController end def require_params - [ - :public_key, - :nonce, - :scopes, - :client_id, - :application_name - ].each { |p| params.require(p) } + %i[public_key nonce scopes client_id application_name].each { |p| params.require(p) } end def validate_params @@ -186,11 +179,7 @@ class UserApiKeysController < ApplicationController end def require_params_otp - [ - :public_key, - :auth_redirect, - :application_name - ].each { |p| params.require(p) } + %i[public_key auth_redirect application_name].each { |p| params.require(p) } end def meets_tl? @@ -198,7 +187,9 @@ class UserApiKeysController < ApplicationController end def one_time_password(public_key, username) - raise Discourse::InvalidAccess unless UserApiKey.allowed_scopes.superset?(Set.new(["one_time_password"])) + unless UserApiKey.allowed_scopes.superset?(Set.new(["one_time_password"])) + raise Discourse::InvalidAccess + end otp = SecureRandom.hex Discourse.redis.setex "otp_#{otp}", 10.minutes, username diff --git a/app/controllers/user_avatars_controller.rb b/app/controllers/user_avatars_controller.rb index 7b1ea6afbf7..abf24083139 100644 --- a/app/controllers/user_avatars_controller.rb +++ b/app/controllers/user_avatars_controller.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true class UserAvatarsController < ApplicationController + skip_before_action :preload_json, + :redirect_to_login_if_required, + :check_xhr, + :verify_authenticity_token, + only: %i[show show_letter show_proxy_letter] - skip_before_action :preload_json, :redirect_to_login_if_required, :check_xhr, :verify_authenticity_token, only: [:show, :show_letter, :show_proxy_letter] - - before_action :apply_cdn_headers, only: [:show, :show_letter, :show_proxy_letter] + before_action :apply_cdn_headers, only: %i[show show_letter show_proxy_letter] def refresh_gravatar user = User.find_by(username_lower: params[:username].downcase) @@ -15,17 +18,16 @@ class UserAvatarsController < ApplicationController user.create_user_avatar(user_id: user.id) unless user.user_avatar user.user_avatar.update_gravatar! - gravatar = if user.user_avatar.gravatar_upload_id - { - gravatar_upload_id: user.user_avatar.gravatar_upload_id, - gravatar_avatar_template: User.avatar_template(user.username, user.user_avatar.gravatar_upload_id) - } - else - { - gravatar_upload_id: nil, - gravatar_avatar_template: nil - } - end + gravatar = + if user.user_avatar.gravatar_upload_id + { + gravatar_upload_id: user.user_avatar.gravatar_upload_id, + gravatar_avatar_template: + User.avatar_template(user.username, user.user_avatar.gravatar_upload_id), + } + else + { gravatar_upload_id: nil, gravatar_avatar_template: nil } + end render json: gravatar end @@ -37,7 +39,7 @@ class UserAvatarsController < ApplicationController def show_proxy_letter is_asset_path - if SiteSetting.external_system_avatars_url !~ /^\/letter_avatar_proxy/ + if SiteSetting.external_system_avatars_url !~ %r{^/letter_avatar_proxy} raise Discourse::NotFound end @@ -48,7 +50,10 @@ class UserAvatarsController < ApplicationController hijack do begin - proxy_avatar("https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png", Time.new(1990, 01, 01)) + proxy_avatar( + "https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png", + Time.new(1990, 01, 01), + ) rescue OpenURI::HTTPError render_blank end @@ -81,16 +86,13 @@ class UserAvatarsController < ApplicationController # we need multisite support to keep a single origin pull for CDNs RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do - hijack do - show_in_site(params[:hostname]) - end + hijack { show_in_site(params[:hostname]) } end end protected def show_in_site(hostname) - username = params[:username].to_s return render_blank unless user = User.find_by(username_lower: username.downcase) @@ -99,9 +101,7 @@ class UserAvatarsController < ApplicationController version = (version || OptimizedImage::VERSION).to_i # old versions simply get new avatar - if version > OptimizedImage::VERSION - return render_blank - end + return render_blank if version > OptimizedImage::VERSION upload_id = upload_id.to_i return render_blank unless upload_id > 0 @@ -111,7 +111,13 @@ class UserAvatarsController < ApplicationController if !Discourse.avatar_sizes.include?(size) && Discourse.store.external? closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs } - avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), upload_id, closest) + avatar_url = + UserAvatar.local_avatar_url( + hostname, + user.encoded_username(lower: true), + upload_id, + closest, + ) return redirect_to cdn_path(avatar_url), allow_other_host: true end @@ -119,7 +125,13 @@ class UserAvatarsController < ApplicationController upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id if user.uploaded_avatar && !upload - avatar_url = UserAvatar.local_avatar_url(hostname, user.encoded_username(lower: true), user.uploaded_avatar_id, size) + avatar_url = + UserAvatar.local_avatar_url( + hostname, + user.encoded_username(lower: true), + user.uploaded_avatar_id, + size, + ) return redirect_to cdn_path(avatar_url), allow_other_host: true elsif upload && optimized = get_optimized_image(upload, size) if optimized.local? @@ -151,10 +163,7 @@ class UserAvatarsController < ApplicationController PROXY_PATH = Rails.root + "tmp/avatar_proxy" def proxy_avatar(url, last_modified) - - if url[0..1] == "//" - url = (SiteSetting.force_https ? "https:" : "http:") + url - end + url = (SiteSetting.force_https ? "https:" : "http:") + url if url[0..1] == "//" sha = Digest::SHA1.hexdigest(url) filename = "#{sha}#{File.extname(url)}" @@ -162,13 +171,14 @@ class UserAvatarsController < ApplicationController unless File.exist? path FileUtils.mkdir_p PROXY_PATH - tmp = FileHelper.download( - url, - max_file_size: max_file_size, - tmp_file_name: filename, - follow_redirect: true, - read_timeout: 10 - ) + tmp = + FileHelper.download( + url, + max_file_size: max_file_size, + tmp_file_name: filename, + follow_redirect: true, + read_timeout: 10, + ) return render_blank if tmp.nil? @@ -206,5 +216,4 @@ class UserAvatarsController < ApplicationController upload.get_optimized_image(size, size) # TODO decide if we want to detach here end - end diff --git a/app/controllers/user_badges_controller.rb b/app/controllers/user_badges_controller.rb index d87504d9a28..1617f55516d 100644 --- a/app/controllers/user_badges_controller.rb +++ b/app/controllers/user_badges_controller.rb @@ -6,11 +6,18 @@ class UserBadgesController < ApplicationController before_action :ensure_badges_enabled def index - params.permit [:granted_before, :offset, :username] + params.permit %i[granted_before offset username] badge = fetch_badge_from_params - user_badges = badge.user_badges.order('granted_at DESC, id DESC').limit(MAX_BADGES) - user_badges = user_badges.includes(:user, :granted_by, badge: :badge_type, post: :topic, user: [:primary_group, :flair_group]) + user_badges = badge.user_badges.order("granted_at DESC, id DESC").limit(MAX_BADGES) + user_badges = + user_badges.includes( + :user, + :granted_by, + badge: :badge_type, + post: :topic, + user: %i[primary_group flair_group], + ) grant_count = nil @@ -26,33 +33,40 @@ class UserBadgesController < ApplicationController user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact - user_badges = UserBadges.new(user_badges: user_badges, - username: params[:username], - grant_count: grant_count) + user_badges = + UserBadges.new( + user_badges: user_badges, + username: params[:username], + grant_count: grant_count, + ) render_serialized( user_badges, UserBadgesSerializer, root: :user_badge_info, include_long_description: true, - allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids) + allowed_user_badge_topic_ids: guardian.can_see_topic_ids(topic_ids: user_badges_topic_ids), ) end def username params.permit [:grouped] - user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + user = + fetch_user_from_params( + include_inactive: + current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts), + ) raise Discourse::NotFound unless guardian.can_see_profile?(user) user_badges = user.user_badges - if params[:grouped] - user_badges = user_badges.group(:badge_id).select_for_grouping - end + user_badges = user_badges.group(:badge_id).select_for_grouping if params[:grouped] - user_badges = user_badges.includes(badge: [:badge_grouping, :badge_type, :image_upload]) - .includes(post: :topic) - .includes(:granted_by) + user_badges = + user_badges + .includes(badge: %i[badge_grouping badge_type image_upload]) + .includes(post: :topic) + .includes(:granted_by) user_badges_topic_ids = user_badges.map { |user_badge| user_badge.post&.topic_id }.compact @@ -68,16 +82,17 @@ class UserBadgesController < ApplicationController params.require(:username) user = fetch_user_from_params - unless can_assign_badge_to_user?(user) - return render json: failed_json, status: 403 - end + return render json: failed_json, status: 403 unless can_assign_badge_to_user?(user) badge = fetch_badge_from_params post_id = nil if params[:reason].present? unless is_badge_reason_valid? params[:reason] - return render json: failed_json.merge(message: I18n.t('invalid_grant_badge_reason_link')), status: 400 + return( + render json: failed_json.merge(message: I18n.t("invalid_grant_badge_reason_link")), + status: 400 + ) end if route = Discourse.route_for(params[:reason]) @@ -112,17 +127,17 @@ class UserBadgesController < ApplicationController user_badge = UserBadge.find(params[:user_badge_id]) user_badges = user_badge.user.user_badges - unless can_favorite_badge?(user_badge) - return render json: failed_json, status: 403 - end + return render json: failed_json, status: 403 unless can_favorite_badge?(user_badge) - if !user_badge.is_favorite && user_badges.select(:badge_id).distinct.where(is_favorite: true).count >= SiteSetting.max_favorite_badges + if !user_badge.is_favorite && + user_badges.select(:badge_id).distinct.where(is_favorite: true).count >= + SiteSetting.max_favorite_badges return render json: failed_json, status: 400 end - UserBadge - .where(user_id: user_badge.user_id, badge_id: user_badge.badge_id) - .update_all(is_favorite: !user_badge.is_favorite) + UserBadge.where(user_id: user_badge.user_id, badge_id: user_badge.badge_id).update_all( + is_favorite: !user_badge.is_favorite, + ) UserBadge.update_featured_ranks!(user_badge.user_id) end @@ -159,6 +174,6 @@ class UserBadgesController < ApplicationController def is_badge_reason_valid?(reason) route = Discourse.route_for(reason) - route && (route[:controller] == 'posts' || route[:controller] == 'topics') + route && (route[:controller] == "posts" || route[:controller] == "topics") end end diff --git a/app/controllers/users/associate_accounts_controller.rb b/app/controllers/users/associate_accounts_controller.rb index 9f12727b944..371cfbf58c4 100644 --- a/app/controllers/users/associate_accounts_controller.rb +++ b/app/controllers/users/associate_accounts_controller.rb @@ -9,11 +9,11 @@ class Users::AssociateAccountsController < ApplicationController account_description = authenticator.description_for_auth_hash(auth_hash) existing_account_description = authenticator.description_for_user(current_user).presence render json: { - token: params[:token], - provider_name: auth_hash.provider, - account_description: account_description, - existing_account_description: existing_account_description - } + token: params[:token], + provider_name: auth_hash.provider, + account_description: account_description, + existing_account_description: existing_account_description, + } end def connect @@ -33,20 +33,23 @@ class Users::AssociateAccountsController < ApplicationController private def auth_hash - @auth_hash ||= begin - token = params[:token] - json = secure_session[self.class.key(token)] - raise Discourse::NotFound if json.nil? + @auth_hash ||= + begin + token = params[:token] + json = secure_session[self.class.key(token)] + raise Discourse::NotFound if json.nil? - OmniAuth::AuthHash.new(JSON.parse(json)) - end + OmniAuth::AuthHash.new(JSON.parse(json)) + end end def authenticator provider_name = auth_hash.provider authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name } - raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) if authenticator.nil? - raise Discourse::InvalidAccess.new(I18n.t('authenticator_no_connect')) if !authenticator.can_connect_existing_user? + raise Discourse::InvalidAccess.new(I18n.t("authenticator_not_found")) if authenticator.nil? + if !authenticator.can_connect_existing_user? + raise Discourse::InvalidAccess.new(I18n.t("authenticator_no_connect")) + end authenticator end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 3634d2f4c0d..673db129c48 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -2,10 +2,9 @@ # frozen_string_literal: true class Users::OmniauthCallbacksController < ApplicationController - skip_before_action :redirect_to_login_if_required - layout 'no_ember' + layout "no_ember" # need to be able to call this skip_before_action :check_xhr @@ -40,7 +39,7 @@ class Users::OmniauthCallbacksController < ApplicationController DiscourseEvent.trigger(:after_auth, authenticator, @auth_result, session, cookies, request) end - preferred_origin = request.env['omniauth.origin'] + preferred_origin = request.env["omniauth.origin"] if session[:destination_url].present? preferred_origin = session[:destination_url] @@ -53,10 +52,11 @@ class Users::OmniauthCallbacksController < ApplicationController end if preferred_origin.present? - parsed = begin - URI.parse(preferred_origin) - rescue URI::Error - end + parsed = + begin + URI.parse(preferred_origin) + rescue URI::Error + end if valid_origin?(parsed) @origin = +"#{parsed.path}" @@ -64,9 +64,7 @@ class Users::OmniauthCallbacksController < ApplicationController end end - if @origin.blank? - @origin = Discourse.base_path("/") - end + @origin = Discourse.base_path("/") if @origin.blank? @auth_result.destination_url = @origin @auth_result.authenticator_name = authenticator.name @@ -81,16 +79,13 @@ class Users::OmniauthCallbacksController < ApplicationController client_hash = @auth_result.to_client_hash if authenticator.can_connect_existing_user? && - (SiteSetting.enable_local_logins || Discourse.enabled_authenticators.count > 1) + (SiteSetting.enable_local_logins || Discourse.enabled_authenticators.count > 1) # There is more than one login method, and users are allowed to manage associations themselves client_hash[:associate_url] = persist_auth_token(auth) end - cookies['_bypass_cache'] = true - cookies[:authentication_data] = { - value: client_hash.to_json, - path: Discourse.base_path("/") - } + cookies["_bypass_cache"] = true + cookies[:authentication_data] = { value: client_hash.to_json, path: Discourse.base_path("/") } redirect_to @origin end @@ -108,24 +103,24 @@ class Users::OmniauthCallbacksController < ApplicationController flash[:error] = I18n.t( "login.omniauth_error.#{error_key}", - default: I18n.t("login.omniauth_error.generic") + default: I18n.t("login.omniauth_error.generic"), ).html_safe - render 'failure' + render "failure" end def self.find_authenticator(name) Discourse.enabled_authenticators.each do |authenticator| return authenticator if authenticator.name == name end - raise Discourse::InvalidAccess.new(I18n.t('authenticator_not_found')) + raise Discourse::InvalidAccess.new(I18n.t("authenticator_not_found")) end protected def render_auth_result_failure flash[:error] = @auth_result.failed_reason.html_safe - render 'failure' + render "failure" end def complete_response_data @@ -160,13 +155,16 @@ class Users::OmniauthCallbacksController < ApplicationController user.update!(password: SecureRandom.hex) # Ensure there is an active email token - if !EmailToken.where(email: user.email, confirmed: true).exists? && !user.email_tokens.active.where(email: user.email).exists? + if !EmailToken.where(email: user.email, confirmed: true).exists? && + !user.email_tokens.active.where(email: user.email).exists? user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) end user.activate end - user.update!(registration_ip_address: request.remote_ip) if user.registration_ip_address.blank? + if user.registration_ip_address.blank? + user.update!(registration_ip_address: request.remote_ip) + end end if ScreenedIpAddress.should_block?(request.remote_ip) @@ -198,7 +196,9 @@ class Users::OmniauthCallbacksController < ApplicationController def persist_auth_token(auth) secret = SecureRandom.hex - secure_session.set "#{Users::AssociateAccountsController.key(secret)}", auth.to_json, expires: 10.minutes + secure_session.set "#{Users::AssociateAccountsController.key(secret)}", + auth.to_json, + expires: 10.minutes "#{Discourse.base_path}/associate/#{secret}" end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index f7debba7844..23032ada602 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,29 +3,72 @@ class UsersController < ApplicationController skip_before_action :authorize_mini_profiler, only: [:avatar] - requires_login only: [ - :username, :update, :upload_user_image, - :pick_avatar, :destroy_user_image, :destroy, :check_emails, - :topic_tracking_state, :preferences, :create_second_factor_totp, - :enable_second_factor_totp, :disable_second_factor, :list_second_factors, - :update_second_factor, :create_second_factor_backup, :select_avatar, - :notification_level, :revoke_auth_token, :register_second_factor_security_key, - :create_second_factor_security_key, :feature_topic, :clear_featured_topic, - :bookmarks, :invited, :check_sso_email, :check_sso_payload, - :recent_searches, :reset_recent_searches, :user_menu_bookmarks, :user_menu_messages - ] + requires_login only: %i[ + username + update + upload_user_image + pick_avatar + destroy_user_image + destroy + check_emails + topic_tracking_state + preferences + create_second_factor_totp + enable_second_factor_totp + disable_second_factor + list_second_factors + update_second_factor + create_second_factor_backup + select_avatar + notification_level + revoke_auth_token + register_second_factor_security_key + create_second_factor_security_key + feature_topic + clear_featured_topic + bookmarks + invited + check_sso_email + check_sso_payload + recent_searches + reset_recent_searches + user_menu_bookmarks + user_menu_messages + ] - skip_before_action :check_xhr, only: [ - :show, :badges, :password_reset_show, :password_reset_update, :update, :account_created, - :activate_account, :perform_account_activation, :avatar, - :my_redirect, :toggle_anon, :admin_login, :confirm_admin, :email_login, :summary, - :feature_topic, :clear_featured_topic, :bookmarks, :user_menu_bookmarks, :user_menu_messages - ] + skip_before_action :check_xhr, + only: %i[ + show + badges + password_reset_show + password_reset_update + update + account_created + activate_account + perform_account_activation + avatar + my_redirect + toggle_anon + admin_login + confirm_admin + email_login + summary + feature_topic + clear_featured_topic + bookmarks + user_menu_bookmarks + user_menu_messages + ] - before_action :second_factor_check_confirmed_password, only: [ - :create_second_factor_totp, :enable_second_factor_totp, - :disable_second_factor, :update_second_factor, :create_second_factor_backup, - :register_second_factor_security_key, :create_second_factor_security_key + before_action :second_factor_check_confirmed_password, + only: %i[ + create_second_factor_totp + enable_second_factor_totp + disable_second_factor + update_second_factor + create_second_factor_backup + register_second_factor_security_key + create_second_factor_security_key ] before_action :respond_to_suspicious_request, only: [:create] @@ -34,22 +77,25 @@ class UsersController < ApplicationController # page is going to be empty, this means that server will see an invalid CSRF and blow the session # once that happens you can't log in with social skip_before_action :verify_authenticity_token, only: [:create] - skip_before_action :redirect_to_login_if_required, only: [:check_username, - :check_email, - :create, - :account_created, - :activate_account, - :perform_account_activation, - :send_activation_email, - :update_activation_email, - :password_reset_show, - :password_reset_update, - :confirm_email_token, - :email_login, - :admin_login, - :confirm_admin] + skip_before_action :redirect_to_login_if_required, + only: %i[ + check_username + check_email + create + account_created + activate_account + perform_account_activation + send_activation_email + update_activation_email + password_reset_show + password_reset_update + confirm_email_token + email_login + admin_login + confirm_admin + ] - after_action :add_noindex_header, only: [:show, :my_redirect] + after_action :add_noindex_header, only: %i[show my_redirect] allow_in_staff_writes_only_mode :admin_login allow_in_staff_writes_only_mode :email_login @@ -60,32 +106,34 @@ class UsersController < ApplicationController end def show(for_card: false) - return redirect_to path('/login') if SiteSetting.hide_user_profiles_from_public && !current_user + return redirect_to path("/login") if SiteSetting.hide_user_profiles_from_public && !current_user - @user = fetch_user_from_params( - include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts) - ) + @user = + fetch_user_from_params( + include_inactive: + current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts), + ) user_serializer = nil if guardian.can_see_profile?(@user) serializer_class = for_card ? UserCardSerializer : UserSerializer - user_serializer = serializer_class.new(@user, scope: guardian, root: 'user') + user_serializer = serializer_class.new(@user, scope: guardian, root: "user") topic_id = params[:include_post_count_for].to_i if topic_id != 0 && guardian.can_see?(Topic.find_by_id(topic_id)) - user_serializer.topic_post_count = { topic_id => Post.secured(guardian).where(topic_id: topic_id, user_id: @user.id).count } + user_serializer.topic_post_count = { + topic_id => Post.secured(guardian).where(topic_id: topic_id, user_id: @user.id).count, + } end else - user_serializer = HiddenProfileSerializer.new(@user, scope: guardian, root: 'user') + user_serializer = HiddenProfileSerializer.new(@user, scope: guardian, root: "user") end - if !params[:skip_track_visit] && (@user != current_user) - track_visit_to_user_profile - end + track_visit_to_user_profile if !params[:skip_track_visit] && (@user != current_user) # This is a hack to get around a Rails issue where values with periods aren't handled correctly # when used as part of a route. - if params[:external_id] && params[:external_id].ends_with?('.json') + if params[:external_id] && params[:external_id].ends_with?(".json") return render_json_dump(user_serializer) end @@ -96,9 +144,7 @@ class UsersController < ApplicationController render :show end - format.json do - render_json_dump(user_serializer) - end + format.json { render_json_dump(user_serializer) } end end @@ -108,25 +154,29 @@ class UsersController < ApplicationController # This route is not used in core, but is used by theme components (e.g. https://meta.discourse.org/t/144479) def cards - return redirect_to path('/login') if SiteSetting.hide_user_profiles_from_public && !current_user + return redirect_to path("/login") if SiteSetting.hide_user_profiles_from_public && !current_user user_ids = params.require(:user_ids).split(",").map(&:to_i) raise Discourse::InvalidParameters.new(:user_ids) if user_ids.length > 50 - users = User.where(id: user_ids).includes(:user_option, - :user_stat, - :default_featured_user_badges, - :user_profile, - :card_background_upload, - :primary_group, - :flair_group, - :primary_email, - :user_status - ) + users = + User.where(id: user_ids).includes( + :user_option, + :user_stat, + :default_featured_user_badges, + :user_profile, + :card_background_upload, + :primary_group, + :flair_group, + :primary_email, + :user_status, + ) users = users.filter { |u| guardian.can_see_profile?(u) } - preload_fields = User.allowed_user_custom_fields(guardian) + UserField.all.pluck(:id).map { |fid| "#{User::USER_FIELD_PREFIX}#{fid}" } + preload_fields = + User.allowed_user_custom_fields(guardian) + + UserField.all.pluck(:id).map { |fid| "#{User::USER_FIELD_PREFIX}#{fid}" } User.preload_custom_fields(users, preload_fields) User.preload_recent_time_read(users) @@ -159,7 +209,9 @@ class UsersController < ApplicationController value = nil if value === "false" value = value[0...UserField.max_length] if value - return render_json_error(I18n.t("login.missing_user_field")) if value.blank? && field.required? + if value.blank? && field.required? + return render_json_error(I18n.t("login.missing_user_field")) + end attributes[:custom_fields]["#{User::USER_FIELD_PREFIX}#{field.id}"] = value end end @@ -168,8 +220,10 @@ class UsersController < ApplicationController attributes[:user_associated_accounts] = [] params[:external_ids].each do |provider_name, provider_uid| - if provider_name == 'discourse_connect' - raise Discourse::InvalidParameters.new(:external_ids) unless SiteSetting.enable_discourse_connect + if provider_name == "discourse_connect" + unless SiteSetting.enable_discourse_connect + raise Discourse::InvalidParameters.new(:external_ids) + end attributes[:discourse_connect] = { external_id: provider_uid } @@ -179,11 +233,18 @@ class UsersController < ApplicationController authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name } raise Discourse::InvalidParameters.new(:external_ids) if !authenticator&.is_managed? - attributes[:user_associated_accounts] << { provider_name: provider_name, provider_uid: provider_uid } + attributes[:user_associated_accounts] << { + provider_name: provider_name, + provider_uid: provider_uid, + } end end - json_result(user, serializer: UserSerializer, additional_errors: [:user_profile, :user_option]) do |u| + json_result( + user, + serializer: UserSerializer, + additional_errors: %i[user_profile user_option], + ) do |u| updater = UserUpdater.new(current_user, user) updater.update(attributes.permit!) end @@ -192,7 +253,8 @@ class UsersController < ApplicationController def username params.require(:new_username) - if clashing_with_existing_route?(params[:new_username]) || User.reserved_username?(params[:new_username]) + if clashing_with_existing_route?(params[:new_username]) || + User.reserved_username?(params[:new_username]) return render_json_error(I18n.t("login.reserved_username")) end @@ -204,11 +266,11 @@ class UsersController < ApplicationController if result render json: { id: user.id, username: user.username } else - render_json_error(user.errors.full_messages.join(',')) + render_json_error(user.errors.full_messages.join(",")) end rescue Discourse::InvalidAccess if current_user&.staff? - render_json_error(I18n.t('errors.messages.auth_overrides_username')) + render_json_error(I18n.t("errors.messages.auth_overrides_username")) else render json: failed_json, status: 403 end @@ -226,11 +288,11 @@ class UsersController < ApplicationController unconfirmed_emails = user.unconfirmed_emails render json: { - email: email, - secondary_emails: secondary_emails, - unconfirmed_emails: unconfirmed_emails, - associated_accounts: user.associated_accounts - } + email: email, + secondary_emails: secondary_emails, + unconfirmed_emails: unconfirmed_emails, + associated_accounts: user.associated_accounts, + } rescue Discourse::InvalidAccess render json: failed_json, status: 403 end @@ -268,9 +330,7 @@ class UsersController < ApplicationController end def update_primary_email - if !SiteSetting.enable_secondary_emails - return render json: failed_json, status: 410 - end + return render json: failed_json, status: 410 if !SiteSetting.enable_secondary_emails params.require(:email) @@ -278,13 +338,13 @@ class UsersController < ApplicationController guardian.ensure_can_edit_email!(user) old_primary = user.primary_email - if old_primary.email == params[:email] - return render json: success_json - end + return render json: success_json if old_primary.email == params[:email] new_primary = user.user_emails.find_by(email: params[:email]) if new_primary.blank? - return render json: failed_json.merge(errors: [I18n.t("change_email.doesnt_exist")]), status: 428 + return( + render json: failed_json.merge(errors: [I18n.t("change_email.doesnt_exist")]), status: 428 + ) end User.transaction do @@ -303,9 +363,7 @@ class UsersController < ApplicationController end def destroy_email - if !SiteSetting.enable_secondary_emails - return render json: failed_json, status: 410 - end + return render json: failed_json, status: 410 if !SiteSetting.enable_secondary_emails params.require(:email) @@ -336,9 +394,12 @@ class UsersController < ApplicationController guardian.ensure_can_edit!(user) report = TopicTrackingState.report(user) - serializer = ActiveModel::ArraySerializer.new( - report, each_serializer: TopicTrackingStateSerializer, scope: guardian - ) + serializer = + ActiveModel::ArraySerializer.new( + report, + each_serializer: TopicTrackingStateSerializer, + scope: guardian, + ) render json: MultiJson.dump(serializer) end @@ -349,11 +410,12 @@ class UsersController < ApplicationController report = PrivateMessageTopicTrackingState.report(user) - serializer = ActiveModel::ArraySerializer.new( - report, - each_serializer: PrivateMessageTopicTrackingStateSerializer, - scope: guardian - ) + serializer = + ActiveModel::ArraySerializer.new( + report, + each_serializer: PrivateMessageTopicTrackingStateSerializer, + scope: guardian, + ) render json: MultiJson.dump(serializer) end @@ -373,28 +435,33 @@ class UsersController < ApplicationController log_params = { details: "title matching badge id #{user_badge.badge.id}", previous_value: previous_title, - new_value: user.title + new_value: user.title, } if current_user.staff? && current_user != user StaffActionLogger.new(current_user).log_title_change(user, log_params) else - UserHistory.create!(log_params.merge(target_user_id: user.id, action: UserHistory.actions[:change_title])) + UserHistory.create!( + log_params.merge(target_user_id: user.id, action: UserHistory.actions[:change_title]), + ) end else - user.title = '' + user.title = "" user.save! - log_params = { - previous_value: previous_title - } + log_params = { previous_value: previous_title } if current_user.staff? && current_user != user - StaffActionLogger - .new(current_user) - .log_title_revoke(user, log_params.merge(revoke_reason: 'user title was same as revoked badge name or custom badge name')) + StaffActionLogger.new(current_user).log_title_revoke( + user, + log_params.merge( + revoke_reason: "user title was same as revoked badge name or custom badge name", + ), + ) else - UserHistory.create!(log_params.merge(target_user_id: user.id, action: UserHistory.actions[:revoke_title])) + UserHistory.create!( + log_params.merge(target_user_id: user.id, action: UserHistory.actions[:revoke_title]), + ) end end @@ -406,7 +473,7 @@ class UsersController < ApplicationController end def my_redirect - raise Discourse::NotFound if params[:path] !~ /^[a-z_\-\/]+$/ + raise Discourse::NotFound if params[:path] !~ %r{^[a-z_\-/]+$} if current_user.blank? cookies[:destination_url] = path("/my/#{params[:path]}") @@ -421,10 +488,14 @@ class UsersController < ApplicationController end def summary - @user = fetch_user_from_params(include_inactive: current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts)) + @user = + fetch_user_from_params( + include_inactive: + current_user.try(:staff?) || (current_user && SiteSetting.show_inactive_accounts), + ) raise Discourse::NotFound unless guardian.can_see_profile?(@user) - response.headers['X-Robots-Tag'] = 'noindex' + response.headers["X-Robots-Tag"] = "noindex" respond_to do |format| format.html do @@ -432,11 +503,14 @@ class UsersController < ApplicationController render :show end format.json do - summary_json = Discourse.cache.fetch(summary_cache_key(@user), expires_in: 1.hour) do - summary = UserSummary.new(@user, guardian) - serializer = UserSummarySerializer.new(summary, scope: guardian) - MultiJson.dump(serializer) - end + summary_json = + Discourse + .cache + .fetch(summary_cache_key(@user), expires_in: 1.hour) do + summary = UserSummary.new(@user, guardian) + serializer = UserSummarySerializer.new(summary, scope: guardian) + MultiJson.dump(serializer) + end render json: summary_json end end @@ -445,24 +519,29 @@ class UsersController < ApplicationController def invited if guardian.can_invite_to_forum? filter = params[:filter] || "redeemed" - inviter = fetch_user_from_params(include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts) + inviter = + fetch_user_from_params( + include_inactive: current_user.staff? || SiteSetting.show_inactive_accounts, + ) - invites = if filter == "pending" && guardian.can_see_invite_details?(inviter) - Invite.includes(:topics, :groups).pending(inviter) - elsif filter == "expired" - Invite.expired(inviter) - elsif filter == "redeemed" - Invite.redeemed_users(inviter) - else - Invite.none - end + invites = + if filter == "pending" && guardian.can_see_invite_details?(inviter) + Invite.includes(:topics, :groups).pending(inviter) + elsif filter == "expired" + Invite.expired(inviter) + elsif filter == "redeemed" + Invite.redeemed_users(inviter) + else + Invite.none + end invites = invites.offset(params[:offset].to_i || 0).limit(SiteSetting.invites_per_page) show_emails = guardian.can_see_invite_emails?(inviter) if params[:search].present? && invites.present? - filter_sql = '(LOWER(users.username) LIKE :filter)' - filter_sql = '(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)' if show_emails + filter_sql = "(LOWER(users.username) LIKE :filter)" + filter_sql = + "(LOWER(invites.email) LIKE :filter) or (LOWER(users.username) LIKE :filter)" if show_emails invites = invites.where(filter_sql, filter: "%#{params[:search].downcase}%") end @@ -470,26 +549,30 @@ class UsersController < ApplicationController expired_count = Invite.expired(inviter).reorder(nil).count.to_i redeemed_count = Invite.redeemed_users(inviter).reorder(nil).count.to_i - render json: MultiJson.dump(InvitedSerializer.new( - OpenStruct.new( - invite_list: invites.to_a, - show_emails: show_emails, - inviter: inviter, - type: filter, - counts: { - pending: pending_count, - expired: expired_count, - redeemed: redeemed_count, - total: pending_count + expired_count - } - ), - scope: guardian, - root: false - )) + render json: + MultiJson.dump( + InvitedSerializer.new( + OpenStruct.new( + invite_list: invites.to_a, + show_emails: show_emails, + inviter: inviter, + type: filter, + counts: { + pending: pending_count, + expired: expired_count, + redeemed: redeemed_count, + total: pending_count + expired_count, + }, + ), + scope: guardian, + root: false, + ), + ) elsif current_user&.staff? - message = if SiteSetting.enable_discourse_connect - I18n.t("invite.disabled_errors.discourse_connect_enabled") - end + message = + if SiteSetting.enable_discourse_connect + I18n.t("invite.disabled_errors.discourse_connect_enabled") + end render_invite_error(message) else @@ -533,27 +616,25 @@ class UsersController < ApplicationController email = Email.downcase((params[:email] || "").strip) - if email.blank? || SiteSetting.hide_email_address_taken? - return render json: success_json - end + return render json: success_json if email.blank? || SiteSetting.hide_email_address_taken? if !EmailAddressValidator.valid_value?(email) - error = User.new.errors.full_message(:email, I18n.t(:'user.email.invalid')) + error = User.new.errors.full_message(:email, I18n.t(:"user.email.invalid")) return render json: failed_json.merge(errors: [error]) end if !EmailValidator.allowed?(email) - error = User.new.errors.full_message(:email, I18n.t(:'user.email.not_allowed')) + error = User.new.errors.full_message(:email, I18n.t(:"user.email.not_allowed")) return render json: failed_json.merge(errors: [error]) end if ScreenedEmail.should_block?(email) - error = User.new.errors.full_message(:email, I18n.t(:'user.email.blocked')) + error = User.new.errors.full_message(:email, I18n.t(:"user.email.blocked")) return render json: failed_json.merge(errors: [error]) end if User.where(staged: false).find_by_email(email).present? - error = User.new.errors.full_message(:email, I18n.t(:'errors.messages.taken')) + error = User.new.errors.full_message(:email, I18n.t(:"errors.messages.taken")) return render json: failed_json.merge(errors: [error]) end @@ -571,23 +652,21 @@ class UsersController < ApplicationController params.permit(:user_fields) params.permit(:external_ids) - unless SiteSetting.allow_new_registrations - return fail_with("login.new_registrations_disabled") - end + return fail_with("login.new_registrations_disabled") unless SiteSetting.allow_new_registrations if params[:password] && params[:password].length > User.max_password_length return fail_with("login.password_too_long") end - if params[:email].length > 254 + 1 + 253 - return fail_with("login.email_too_long") - end + return fail_with("login.email_too_long") if params[:email].length > 254 + 1 + 253 - if SiteSetting.require_invite_code && SiteSetting.invite_code.strip.downcase != params[:invite_code].strip.downcase + if SiteSetting.require_invite_code && + SiteSetting.invite_code.strip.downcase != params[:invite_code].strip.downcase return fail_with("login.wrong_invite_code") end - if clashing_with_existing_route?(params[:username]) || User.reserved_username?(params[:username]) + if clashing_with_existing_route?(params[:username]) || + User.reserved_username?(params[:username]) return fail_with("login.reserved_username") end @@ -636,14 +715,19 @@ class UsersController < ApplicationController authenticator = Discourse.enabled_authenticators.find { |a| a.name == provider_name } raise Discourse::InvalidParameters.new(:external_ids) if !authenticator&.is_managed? - association = UserAssociatedAccount.find_or_initialize_by(provider_name: provider_name, provider_uid: provider_uid) + association = + UserAssociatedAccount.find_or_initialize_by( + provider_name: provider_name, + provider_uid: provider_uid, + ) associations << association end end authentication = UserAuthenticator.new(user, session) - if !authentication.has_authenticator? && !SiteSetting.enable_local_logins && !(current_user&.admin? && is_api?) + if !authentication.has_authenticator? && !SiteSetting.enable_local_logins && + !(current_user&.admin? && is_api?) return render body: nil, status: :forbidden end @@ -651,7 +735,7 @@ class UsersController < ApplicationController if authentication.email_valid? && !authentication.authenticated? # posted email is different that the already validated one? - return fail_with('login.incorrect_username_email_or_password') + return fail_with("login.incorrect_username_email_or_password") end activation = UserActivator.new(user, request, session, cookies) @@ -659,7 +743,8 @@ class UsersController < ApplicationController # just assign a password if we have an authenticator and no password # this is the case for Twitter - user.password = SecureRandom.hex if user.password.blank? && (authentication.has_authenticator? || associations.present?) + user.password = SecureRandom.hex if user.password.blank? && + (authentication.has_authenticator? || associations.present?) if user.save authentication.finish @@ -679,47 +764,36 @@ class UsersController < ApplicationController # add them to the review queue if they need to be approved user.activate if user.active? - render json: { - success: true, - active: user.active?, - message: activation.message, - }.merge(SiteSetting.hide_email_address_taken ? {} : { user_id: user.id }) - elsif SiteSetting.hide_email_address_taken && user.errors[:primary_email]&.include?(I18n.t('errors.messages.taken')) + render json: { success: true, active: user.active?, message: activation.message }.merge( + SiteSetting.hide_email_address_taken ? {} : { user_id: user.id }, + ) + elsif SiteSetting.hide_email_address_taken && + user.errors[:primary_email]&.include?(I18n.t("errors.messages.taken")) session["user_created_message"] = activation.success_message if existing_user = User.find_by_email(user.primary_email&.email) Jobs.enqueue(:critical_user_email, type: "account_exists", user_id: existing_user.id) end - render json: { - success: true, - active: false, - message: activation.success_message - } + render json: { success: true, active: false, message: activation.success_message } else errors = user.errors.to_hash errors[:email] = errors.delete(:primary_email) if errors[:primary_email] render json: { - success: false, - message: I18n.t( - 'login.errors', - errors: user.errors.full_messages.join("\n") - ), - errors: errors, - values: { - name: user.name, - username: user.username, - email: user.primary_email&.email - }, - is_developer: UsernameCheckerService.is_developer?(user.email) - } + success: false, + message: I18n.t("login.errors", errors: user.errors.full_messages.join("\n")), + errors: errors, + values: { + name: user.name, + username: user.username, + email: user.primary_email&.email, + }, + is_developer: UsernameCheckerService.is_developer?(user.email), + } end rescue ActiveRecord::StatementInvalid - render json: { - success: false, - message: I18n.t("login.something_already_taken") - } + render json: { success: false, message: I18n.t("login.something_already_taken") } end def password_reset_show @@ -735,21 +809,23 @@ class UsersController < ApplicationController second_factor_required: @user.totp_enabled?, security_key_required: @user.security_keys_enabled?, backup_enabled: @user.backup_codes_enabled?, - multiple_second_factor_methods: @user.has_multiple_second_factor_methods? + multiple_second_factor_methods: @user.has_multiple_second_factor_methods?, } end respond_to do |format| format.html do - return render 'password_reset', layout: 'no_ember' if @error + return render "password_reset", layout: "no_ember" if @error Webauthn.stage_challenge(@user, secure_session) store_preloaded( "password_reset", - MultiJson.dump(security_params.merge(Webauthn.allowed_credentials(@user, secure_session))) + MultiJson.dump( + security_params.merge(Webauthn.allowed_credentials(@user, secure_session)), + ), ) - render 'password_reset' + render "password_reset" end format.json do @@ -772,13 +848,20 @@ class UsersController < ApplicationController # a user from the token if @user if !secure_session["second-factor-#{token}"] - second_factor_authentication_result = @user.authenticate_second_factor(params, secure_session) + second_factor_authentication_result = + @user.authenticate_second_factor(params, secure_session) if !second_factor_authentication_result.ok - user_error_key = second_factor_authentication_result.reason == "invalid_security_key" ? :user_second_factors : :security_keys + user_error_key = + ( + if second_factor_authentication_result.reason == "invalid_security_key" + :user_second_factors + else + :security_keys + end + ) @user.errors.add(user_error_key, :invalid) @error = second_factor_authentication_result.error else - # this must be set because the first call we authenticate e.g. TOTP, and we do # not want to re-authenticate on the second call to change the password as this # will cause a TOTP error saying the code has already been used @@ -786,7 +869,8 @@ class UsersController < ApplicationController end end - if @invalid_password = params[:password].blank? || params[:password].size > User.max_password_length + if @invalid_password = + params[:password].blank? || params[:password].size > User.max_password_length @user.errors.add(:password, :invalid) end @@ -804,7 +888,7 @@ class UsersController < ApplicationController UserHistory.create!( target_user: @user, acting_user: @user, - action: UserHistory.actions[:change_password] + action: UserHistory.actions[:change_password], ) logon_after_password_reset end @@ -813,7 +897,7 @@ class UsersController < ApplicationController respond_to do |format| format.html do - return render 'password_reset', layout: 'no_ember' if @error + return render "password_reset", layout: "no_ember" if @error Webauthn.stage_challenge(@user, secure_session) @@ -823,32 +907,32 @@ class UsersController < ApplicationController second_factor_required: @user.totp_enabled?, security_key_required: @user.security_keys_enabled?, backup_enabled: @user.backup_codes_enabled?, - multiple_second_factor_methods: @user.has_multiple_second_factor_methods? + multiple_second_factor_methods: @user.has_multiple_second_factor_methods?, }.merge(Webauthn.allowed_credentials(@user, secure_session)) store_preloaded("password_reset", MultiJson.dump(security_params)) return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user) - render 'password_reset' + render "password_reset" end format.json do if @error || @user&.errors&.any? render json: { - success: false, - message: @error, - errors: @user&.errors&.to_hash, - is_developer: UsernameCheckerService.is_developer?(@user&.email), - admin: @user&.admin? - } + success: false, + message: @error, + errors: @user&.errors&.to_hash, + is_developer: UsernameCheckerService.is_developer?(@user&.email), + admin: @user&.admin?, + } else render json: { - success: true, - message: @success, - requires_approval: !Guardian.new(@user).can_access_forum?, - redirect_to: Wizard.user_requires_completion?(@user) ? wizard_path : nil - } + success: true, + message: @success, + requires_approval: !Guardian.new(@user).can_access_forum?, + redirect_to: Wizard.user_requires_completion?(@user) ? wizard_path : nil, + } end end end @@ -865,10 +949,10 @@ class UsersController < ApplicationController if Guardian.new(@user).can_access_forum? # Log in the user log_on_user(@user) - 'password_reset.success' + "password_reset.success" else @requires_approval = true - 'password_reset.success_unapproved' + "password_reset.success_unapproved" end @success = I18n.t(message) @@ -882,22 +966,26 @@ class UsersController < ApplicationController RateLimiter.new(nil, "admin-login-min-#{request.remote_ip}", 3, 1.minute).performed! if user = User.with_email(params[:email]).admins.human_users.first - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:email_login]) + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:email_login]) token_string = email_token.token - if params["use_safe_mode"] - token_string += "?safe_mode=no_plugins,no_themes" - end - Jobs.enqueue(:critical_user_email, type: "admin_login", user_id: user.id, email_token: token_string) + token_string += "?safe_mode=no_plugins,no_themes" if params["use_safe_mode"] + Jobs.enqueue( + :critical_user_email, + type: "admin_login", + user_id: user.id, + email_token: token_string, + ) @message = I18n.t("admin_login.success") else @message = I18n.t("admin_login.errors.unknown_email_address") end end - render layout: 'no_ember' + render layout: "no_ember" rescue RateLimiter::LimitExceeded @message = I18n.t("rate_limiter.slow_down") - render layout: 'no_ember' + render layout: "no_ember" end def email_login @@ -919,12 +1007,14 @@ class UsersController < ApplicationController if user_presence DiscourseEvent.trigger(:before_email_login, user) - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:email_login]) + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:email_login]) - Jobs.enqueue(:critical_user_email, + Jobs.enqueue( + :critical_user_email, type: "email_login", user_id: user.id, - email_token: email_token.token + email_token: email_token.token, ) end end @@ -938,8 +1028,8 @@ class UsersController < ApplicationController end def toggle_anon - user = AnonymousShadowCreator.get_master(current_user) || - AnonymousShadowCreator.get(current_user) + user = + AnonymousShadowCreator.get_master(current_user) || AnonymousShadowCreator.get(current_user) if user log_on_user(user) @@ -956,12 +1046,12 @@ class UsersController < ApplicationController elsif destination_url = cookies.delete(:destination_url) return redirect_to(destination_url, allow_other_host: true) else - return redirect_to(path('/')) + return redirect_to(path("/")) end end @custom_body_class = "static-account-created" - @message = session['user_created_message'] || I18n.t('activation.missing_session') + @message = session["user_created_message"] || I18n.t("activation.missing_session") @account_created = { message: @message, show_controls: false } if session_user_id = session[SessionController::ACTIVATE_USER_KEY] @@ -983,7 +1073,7 @@ class UsersController < ApplicationController def activate_account expires_now - render layout: 'no_ember' + render layout: "no_ember" end def perform_account_activation @@ -992,7 +1082,7 @@ class UsersController < ApplicationController if @user = EmailToken.confirm(params[:token], scope: EmailToken.scopes[:signup]) # Log in the user unless they need to be approved if Guardian.new(@user).can_access_forum? - @user.enqueue_welcome_message('welcome_user') if @user.send_welcome_message + @user.enqueue_welcome_message("welcome_user") if @user.send_welcome_message log_on_user(@user) # invites#perform_accept_invitation already sets destination_url, but @@ -1002,39 +1092,44 @@ class UsersController < ApplicationController # the topic they were originally invited to. destination_url = cookies.delete(:destination_url) if destination_url.blank? - topic = Invite - .joins(:invited_users) - .find_by(invited_users: { user_id: @user.id }) - &.topics - &.first + topic = + Invite + .joins(:invited_users) + .find_by(invited_users: { user_id: @user.id }) + &.topics + &.first - if @user.guardian.can_see?(topic) - destination_url = path(topic.relative_url) - end + destination_url = path(topic.relative_url) if @user.guardian.can_see?(topic) end if Wizard.user_requires_completion?(@user) return redirect_to(wizard_path) elsif destination_url.present? return redirect_to(destination_url, allow_other_host: true) - elsif SiteSetting.enable_discourse_connect_provider && payload = cookies.delete(:sso_payload) + elsif SiteSetting.enable_discourse_connect_provider && + payload = cookies.delete(:sso_payload) return redirect_to(session_sso_provider_url + "?" + payload) end else @needs_approval = true end else - flash.now[:error] = I18n.t('activation.already_done') + flash.now[:error] = I18n.t("activation.already_done") end - render layout: 'no_ember' + render layout: "no_ember" end def update_activation_email RateLimiter.new(nil, "activate-edit-email-hr-#{request.remote_ip}", 5, 1.hour).performed! if params[:username].present? - RateLimiter.new(nil, "activate-edit-email-hr-username-#{params[:username]}", 5, 1.hour).performed! + RateLimiter.new( + nil, + "activate-edit-email-hr-username-#{params[:username]}", + 5, + 1.hour, + ).performed! @user = User.find_by_username_or_email(params[:username]) raise Discourse::InvalidAccess.new unless @user.present? raise Discourse::InvalidAccess.new unless @user.confirm_password?(params[:password]) @@ -1053,7 +1148,8 @@ class UsersController < ApplicationController primary_email.skip_validate_email = false if primary_email.save - @email_token = @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) + @email_token = + @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) EmailToken.enqueue_signup_email(@email_token, to_address: @user.email) render json: success_json else @@ -1070,9 +1166,7 @@ class UsersController < ApplicationController raise Discourse::InvalidAccess.new if SiteSetting.must_approve_users? - if params[:username].present? - @user = User.find_by_username_or_email(params[:username].to_s) - end + @user = User.find_by_username_or_email(params[:username].to_s) if params[:username].present? raise Discourse::NotFound unless @user @@ -1083,9 +1177,10 @@ class UsersController < ApplicationController session.delete(SessionController::ACTIVATE_USER_KEY) if @user.active && @user.email_confirmed? - render_json_error(I18n.t('activation.activated'), status: 409) + render_json_error(I18n.t("activation.activated"), status: 409) else - @email_token = @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) + @email_token = + @user.email_tokens.create!(email: @user.email, scope: EmailToken.scopes[:signup]) EmailToken.enqueue_signup_email(@email_token, to_address: @user.email) render body: nil end @@ -1104,12 +1199,14 @@ class UsersController < ApplicationController @groups = Group.where(name: group_names) if group_names.present? options = { - topic_allowed_users: topic_allowed_users, - searching_user: current_user, - groups: @groups + topic_allowed_users: topic_allowed_users, + searching_user: current_user, + groups: @groups, } - options[:include_staged_users] = !!ActiveModel::Type::Boolean.new.cast(params[:include_staged_users]) + options[:include_staged_users] = !!ActiveModel::Type::Boolean.new.cast( + params[:include_staged_users], + ) options[:last_seen_users] = !!ActiveModel::Type::Boolean.new.cast(params[:last_seen_users]) if params[:limit].present? options[:limit] = params[:limit].to_i @@ -1125,50 +1222,44 @@ class UsersController < ApplicationController # we do not want group results ever if term is blank groups = if term.present? && current_user - if params[:include_groups] == 'true' + if params[:include_groups] == "true" Group.visible_groups(current_user) - elsif params[:include_mentionable_groups] == 'true' + elsif params[:include_mentionable_groups] == "true" Group.mentionable(current_user) - elsif params[:include_messageable_groups] == 'true' + elsif params[:include_messageable_groups] == "true" Group.messageable(current_user) end end if groups - DiscoursePluginRegistry.groups_callback_for_users_search_controller_action.each do |param_name, block| - if params[param_name.to_s] - groups = block.call(groups, current_user) - end + DiscoursePluginRegistry + .groups_callback_for_users_search_controller_action + .each do |param_name, block| + groups = block.call(groups, current_user) if params[param_name.to_s] end groups = Group.search_groups(term, groups: groups, sort: :auto) - to_render[:groups] = groups.map do |m| - { name: m.name, full_name: m.full_name } - end + to_render[:groups] = groups.map { |m| { name: m.name, full_name: m.full_name } } end render json: to_render end - AVATAR_TYPES_WITH_UPLOAD ||= %w{uploaded custom gravatar} + AVATAR_TYPES_WITH_UPLOAD ||= %w[uploaded custom gravatar] def pick_avatar user = fetch_user_from_params guardian.ensure_can_edit!(user) - if SiteSetting.discourse_connect_overrides_avatar - return render json: failed_json, status: 422 - end + return render json: failed_json, status: 422 if SiteSetting.discourse_connect_overrides_avatar type = params[:type] - invalid_type = type.present? && !AVATAR_TYPES_WITH_UPLOAD.include?(type) && type != 'system' - if invalid_type - return render json: failed_json, status: 422 - end + invalid_type = type.present? && !AVATAR_TYPES_WITH_UPLOAD.include?(type) && type != "system" + return render json: failed_json, status: 422 if invalid_type - if type.blank? || type == 'system' + if type.blank? || type == "system" upload_id = nil elsif !TrustLevelAndStaffAndDisabledSetting.matches?(SiteSetting.allow_uploaded_avatars, user) return render json: failed_json, status: 422 @@ -1176,25 +1267,21 @@ class UsersController < ApplicationController upload_id = params[:upload_id] upload = Upload.find_by(id: upload_id) - if upload.nil? - return render_json_error I18n.t('avatar.missing') - end + return render_json_error I18n.t("avatar.missing") if upload.nil? # old safeguard user.create_user_avatar unless user.user_avatar guardian.ensure_can_pick_avatar!(user.user_avatar, upload) - if type == 'gravatar' + if type == "gravatar" user.user_avatar.gravatar_upload_id = upload_id else user.user_avatar.custom_upload_id = upload_id end end - if user.is_system_user? - SiteSetting.use_site_small_logo_as_system_avatar = false - end + SiteSetting.use_site_small_logo_as_system_avatar = false if user.is_system_user? user.uploaded_avatar_id = upload_id user.save! @@ -1209,17 +1296,13 @@ class UsersController < ApplicationController url = params[:url] - if url.blank? - return render json: failed_json, status: 422 - end + return render json: failed_json, status: 422 if url.blank? if SiteSetting.selectable_avatars_mode == "disabled" return render json: failed_json, status: 422 end - if SiteSetting.selectable_avatars.blank? - return render json: failed_json, status: 422 - end + return render json: failed_json, status: 422 if SiteSetting.selectable_avatars.blank? unless upload = Upload.get_from_url(url) return render json: failed_json, status: 422 @@ -1231,9 +1314,7 @@ class UsersController < ApplicationController user.uploaded_avatar_id = upload.id - if user.is_system_user? - SiteSetting.use_site_small_logo_as_system_avatar = false - end + SiteSetting.use_site_small_logo_as_system_avatar = false if user.is_system_user? user.save! @@ -1242,10 +1323,10 @@ class UsersController < ApplicationController avatar.save! render json: { - avatar_template: user.avatar_template, - custom_avatar_template: user.avatar_template, - uploaded_avatar_id: upload.id, - } + avatar_template: user.avatar_template, + custom_avatar_template: user.avatar_template, + uploaded_avatar_id: upload.id, + } end def destroy_user_image @@ -1280,8 +1361,7 @@ class UsersController < ApplicationController # the admin should be able to change notification levels # on behalf of other users, so we cannot rely on current_user # for this case - if params[:acting_user_id].present? && - params[:acting_user_id].to_i != current_user.id + if params[:acting_user_id].present? && params[:acting_user_id].to_i != current_user.id if current_user.staff? acting_user = User.find(params[:acting_user_id]) else @@ -1298,20 +1378,26 @@ class UsersController < ApplicationController if ignored_user.present? ignored_user.update(expiring_at: DateTime.parse(params[:expiring_at])) else - IgnoredUser.create!(user: acting_user, ignored_user: target_user, expiring_at: Time.parse(params[:expiring_at])) + IgnoredUser.create!( + user: acting_user, + ignored_user: target_user, + expiring_at: Time.parse(params[:expiring_at]), + ) end - elsif params[:notification_level] == "mute" @error_message = "mute_error" guardian.ensure_can_mute_user!(target_user) IgnoredUser.where(user: acting_user, ignored_user: target_user).delete_all MutedUser.find_or_create_by!(user: acting_user, muted_user: target_user) - elsif params[:notification_level] == "normal" MutedUser.where(user: acting_user, muted_user: target_user).delete_all IgnoredUser.where(user: acting_user, ignored_user: target_user).delete_all else - return render_json_error(I18n.t("notification_level.invalid_value", value: params[:notification_level])) + return( + render_json_error( + I18n.t("notification_level.invalid_value", value: params[:notification_level]), + ) + ) end render json: success_json @@ -1330,22 +1416,20 @@ class UsersController < ApplicationController def recent_searches if !SiteSetting.log_search_queries - return render json: failed_json.merge( - error: I18n.t("user_activity.no_log_search_queries") - ), status: 403 + return( + render json: failed_json.merge(error: I18n.t("user_activity.no_log_search_queries")), + status: 403 + ) end query = SearchLog.where(user_id: current_user.id) if current_user.user_option.oldest_search_log_date - query = query - .where("created_at > ?", current_user.user_option.oldest_search_log_date) + query = query.where("created_at > ?", current_user.user_option.oldest_search_log_date) end - results = query.group(:term) - .order("max(created_at) DESC") - .limit(MAX_RECENT_SEARCHES) - .pluck(:term) + results = + query.group(:term).order("max(created_at) DESC").limit(MAX_RECENT_SEARCHES).pluck(:term) render json: success_json.merge(recent_searches: results) end @@ -1361,12 +1445,14 @@ class UsersController < ApplicationController result = {} - %W{ - number_of_deleted_posts number_of_flagged_posts number_of_flags_given - number_of_suspensions warnings_received_count number_of_rejected_posts - }.each do |info| - result[info] = @user.public_send(info) - end + %W[ + number_of_deleted_posts + number_of_flagged_posts + number_of_flags_given + number_of_suspensions + warnings_received_count + number_of_rejected_posts + ].each { |info| result[info] = @user.public_send(info) } render json: result end @@ -1375,8 +1461,9 @@ class UsersController < ApplicationController @confirmation = AdminConfirmation.find_by_code(params[:token]) raise Discourse::NotFound unless @confirmation - raise Discourse::InvalidAccess.new unless - @confirmation.performed_by.id == (current_user&.id || @confirmation.performed_by.id) + unless @confirmation.performed_by.id == (current_user&.id || @confirmation.performed_by.id) + raise Discourse::InvalidAccess.new + end if request.post? @confirmation.email_confirmed! @@ -1385,75 +1472,86 @@ class UsersController < ApplicationController respond_to do |format| format.json { render json: success_json } - format.html { render layout: 'no_ember' } + format.html { render layout: "no_ember" } end end def list_second_factors - raise Discourse::NotFound if SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins + if SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins + raise Discourse::NotFound + end unless params[:password].empty? - RateLimiter.new(nil, "login-hr-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_hour, 1.hour).performed! - RateLimiter.new(nil, "login-min-#{request.remote_ip}", SiteSetting.max_logins_per_ip_per_minute, 1.minute).performed! + RateLimiter.new( + nil, + "login-hr-#{request.remote_ip}", + SiteSetting.max_logins_per_ip_per_hour, + 1.hour, + ).performed! + RateLimiter.new( + nil, + "login-min-#{request.remote_ip}", + SiteSetting.max_logins_per_ip_per_minute, + 1.minute, + ).performed! unless current_user.confirm_password?(params[:password]) - return render json: failed_json.merge( - error: I18n.t("login.incorrect_password") - ) + return render json: failed_json.merge(error: I18n.t("login.incorrect_password")) end confirm_secure_session end if secure_session_confirmed? - totp_second_factors = current_user.totps - .select(:id, :name, :last_used, :created_at, :method) - .where(enabled: true).order(:created_at) + totp_second_factors = + current_user + .totps + .select(:id, :name, :last_used, :created_at, :method) + .where(enabled: true) + .order(:created_at) - security_keys = current_user.security_keys.where(factor_type: UserSecurityKey.factor_types[:second_factor]).order(:created_at) + security_keys = + current_user + .security_keys + .where(factor_type: UserSecurityKey.factor_types[:second_factor]) + .order(:created_at) - render json: success_json.merge( - totps: totp_second_factors, - security_keys: security_keys - ) + render json: success_json.merge(totps: totp_second_factors, security_keys: security_keys) else - render json: success_json.merge( - password_required: true - ) + render json: success_json.merge(password_required: true) end end def create_second_factor_backup backup_codes = current_user.generate_backup_codes - render json: success_json.merge( - backup_codes: backup_codes - ) + render json: success_json.merge(backup_codes: backup_codes) end def create_second_factor_totp - require 'rotp' if !defined? ROTP + require "rotp" if !defined?(ROTP) totp_data = ROTP::Base32.random secure_session["staged-totp-#{current_user.id}"] = totp_data - qrcode_png = RQRCode::QRCode.new(current_user.totp_provisioning_uri(totp_data)).as_png( - border_modules: 1, - size: 240 - ) + qrcode_png = + RQRCode::QRCode.new(current_user.totp_provisioning_uri(totp_data)).as_png( + border_modules: 1, + size: 240, + ) - render json: success_json.merge( - key: totp_data.scan(/.{4}/).join(" "), - qr: qrcode_png.to_data_url - ) + render json: + success_json.merge(key: totp_data.scan(/.{4}/).join(" "), qr: qrcode_png.to_data_url) end def create_second_factor_security_key challenge_session = Webauthn.stage_challenge(current_user, secure_session) - render json: success_json.merge( - challenge: challenge_session.challenge, - rp_id: challenge_session.rp_id, - rp_name: challenge_session.rp_name, - supported_algorithms: ::Webauthn::SUPPORTED_ALGORITHMS, - user_secure_id: current_user.create_or_fetch_secure_identifier, - existing_active_credential_ids: current_user.second_factor_security_key_credential_ids - ) + render json: + success_json.merge( + challenge: challenge_session.challenge, + rp_id: challenge_session.rp_id, + rp_name: challenge_session.rp_name, + supported_algorithms: ::Webauthn::SUPPORTED_ALGORITHMS, + user_secure_id: current_user.create_or_fetch_secure_identifier, + existing_active_credential_ids: + current_user.second_factor_security_key_credential_ids, + ) end def register_second_factor_security_key @@ -1466,7 +1564,7 @@ class UsersController < ApplicationController params, challenge: Webauthn.challenge(current_user, secure_session), rp_id: Webauthn.rp_id(current_user, secure_session), - origin: Discourse.base_url + origin: Discourse.base_url, ).register_second_factor_security_key render json: success_json rescue ::Webauthn::SecurityKeyError => err @@ -1477,12 +1575,8 @@ class UsersController < ApplicationController user_security_key = current_user.security_keys.find_by(id: params[:id].to_i) raise Discourse::InvalidParameters unless user_security_key - if params[:name] && !params[:name].blank? - user_security_key.update!(name: params[:name]) - end - if params[:disable] == "true" - user_security_key.update!(enabled: false) - end + user_security_key.update!(name: params[:name]) if params[:name] && !params[:name].blank? + user_security_key.update!(enabled: false) if params[:disable] == "true" render json: success_json end @@ -1501,15 +1595,15 @@ class UsersController < ApplicationController rate_limit_second_factor!(current_user) - authenticated = !auth_token.blank? && totp_object.verify( - auth_token, - drift_ahead: SecondFactorManager::TOTP_ALLOWED_DRIFT_SECONDS, - drift_behind: SecondFactorManager::TOTP_ALLOWED_DRIFT_SECONDS - ) + authenticated = + !auth_token.blank? && + totp_object.verify( + auth_token, + drift_ahead: SecondFactorManager::TOTP_ALLOWED_DRIFT_SECONDS, + drift_behind: SecondFactorManager::TOTP_ALLOWED_DRIFT_SECONDS, + ) unless authenticated - return render json: failed_json.merge( - error: I18n.t("login.invalid_second_factor_code") - ) + return render json: failed_json.merge(error: I18n.t("login.invalid_second_factor_code")) end current_user.create_totp(data: totp_data, name: params[:name], enabled: true) render json: success_json @@ -1523,7 +1617,7 @@ class UsersController < ApplicationController Jobs.enqueue( :critical_user_email, type: "account_second_factor_disabled", - user_id: current_user.id + user_id: current_user.id, ) render json: success_json @@ -1543,24 +1637,26 @@ class UsersController < ApplicationController raise Discourse::InvalidParameters unless user_second_factor - if params[:name] && !params[:name].blank? - user_second_factor.update!(name: params[:name]) - end + user_second_factor.update!(name: params[:name]) if params[:name] && !params[:name].blank? if params[:disable] == "true" # Disabling backup codes deletes *all* backup codes if update_second_factor_method == UserSecondFactor.methods[:backup_codes] - current_user.user_second_factors.where(method: UserSecondFactor.methods[:backup_codes]).destroy_all + current_user + .user_second_factors + .where(method: UserSecondFactor.methods[:backup_codes]) + .destroy_all else user_second_factor.update!(enabled: false) end - end render json: success_json end def second_factor_check_confirmed_password - raise Discourse::NotFound if SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins + if SiteSetting.enable_discourse_connect || !SiteSetting.enable_local_logins + raise Discourse::NotFound + end raise Discourse::InvalidAccess.new unless current_user && secure_session_confirmed? end @@ -1585,9 +1681,9 @@ class UsersController < ApplicationController render json: success_json else render json: { - success: false, - message: I18n.t("associated_accounts.revoke_failed", provider_name: provider_name) - } + success: false, + message: I18n.t("associated_accounts.revoke_failed", provider_name: provider_name), + } end end end @@ -1599,7 +1695,9 @@ class UsersController < ApplicationController if params[:token_id] token = UserAuthToken.find_by(id: params[:token_id], user_id: user.id) # The user should not be able to revoke the auth token of current session. - raise Discourse::InvalidParameters.new(:token_id) if !token || guardian.auth_token == token.auth_token + if !token || guardian.auth_token == token.auth_token + raise Discourse::InvalidParameters.new(:token_id) + end UserAuthToken.where(id: params[:token_id], user_id: user.id).each(&:destroy!) MessageBus.publish "/file-change", ["refresh"], user_ids: [user.id] @@ -1615,7 +1713,12 @@ class UsersController < ApplicationController topic = Topic.find(params[:topic_id].to_i) if !guardian.can_feature_topic?(user, topic) - return render_json_error(I18n.t('activerecord.errors.models.user_profile.attributes.featured_topic_id.invalid'), 403) + return( + render_json_error( + I18n.t("activerecord.errors.models.user_profile.attributes.featured_topic_id.invalid"), + 403, + ) + ) end user.user_profile.update(featured_topic_id: topic.id) @@ -1640,24 +1743,27 @@ class UsersController < ApplicationController bookmark_list.load if bookmark_list.bookmarks.empty? - render json: { - bookmarks: [] - } + render json: { bookmarks: [] } else page = params[:page].to_i + 1 - bookmark_list.more_bookmarks_url = "#{Discourse.base_path}/u/#{params[:username]}/bookmarks.json?page=#{page}" + bookmark_list.more_bookmarks_url = + "#{Discourse.base_path}/u/#{params[:username]}/bookmarks.json?page=#{page}" render_serialized(bookmark_list, UserBookmarkListSerializer) end end format.ics do - @bookmark_reminders = Bookmark.with_reminders - .where(user_id: user.id) - .order(:reminder_at) - .map do |bookmark| - bookmark.registered_bookmarkable.serializer.new( - bookmark, scope: user_guardian, root: false - ) - end + @bookmark_reminders = + Bookmark + .with_reminders + .where(user_id: user.id) + .order(:reminder_at) + .map do |bookmark| + bookmark.registered_bookmarkable.serializer.new( + bookmark, + scope: user_guardian, + root: false, + ) + end end end end @@ -1668,22 +1774,24 @@ class UsersController < ApplicationController raise Discourse::InvalidAccess.new("username doesn't match current_user's username") end - reminder_notifications = Notification - .for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT) - .unread - .where(notification_type: Notification.types[:bookmark_reminder]) + reminder_notifications = + Notification + .for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT) + .unread + .where(notification_type: Notification.types[:bookmark_reminder]) if reminder_notifications.size < USER_MENU_LIST_LIMIT - exclude_bookmark_ids = reminder_notifications - .filter_map { |notification| notification.data_hash[:bookmark_id] } + exclude_bookmark_ids = + reminder_notifications.filter_map { |notification| notification.data_hash[:bookmark_id] } - bookmark_list = UserBookmarkList.new( - user: current_user, - guardian: guardian, - params: { - per_page: USER_MENU_LIST_LIMIT - reminder_notifications.size - } - ) + bookmark_list = + UserBookmarkList.new( + user: current_user, + guardian: guardian, + params: { + per_page: USER_MENU_LIST_LIMIT - reminder_notifications.size, + }, + ) bookmark_list.load do |query| if exclude_bookmark_ids.present? query.where("bookmarks.id NOT IN (?)", exclude_bookmark_ids) @@ -1692,27 +1800,26 @@ class UsersController < ApplicationController end if reminder_notifications.present? - serialized_notifications = ActiveModel::ArraySerializer.new( - reminder_notifications, - each_serializer: NotificationSerializer, - scope: guardian - ) + serialized_notifications = + ActiveModel::ArraySerializer.new( + reminder_notifications, + each_serializer: NotificationSerializer, + scope: guardian, + ) end if bookmark_list bookmark_list.bookmark_serializer_opts = { link_to_first_unread_post: true } - serialized_bookmarks = serialize_data( - bookmark_list, - UserBookmarkListSerializer, - scope: guardian, - root: false - )[:bookmarks] + serialized_bookmarks = + serialize_data(bookmark_list, UserBookmarkListSerializer, scope: guardian, root: false)[ + :bookmarks + ] end render json: { - notifications: serialized_notifications || [], - bookmarks: serialized_bookmarks || [] - } + notifications: serialized_notifications || [], + bookmarks: serialized_bookmarks || [], + } end def user_menu_messages @@ -1720,68 +1827,71 @@ class UsersController < ApplicationController raise Discourse::InvalidAccess.new("username doesn't match current_user's username") end - if !current_user.staff? && !current_user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) + if !current_user.staff? && + !current_user.in_any_groups?(SiteSetting.personal_message_enabled_groups_map) raise Discourse::InvalidAccess.new("personal messages are disabled.") end - unread_notifications = Notification - .for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT) - .unread - .where(notification_type: [Notification.types[:private_message], Notification.types[:group_message_summary]]) - .to_a + unread_notifications = + Notification + .for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT) + .unread + .where( + notification_type: [ + Notification.types[:private_message], + Notification.types[:group_message_summary], + ], + ) + .to_a if unread_notifications.size < USER_MENU_LIST_LIMIT exclude_topic_ids = unread_notifications.filter_map(&:topic_id).uniq limit = USER_MENU_LIST_LIMIT - unread_notifications.size - messages_list = TopicQuery.new( - current_user, - per_page: limit - ).list_private_messages_direct_and_groups(current_user) do |query| - if exclude_topic_ids.present? - query.where("topics.id NOT IN (?)", exclude_topic_ids) - else - query - end - end - read_notifications = Notification - .for_user_menu(current_user.id, limit: limit) - .where( - read: true, - notification_type: Notification.types[:group_message_summary], - ) - .to_a + messages_list = + TopicQuery + .new(current_user, per_page: limit) + .list_private_messages_direct_and_groups(current_user) do |query| + if exclude_topic_ids.present? + query.where("topics.id NOT IN (?)", exclude_topic_ids) + else + query + end + end + read_notifications = + Notification + .for_user_menu(current_user.id, limit: limit) + .where(read: true, notification_type: Notification.types[:group_message_summary]) + .to_a end if unread_notifications.present? - serialized_unread_notifications = ActiveModel::ArraySerializer.new( - unread_notifications, - each_serializer: NotificationSerializer, - scope: guardian - ) + serialized_unread_notifications = + ActiveModel::ArraySerializer.new( + unread_notifications, + each_serializer: NotificationSerializer, + scope: guardian, + ) end if messages_list - serialized_messages = serialize_data( - messages_list, - TopicListSerializer, - scope: guardian, - root: false - )[:topics] + serialized_messages = + serialize_data(messages_list, TopicListSerializer, scope: guardian, root: false)[:topics] end if read_notifications.present? - serialized_read_notifications = ActiveModel::ArraySerializer.new( - read_notifications, - each_serializer: NotificationSerializer, - scope: guardian - ) + serialized_read_notifications = + ActiveModel::ArraySerializer.new( + read_notifications, + each_serializer: NotificationSerializer, + scope: guardian, + ) end render json: { - unread_notifications: serialized_unread_notifications || [], - read_notifications: serialized_read_notifications || [], - topics: serialized_messages || [] - } + unread_notifications: serialized_unread_notifications || [], + read_notifications: serialized_read_notifications || [], + topics: serialized_messages || [], + } end private @@ -1803,11 +1913,12 @@ class UsersController < ApplicationController end def password_reset_find_user(token, committing_change:) - @user = if committing_change - EmailToken.confirm(token, scope: EmailToken.scopes[:password_reset]) - else - EmailToken.confirmable(token, scope: EmailToken.scopes[:password_reset])&.user - end + @user = + if committing_change + EmailToken.confirm(token, scope: EmailToken.scopes[:password_reset]) + else + EmailToken.confirmable(token, scope: EmailToken.scopes[:password_reset])&.user + end if @user secure_session["password-#{token}"] = @user.id @@ -1816,16 +1927,16 @@ class UsersController < ApplicationController @user = User.find(user_id) if user_id > 0 end - @error = I18n.t('password_reset.no_token', base_url: Discourse.base_url) if !@user + @error = I18n.t("password_reset.no_token", base_url: Discourse.base_url) if !@user end def respond_to_suspicious_request if suspicious?(params) render json: { - success: true, - active: false, - message: I18n.t("login.activate_email", email: params[:email]) - } + success: true, + active: false, + message: I18n.t("login.activate_email", email: params[:email]), + } end end @@ -1837,30 +1948,30 @@ class UsersController < ApplicationController def honeypot_or_challenge_fails?(params) return false if is_api? params[:password_confirmation] != honeypot_value || - params[:challenge] != challenge_value.try(:reverse) + params[:challenge] != challenge_value.try(:reverse) end def user_params - permitted = [ - :name, - :email, - :password, - :username, - :title, - :date_of_birth, - :muted_usernames, - :allowed_pm_usernames, - :theme_ids, - :locale, - :bio_raw, - :location, - :website, - :dismissed_banner_key, - :profile_background_upload_url, - :card_background_upload_url, - :primary_group_id, - :flair_group_id, - :featured_topic_id, + permitted = %i[ + name + email + password + username + title + date_of_birth + muted_usernames + allowed_pm_usernames + theme_ids + locale + bio_raw + location + website + dismissed_banner_key + profile_background_upload_url + card_background_upload_url + primary_group_id + flair_group_id + featured_topic_id ] editable_custom_fields = User.editable_user_custom_fields(by_staff: current_user.try(:staff?)) @@ -1888,21 +1999,17 @@ class UsersController < ApplicationController if SiteSetting.enable_user_status permitted << :status - permitted << { status: [:emoji, :description, :ends_at] } + permitted << { status: %i[emoji description ends_at] } end - result = params - .permit(permitted, theme_ids: [], seen_popups: []) - .reverse_merge( + result = + params.permit(permitted, theme_ids: [], seen_popups: []).reverse_merge( ip_address: request.remote_ip, - registration_ip_address: request.remote_ip + registration_ip_address: request.remote_ip, ) - if !UsernameCheckerService.is_developer?(result['email']) && - is_api? && - current_user.present? && - current_user.admin? - + if !UsernameCheckerService.is_developer?(result["email"]) && is_api? && current_user.present? && + current_user.admin? result.merge!(params.permit(:active, :staged, :approved)) end @@ -1923,7 +2030,7 @@ class UsersController < ApplicationController ip = request.remote_ip user_id = (current_user.id if current_user) - Scheduler::Defer.later 'Track profile view visit' do + Scheduler::Defer.later "Track profile view visit" do UserProfileView.add(user_profile_id, ip, user_id) end end @@ -1956,17 +2063,12 @@ class UsersController < ApplicationController end def render_invite_error(message) - render json: { - invites: [], - can_see_invite_details: false, - error: message - } + render json: { invites: [], can_see_invite_details: false, error: message } end def serialize_found_users(users) - each_serializer = SiteSetting.enable_user_status? ? - FoundUserWithStatusSerializer : - FoundUserSerializer + each_serializer = + SiteSetting.enable_user_status? ? FoundUserWithStatusSerializer : FoundUserSerializer { users: ActiveModel::ArraySerializer.new(users, each_serializer: each_serializer).as_json } end diff --git a/app/controllers/users_email_controller.rb b/app/controllers/users_email_controller.rb index f3f30a47dd5..98ac6458ed0 100644 --- a/app/controllers/users_email_controller.rb +++ b/app/controllers/users_email_controller.rb @@ -1,35 +1,31 @@ # frozen_string_literal: true class UsersEmailController < ApplicationController + requires_login only: %i[index update] - requires_login only: [:index, :update] + skip_before_action :check_xhr, + only: %i[ + confirm_old_email + show_confirm_old_email + confirm_new_email + show_confirm_new_email + ] - skip_before_action :check_xhr, only: [ - :confirm_old_email, - :show_confirm_old_email, - :confirm_new_email, - :show_confirm_new_email - ] + skip_before_action :redirect_to_login_if_required, + only: %i[ + confirm_old_email + show_confirm_old_email + confirm_new_email + show_confirm_new_email + ] - skip_before_action :redirect_to_login_if_required, only: [ - :confirm_old_email, - :show_confirm_old_email, - :confirm_new_email, - :show_confirm_new_email - ] - - before_action :require_login, only: [ - :confirm_old_email, - :show_confirm_old_email - ] + before_action :require_login, only: %i[confirm_old_email show_confirm_old_email] def index end def create - if !SiteSetting.enable_secondary_emails - return render json: failed_json, status: 410 - end + return render json: failed_json, status: 410 if !SiteSetting.enable_secondary_emails params.require(:email) user = fetch_user_from_params @@ -40,9 +36,7 @@ class UsersEmailController < ApplicationController updater = EmailUpdater.new(guardian: guardian, user: user) updater.change_to(params[:email], add: true) - if updater.errors.present? - return render_json_error(updater.errors.full_messages) - end + return render_json_error(updater.errors.full_messages) if updater.errors.present? render body: nil rescue RateLimiter::LimitExceeded @@ -59,9 +53,7 @@ class UsersEmailController < ApplicationController updater = EmailUpdater.new(guardian: guardian, user: user) updater.change_to(params[:email]) - if updater.errors.present? - return render_json_error(updater.errors.full_messages) - end + return render_json_error(updater.errors.full_messages) if updater.errors.present? render body: nil rescue RateLimiter::LimitExceeded @@ -119,9 +111,7 @@ class UsersEmailController < ApplicationController def show_confirm_new_email load_change_request(:new) - if params[:done].to_s == "true" - @done = true - end + @done = true if params[:done].to_s == "true" if @change_request&.change_state != EmailChangeRequest.states[:authorizing_new] @error = I18n.t("change_email.already_done") @@ -135,21 +125,20 @@ class UsersEmailController < ApplicationController if params[:show_backup].to_s == "true" && @backup_codes_enabled @show_backup_codes = true else - if @user.totp_enabled? - @show_second_factor = true - end + @show_second_factor = true if @user.totp_enabled? if @user.security_keys_enabled? Webauthn.stage_challenge(@user, secure_session) @show_security_key = params[:show_totp].to_s == "true" ? false : true @security_key_challenge = Webauthn.challenge(@user, secure_session) - @security_key_allowed_credential_ids = Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids] + @security_key_allowed_credential_ids = + Webauthn.allowed_credentials(@user, secure_session)[:allowed_credential_ids] end end @to_email = @change_request.new_email end - render layout: 'no_ember' + render layout: "no_ember" end def confirm_old_email @@ -183,16 +172,14 @@ class UsersEmailController < ApplicationController @error = I18n.t("change_email.already_done") end - if params[:done].to_s == "true" - @almost_done = true - end + @almost_done = true if params[:done].to_s == "true" if !@error @from_email = @user.email @to_email = @change_request.new_email end - render layout: 'no_ember' + render layout: "no_ember" end private @@ -204,27 +191,24 @@ class UsersEmailController < ApplicationController if token if type == :old - @change_request = token.user&.email_change_requests.where(old_email_token_id: token.id).first + @change_request = + token.user&.email_change_requests.where(old_email_token_id: token.id).first elsif type == :new - @change_request = token.user&.email_change_requests.where(new_email_token_id: token.id).first + @change_request = + token.user&.email_change_requests.where(new_email_token_id: token.id).first end end @user = token&.user - if (!@user || !@change_request) - @error = I18n.t("change_email.already_done") - end + @error = I18n.t("change_email.already_done") if (!@user || !@change_request) if current_user && current_user.id != @user&.id - @error = I18n.t 'change_email.wrong_account_error' + @error = I18n.t "change_email.wrong_account_error" end end def require_login - if !current_user - redirect_to_login - end + redirect_to_login if !current_user end - end diff --git a/app/controllers/webhooks_controller.rb b/app/controllers/webhooks_controller.rb index 74024b31524..b84d59c269e 100644 --- a/app/controllers/webhooks_controller.rb +++ b/app/controllers/webhooks_controller.rb @@ -95,8 +95,8 @@ class WebhooksController < ActionController::Base message_event = event.dig("msys", "message_event") next unless message_event - message_id = message_event.dig("rcpt_meta", "message_id") - to_address = message_event["rcpt_to"] + message_id = message_event.dig("rcpt_meta", "message_id") + to_address = message_event["rcpt_to"] bounce_class = message_event["bounce_class"] next unless bounce_class @@ -116,7 +116,7 @@ class WebhooksController < ActionController::Base end def aws - raw = request.raw_post + raw = request.raw_post json = JSON.parse(raw) case json["Type"] @@ -152,11 +152,14 @@ class WebhooksController < ActionController::Base return false if (Time.at(timestamp.to_i) - Time.now).abs > 12.hours.to_i # check the signature - signature == OpenSSL::HMAC.hexdigest("SHA256", SiteSetting.mailgun_api_key, "#{timestamp}#{token}") + signature == + OpenSSL::HMAC.hexdigest("SHA256", SiteSetting.mailgun_api_key, "#{timestamp}#{token}") end def handle_mailgun_legacy(params) - return mailgun_failure unless valid_mailgun_signature?(params["token"], params["timestamp"], params["signature"]) + unless valid_mailgun_signature?(params["token"], params["timestamp"], params["signature"]) + return mailgun_failure + end event = params["event"] message_id = Email::MessageIdService.message_id_clean(params["Message-Id"]) @@ -177,7 +180,13 @@ class WebhooksController < ActionController::Base def handle_mailgun_new(params) signature = params["signature"] - return mailgun_failure unless valid_mailgun_signature?(signature["token"], signature["timestamp"], signature["signature"]) + unless valid_mailgun_signature?( + signature["token"], + signature["timestamp"], + signature["signature"], + ) + return mailgun_failure + end data = params["event-data"] error_code = params.dig("delivery-status", "code") @@ -207,5 +216,4 @@ class WebhooksController < ActionController::Base Email::Receiver.update_bounce_score(email_log.user.email, bounce_score) end - end diff --git a/app/controllers/wizard_controller.rb b/app/controllers/wizard_controller.rb index e7581dcca53..cdce04c1589 100644 --- a/app/controllers/wizard_controller.rb +++ b/app/controllers/wizard_controller.rb @@ -13,9 +13,7 @@ class WizardController < ApplicationController render_serialized(wizard, WizardSerializer) end - format.html do - render body: nil - end + format.html { render body: nil } end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 06e8be21459..2520cd69b5d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,7 +1,7 @@ # coding: utf-8 # frozen_string_literal: true -require 'current_user' -require 'canonical_url' +require "current_user" +require "canonical_url" module ApplicationHelper include CurrentUser @@ -14,7 +14,6 @@ module ApplicationHelper end def discourse_config_environment(testing: false) - # TODO: Can this come from Ember CLI somehow? config = { modulePrefix: "discourse", @@ -23,24 +22,27 @@ module ApplicationHelper locationType: "history", historySupportMiddleware: false, EmberENV: { - FEATURES: {}, - EXTEND_PROTOTYPES: { "Date": false }, + FEATURES: { + }, + EXTEND_PROTOTYPES: { + Date: false, + }, _APPLICATION_TEMPLATE_WRAPPER: false, _DEFAULT_ASYNC_OBSERVERS: true, - _JQUERY_INTEGRATION: true + _JQUERY_INTEGRATION: true, }, APP: { name: "discourse", version: "#{Discourse::VERSION::STRING} #{Discourse.git_version}", - exportApplicationGlobal: true - } + exportApplicationGlobal: true, + }, } if testing config[:environment] = "test" config[:locationType] = "none" config[:APP][:autoboot] = false - config[:APP][:rootElement] = '#ember-testing' + config[:APP][:rootElement] = "#ember-testing" end config.to_json @@ -48,15 +50,9 @@ module ApplicationHelper def google_universal_analytics_json(ua_domain_name = nil) result = {} - if ua_domain_name - result[:cookieDomain] = ua_domain_name.gsub(/^http(s)?:\/\//, '') - end - if current_user.present? - result[:userId] = current_user.id - end - if SiteSetting.ga_universal_auto_link_domains.present? - result[:allowLinker] = true - end + result[:cookieDomain] = ua_domain_name.gsub(%r{^http(s)?://}, "") if ua_domain_name + result[:userId] = current_user.id if current_user.present? + result[:allowLinker] = true if SiteSetting.ga_universal_auto_link_domains.present? result.to_json end @@ -73,7 +69,7 @@ module ApplicationHelper end def shared_session_key - if SiteSetting.long_polling_base_url != '/' && current_user + if SiteSetting.long_polling_base_url != "/" && current_user sk = "shared_session_key" return request.env[sk] if request.env[sk] @@ -95,10 +91,15 @@ module ApplicationHelper path = ActionController::Base.helpers.asset_path("#{script}.js") if GlobalSetting.use_s3? && GlobalSetting.s3_cdn_url - resolved_s3_asset_cdn_url = GlobalSetting.s3_asset_cdn_url.presence || GlobalSetting.s3_cdn_url + resolved_s3_asset_cdn_url = + GlobalSetting.s3_asset_cdn_url.presence || GlobalSetting.s3_cdn_url if GlobalSetting.cdn_url folder = ActionController::Base.config.relative_url_root || "/" - path = path.gsub(File.join(GlobalSetting.cdn_url, folder, "/"), File.join(resolved_s3_asset_cdn_url, "/")) + path = + path.gsub( + File.join(GlobalSetting.cdn_url, folder, "/"), + File.join(resolved_s3_asset_cdn_url, "/"), + ) else # we must remove the subfolder path here, assets are uploaded to s3 # without it getting involved @@ -121,8 +122,8 @@ module ApplicationHelper path = path.gsub(/\.([^.]+)$/, '.gz.\1') end end - - elsif GlobalSetting.cdn_url&.start_with?("https") && is_brotli_req? && Rails.env != "development" + elsif GlobalSetting.cdn_url&.start_with?("https") && is_brotli_req? && + Rails.env != "development" path = path.gsub("#{GlobalSetting.cdn_url}/assets/", "#{GlobalSetting.cdn_url}/brotli_asset/") end @@ -136,14 +137,17 @@ module ApplicationHelper scripts.push(*chunks) end - scripts.map do |name| - path = script_asset_path(name) - preload_script_url(path) - end.join("\n").html_safe + scripts + .map do |name| + path = script_asset_path(name) + preload_script_url(path) + end + .join("\n") + .html_safe end def preload_script_url(url) - add_resource_preload_list(url, 'script') + add_resource_preload_list(url, "script") if GlobalSetting.preload_link_header <<~HTML.html_safe @@ -157,50 +161,47 @@ module ApplicationHelper end def add_resource_preload_list(resource_url, type) - @links_to_preload << %Q(<#{resource_url}>; rel="preload"; as="#{type}") if !@links_to_preload.nil? + if !@links_to_preload.nil? + @links_to_preload << %Q(<#{resource_url}>; rel="preload"; as="#{type}") + end end def discourse_csrf_tags # anon can not have a CSRF token cause these are all pages # that may be cached, causing a mismatch between session CSRF # and CSRF on page and horrible impossible to debug login issues - if current_user - csrf_meta_tags - end + csrf_meta_tags if current_user end def html_classes list = [] - list << (mobile_view? ? 'mobile-view' : 'desktop-view') - list << (mobile_device? ? 'mobile-device' : 'not-mobile-device') - list << 'ios-device' if ios_device? - list << 'rtl' if rtl? + list << (mobile_view? ? "mobile-view" : "desktop-view") + list << (mobile_device? ? "mobile-device" : "not-mobile-device") + list << "ios-device" if ios_device? + list << "rtl" if rtl? list << text_size_class - list << 'anon' unless current_user - list.join(' ') + list << "anon" unless current_user + list.join(" ") end def body_classes result = ApplicationHelper.extra_body_classes.to_a - if @category && @category.url.present? - result << "category-#{@category.slug_path.join('-')}" - end + result << "category-#{@category.slug_path.join("-")}" if @category && @category.url.present? - if current_user.present? && - current_user.primary_group_id && - primary_group_name = Group.where(id: current_user.primary_group_id).pluck_first(:name) + if current_user.present? && current_user.primary_group_id && + primary_group_name = Group.where(id: current_user.primary_group_id).pluck_first(:name) result << "primary-group-#{primary_group_name.downcase}" end - result.join(' ') + result.join(" ") end def text_size_class requested_cookie_size, cookie_seq = cookies[:text_size]&.split("|") server_seq = current_user&.user_option&.text_size_seq if cookie_seq && server_seq && cookie_seq.to_i >= server_seq && - UserOption.text_sizes.keys.include?(requested_cookie_size&.to_sym) + UserOption.text_sizes.keys.include?(requested_cookie_size&.to_sym) cookie_size = requested_cookie_size end @@ -211,11 +212,11 @@ module ApplicationHelper def escape_unicode(javascript) if javascript javascript = javascript.scrub - javascript.gsub!(/\342\200\250/u, '
') - javascript.gsub!(/(<\/)/u, '\u003C/') + javascript.gsub!(/\342\200\250/u, "
") + javascript.gsub!(%r{( 0 && opts[:like_count] && opts[:like_count] > 0 - result << tag(:meta, name: 'twitter:label1', value: I18n.t("reading_time")) - result << tag(:meta, name: 'twitter:data1', value: "#{opts[:read_time]} mins 🕑") - result << tag(:meta, name: 'twitter:label2', value: I18n.t("likes")) - result << tag(:meta, name: 'twitter:data2', value: "#{opts[:like_count]} ❤") + result << tag(:meta, name: "twitter:label1", value: I18n.t("reading_time")) + result << tag(:meta, name: "twitter:data1", value: "#{opts[:read_time]} mins 🕑") + result << tag(:meta, name: "twitter:label2", value: I18n.t("likes")) + result << tag(:meta, name: "twitter:data2", value: "#{opts[:like_count]} ❤") end if opts[:published_time] - result << tag(:meta, property: 'article:published_time', content: opts[:published_time]) + result << tag(:meta, property: "article:published_time", content: opts[:published_time]) end - if opts[:ignore_canonical] - result << tag(:meta, property: 'og:ignore_canonical', content: true) - end + result << tag(:meta, property: "og:ignore_canonical", content: true) if opts[:ignore_canonical] result.join("\n") end private def generate_twitter_card_metadata(result, opts) - img_url = opts[:twitter_summary_large_image].present? ? \ - opts[:twitter_summary_large_image] : - opts[:image] + img_url = + ( + if opts[:twitter_summary_large_image].present? + opts[:twitter_summary_large_image] + else + opts[:image] + end + ) # Twitter does not allow SVGs, see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup if img_url.ends_with?(".svg") @@ -339,29 +344,29 @@ module ApplicationHelper end if opts[:twitter_summary_large_image].present? && img_url.present? - result << tag(:meta, name: 'twitter:card', content: "summary_large_image") + result << tag(:meta, name: "twitter:card", content: "summary_large_image") result << tag(:meta, name: "twitter:image", content: img_url) elsif opts[:image].present? && img_url.present? - result << tag(:meta, name: 'twitter:card', content: "summary") + result << tag(:meta, name: "twitter:card", content: "summary") result << tag(:meta, name: "twitter:image", content: img_url) else - result << tag(:meta, name: 'twitter:card', content: "summary") + result << tag(:meta, name: "twitter:card", content: "summary") end end def render_sitelinks_search_tag - if current_page?('/') || current_page?(Discourse.base_path) + if current_page?("/") || current_page?(Discourse.base_path) json = { - '@context' => 'http://schema.org', - '@type' => 'WebSite', - url: Discourse.base_url, - potentialAction: { - '@type' => 'SearchAction', - target: "#{Discourse.base_url}/search?q={search_term_string}", - 'query-input' => 'required name=search_term_string', - } + "@context" => "http://schema.org", + "@type" => "WebSite", + :url => Discourse.base_url, + :potentialAction => { + "@type" => "SearchAction", + :target => "#{Discourse.base_url}/search?q={search_term_string}", + "query-input" => "required name=search_term_string", + }, } - content_tag(:script, MultiJson.dump(json).html_safe, type: 'application/ld+json') + content_tag(:script, MultiJson.dump(json).html_safe, type: "application/ld+json") end end @@ -370,33 +375,35 @@ module ApplicationHelper end def application_logo_url - @application_logo_url ||= begin - if mobile_view? - if dark_color_scheme? && SiteSetting.site_mobile_logo_dark_url.present? - SiteSetting.site_mobile_logo_dark_url - elsif SiteSetting.site_mobile_logo_url.present? - SiteSetting.site_mobile_logo_url - end - else - if dark_color_scheme? && SiteSetting.site_logo_dark_url.present? - SiteSetting.site_logo_dark_url + @application_logo_url ||= + begin + if mobile_view? + if dark_color_scheme? && SiteSetting.site_mobile_logo_dark_url.present? + SiteSetting.site_mobile_logo_dark_url + elsif SiteSetting.site_mobile_logo_url.present? + SiteSetting.site_mobile_logo_url + end else - SiteSetting.site_logo_url + if dark_color_scheme? && SiteSetting.site_logo_dark_url.present? + SiteSetting.site_logo_dark_url + else + SiteSetting.site_logo_url + end end end - end end def application_logo_dark_url - @application_logo_dark_url ||= begin - if dark_scheme_id != -1 - if mobile_view? && SiteSetting.site_mobile_logo_dark_url != application_logo_url - SiteSetting.site_mobile_logo_dark_url - elsif !mobile_view? && SiteSetting.site_logo_dark_url != application_logo_url - SiteSetting.site_logo_dark_url + @application_logo_dark_url ||= + begin + if dark_scheme_id != -1 + if mobile_view? && SiteSetting.site_mobile_logo_dark_url != application_logo_url + SiteSetting.site_mobile_logo_dark_url + elsif !mobile_view? && SiteSetting.site_logo_dark_url != application_logo_url + SiteSetting.site_logo_dark_url + end end end - end end def login_path @@ -437,8 +444,11 @@ module ApplicationHelper def ios_app_argument # argument only makes sense for DiscourseHub app - SiteSetting.ios_app_id == "1173672076" ? - ", app-argument=discourse://new?siteUrl=#{Discourse.base_url}" : "" + if SiteSetting.ios_app_id == "1173672076" + ", app-argument=discourse://new?siteUrl=#{Discourse.base_url}" + else + "" + end end def include_splash_screen? @@ -496,9 +506,9 @@ module ApplicationHelper uri = UrlHelper.encode_and_parse(link) uri = URI.parse("http://#{uri}") if uri.scheme.nil? host = uri.host.downcase - host.start_with?('www.') ? host[4..-1] : host - rescue - '' + host.start_with?("www.") ? host[4..-1] : host + rescue StandardError + "" end end @@ -529,7 +539,8 @@ module ApplicationHelper end def dark_scheme_id - cookies[:dark_scheme_id] || current_user&.user_option&.dark_scheme_id || SiteSetting.default_dark_mode_color_scheme_id + cookies[:dark_scheme_id] || current_user&.user_option&.dark_scheme_id || + SiteSetting.default_dark_mode_color_scheme_id end def current_homepage @@ -556,7 +567,7 @@ module ApplicationHelper theme_id, mobile_view? ? :mobile : :desktop, name, - skip_transformation: request.env[:skip_theme_ids_transformation].present? + skip_transformation: request.env[:skip_theme_ids_transformation].present?, ) end @@ -565,7 +576,7 @@ module ApplicationHelper theme_id, :translations, I18n.locale, - skip_transformation: request.env[:skip_theme_ids_transformation].present? + skip_transformation: request.env[:skip_theme_ids_transformation].present?, ) end @@ -574,42 +585,41 @@ module ApplicationHelper theme_id, :extra_js, nil, - skip_transformation: request.env[:skip_theme_ids_transformation].present? + skip_transformation: request.env[:skip_theme_ids_transformation].present?, ) end def discourse_stylesheet_preload_tag(name, opts = {}) manager = if opts.key?(:theme_id) - Stylesheet::Manager.new( - theme_id: customization_disabled? ? nil : opts[:theme_id] - ) + Stylesheet::Manager.new(theme_id: customization_disabled? ? nil : opts[:theme_id]) else stylesheet_manager end - manager.stylesheet_preload_tag(name, 'all') + manager.stylesheet_preload_tag(name, "all") end def discourse_stylesheet_link_tag(name, opts = {}) manager = if opts.key?(:theme_id) - Stylesheet::Manager.new( - theme_id: customization_disabled? ? nil : opts[:theme_id] - ) + Stylesheet::Manager.new(theme_id: customization_disabled? ? nil : opts[:theme_id]) else stylesheet_manager end - manager.stylesheet_link_tag(name, 'all', self.method(:add_resource_preload_list)) + manager.stylesheet_link_tag(name, "all", self.method(:add_resource_preload_list)) end def discourse_preload_color_scheme_stylesheets result = +"" - result << stylesheet_manager.color_scheme_stylesheet_preload_tag(scheme_id, 'all') + result << stylesheet_manager.color_scheme_stylesheet_preload_tag(scheme_id, "all") if dark_scheme_id != -1 - result << stylesheet_manager.color_scheme_stylesheet_preload_tag(dark_scheme_id, '(prefers-color-scheme: dark)') + result << stylesheet_manager.color_scheme_stylesheet_preload_tag( + dark_scheme_id, + "(prefers-color-scheme: dark)", + ) end result.html_safe @@ -617,10 +627,18 @@ module ApplicationHelper def discourse_color_scheme_stylesheets result = +"" - result << stylesheet_manager.color_scheme_stylesheet_link_tag(scheme_id, 'all', self.method(:add_resource_preload_list)) + result << stylesheet_manager.color_scheme_stylesheet_link_tag( + scheme_id, + "all", + self.method(:add_resource_preload_list), + ) if dark_scheme_id != -1 - result << stylesheet_manager.color_scheme_stylesheet_link_tag(dark_scheme_id, '(prefers-color-scheme: dark)', self.method(:add_resource_preload_list)) + result << stylesheet_manager.color_scheme_stylesheet_link_tag( + dark_scheme_id, + "(prefers-color-scheme: dark)", + self.method(:add_resource_preload_list), + ) end result.html_safe @@ -630,12 +648,12 @@ module ApplicationHelper result = +"" if dark_scheme_id != -1 result << <<~HTML - - + + HTML else result << <<~HTML - + HTML end result.html_safe @@ -647,7 +665,7 @@ module ApplicationHelper end def preloaded_json - return '{}' if @preloaded.blank? + return "{}" if @preloaded.blank? @preloaded.transform_values { |value| escape_unicode(value) }.to_json end @@ -658,8 +676,8 @@ module ApplicationHelper base_uri: Discourse.base_path, environment: Rails.env, letter_avatar_version: LetterAvatar.version, - markdown_it_url: script_asset_path('markdown-it-bundle'), - service_worker_url: 'service-worker.js', + markdown_it_url: script_asset_path("markdown-it-bundle"), + service_worker_url: "service-worker.js", default_locale: SiteSetting.default_locale, asset_version: Discourse.assets_digest, disable_custom_css: loading_admin?, @@ -668,16 +686,14 @@ module ApplicationHelper enable_js_error_reporting: GlobalSetting.enable_js_error_reporting, color_scheme_is_dark: dark_color_scheme?, user_color_scheme_id: scheme_id, - user_dark_scheme_id: dark_scheme_id + user_dark_scheme_id: dark_scheme_id, } if Rails.env.development? setup_data[:svg_icon_list] = SvgSprite.all_icons(theme_id) - if ENV['DEBUG_PRELOADED_APP_DATA'] - setup_data[:debug_preloaded_app_data] = true - end - setup_data[:mb_last_file_change_id] = MessageBus.last_id('/file-change') + setup_data[:debug_preloaded_app_data] = true if ENV["DEBUG_PRELOADED_APP_DATA"] + setup_data[:mb_last_file_change_id] = MessageBus.last_id("/file-change") end if guardian.can_enable_safe_mode? && params["safe_mode"] @@ -694,10 +710,10 @@ module ApplicationHelper def get_absolute_image_url(link) absolute_url = link - if link.start_with?('//') + if link.start_with?("//") uri = URI(Discourse.base_url) absolute_url = "#{uri.scheme}:#{link}" - elsif link.start_with?('/uploads/', '/images/', '/user_avatar/') + elsif link.start_with?("/uploads/", "/images/", "/user_avatar/") absolute_url = "#{Discourse.base_url}#{link}" elsif GlobalSetting.relative_url_root && link.start_with?(GlobalSetting.relative_url_root) absolute_url = "#{Discourse.base_url_no_prefix}#{link}" @@ -713,9 +729,8 @@ module ApplicationHelper end def can_sign_up? - SiteSetting.allow_new_registrations && - !SiteSetting.invite_only && - !SiteSetting.enable_discourse_connect + SiteSetting.allow_new_registrations && !SiteSetting.invite_only && + !SiteSetting.enable_discourse_connect end def rss_creator(user) @@ -725,12 +740,11 @@ module ApplicationHelper def authentication_data return @authentication_data if defined?(@authentication_data) - @authentication_data = begin - value = cookies[:authentication_data] - if value - cookies.delete(:authentication_data, path: Discourse.base_path("/")) + @authentication_data = + begin + value = cookies[:authentication_data] + cookies.delete(:authentication_data, path: Discourse.base_path("/")) if value + current_user ? nil : value end - current_user ? nil : value - end end end diff --git a/app/helpers/email_helper.rb b/app/helpers/email_helper.rb index ba14776aff7..64cb028c535 100644 --- a/app/helpers/email_helper.rb +++ b/app/helpers/email_helper.rb @@ -1,13 +1,15 @@ # frozen_string_literal: true -require 'erb' +require "erb" module EmailHelper - def mailing_list_topic(topic, post_count) render( partial: partial_for("mailing_list_post"), - locals: { topic: topic, post_count: post_count } + locals: { + topic: topic, + post_count: post_count, + }, ) end @@ -26,11 +28,13 @@ module EmailHelper end def email_html_template - EmailStyle.new.html - .sub('%{email_content}') { capture { yield } } - .gsub('%{html_lang}', html_lang) - .gsub('%{dark_mode_meta_tags}', dark_mode_meta_tags) - .gsub('%{dark_mode_styles}', dark_mode_styles) + EmailStyle + .new + .html + .sub("%{email_content}") { capture { yield } } + .gsub("%{html_lang}", html_lang) + .gsub("%{dark_mode_meta_tags}", dark_mode_meta_tags) + .gsub("%{dark_mode_styles}", dark_mode_styles) .html_safe end @@ -118,5 +122,4 @@ module EmailHelper def partial_for(name) SiteSetting.private_email? ? "email/secure_#{name}" : "email/#{name}" end - end diff --git a/app/helpers/embed_helper.rb b/app/helpers/embed_helper.rb index 42e728ddba2..5e133167fbf 100644 --- a/app/helpers/embed_helper.rb +++ b/app/helpers/embed_helper.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module EmbedHelper - def embed_post_date(dt) current = Time.now diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb index 9a1ed6b563f..ea2c1da006e 100644 --- a/app/helpers/posts_helper.rb +++ b/app/helpers/posts_helper.rb @@ -22,9 +22,7 @@ module PostsHelper url = Discourse.redis.get(key) # break cache if either slug or topic_id changes - if url && !url.start_with?(post.topic.url) - url = nil - end + url = nil if url && !url.start_with?(post.topic.url) if !url url = post.canonical_url diff --git a/app/helpers/qunit_helper.rb b/app/helpers/qunit_helper.rb index 3ca1b419bfc..2813ed1e9d6 100644 --- a/app/helpers/qunit_helper.rb +++ b/app/helpers/qunit_helper.rb @@ -6,10 +6,11 @@ module QunitHelper return "" if theme.blank? _, digest = theme.baked_js_tests_with_digest - src = "#{GlobalSetting.cdn_url}" \ - "#{Discourse.base_path}" \ - "/theme-javascripts/tests/#{theme.id}-#{digest}.js" \ - "?__ws=#{Discourse.current_hostname}" + src = + "#{GlobalSetting.cdn_url}" \ + "#{Discourse.base_path}" \ + "/theme-javascripts/tests/#{theme.id}-#{digest}.js" \ + "?__ws=#{Discourse.current_hostname}" "".html_safe end end diff --git a/app/helpers/splash_screen_helper.rb b/app/helpers/splash_screen_helper.rb index 7dfc4194565..af2ec3824d4 100644 --- a/app/helpers/splash_screen_helper.rb +++ b/app/helpers/splash_screen_helper.rb @@ -18,7 +18,10 @@ module SplashScreenHelper private def self.load_js - File.read("#{Rails.root}/app/assets/javascripts/discourse/dist/assets/splash-screen.js").sub("//# sourceMappingURL=splash-screen.map\n", "") + File.read("#{Rails.root}/app/assets/javascripts/discourse/dist/assets/splash-screen.js").sub( + "//# sourceMappingURL=splash-screen.map\n", + "", + ) rescue Errno::ENOENT Rails.logger.error("Unable to load splash screen JS") if Rails.env.production? "console.log('Unable to load splash screen JS')" diff --git a/app/helpers/topic_post_bookmarkable_helper.rb b/app/helpers/topic_post_bookmarkable_helper.rb index 43f5699b876..27bd6935ae9 100644 --- a/app/helpers/topic_post_bookmarkable_helper.rb +++ b/app/helpers/topic_post_bookmarkable_helper.rb @@ -9,7 +9,7 @@ module TopicPostBookmarkableHelper TopicUser.change( user.id, topic.id, - bookmarked: Bookmark.for_user_in_topic(user.id, topic).exists? + bookmarked: Bookmark.for_user_in_topic(user.id, topic).exists?, ) end end diff --git a/app/helpers/topics_helper.rb b/app/helpers/topics_helper.rb index 00204236218..bd595814ae0 100644 --- a/app/helpers/topics_helper.rb +++ b/app/helpers/topics_helper.rb @@ -20,5 +20,4 @@ module TopicsHelper Plugin::Filter.apply(:topic_categories_breadcrumb, topic, breadcrumb) end - end diff --git a/app/helpers/user_notifications_helper.rb b/app/helpers/user_notifications_helper.rb index 6d46583532d..e92dbeccd96 100644 --- a/app/helpers/user_notifications_helper.rb +++ b/app/helpers/user_notifications_helper.rb @@ -6,9 +6,7 @@ module UserNotificationsHelper def indent(text, by = 2) spacer = " " * by result = +"" - text.each_line do |line| - result << spacer << line - end + text.each_line { |line| result << spacer << line } result end @@ -32,24 +30,28 @@ module UserNotificationsHelper end def first_paragraphs_from(html) - doc = Nokogiri::HTML5(html) + doc = Nokogiri.HTML5(html) result = +"" length = 0 - doc.css('body > p, aside.onebox, body > ul, body > blockquote').each do |node| - if node.text.present? - result << node.to_s - length += node.inner_text.length - return result if length >= SiteSetting.digest_min_excerpt_length + doc + .css("body > p, aside.onebox, body > ul, body > blockquote") + .each do |node| + if node.text.present? + result << node.to_s + length += node.inner_text.length + return result if length >= SiteSetting.digest_min_excerpt_length + end end - end return result unless result.blank? # If there is no first paragraph with text, return the first paragraph with # something else (an image) or div (a onebox). - doc.css('body > p:not(:empty), body > div:not(:empty), body > p > div.lightbox-wrapper img').first + doc.css( + "body > p:not(:empty), body > div:not(:empty), body > p > div.lightbox-wrapper img", + ).first end def email_excerpt(html_arg, post = nil) @@ -58,7 +60,7 @@ module UserNotificationsHelper end def normalize_name(name) - name.downcase.gsub(/[\s_-]/, '') + name.downcase.gsub(/[\s_-]/, "") end def show_username_on_post(post) @@ -70,9 +72,7 @@ module UserNotificationsHelper end def show_name_on_post(post) - SiteSetting.enable_names? && - SiteSetting.display_name_on_posts? && - post.user.name.present? && + SiteSetting.enable_names? && SiteSetting.display_name_on_posts? && post.user.name.present? && normalize_name(post.user.name) != normalize_name(post.user.username) end @@ -94,7 +94,7 @@ module UserNotificationsHelper end def show_image_with_url(url) - !(url.nil? || url.downcase.end_with?('svg')) + !(url.nil? || url.downcase.end_with?("svg")) end def email_image_url(basename) @@ -106,5 +106,4 @@ module UserNotificationsHelper rescue URI::Error href end - end diff --git a/app/jobs/base.rb b/app/jobs/base.rb index d38e57fcd26..14255c53310 100644 --- a/app/jobs/base.rb +++ b/app/jobs/base.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - def self.queued Sidekiq::Stats.new.enqueued end @@ -24,7 +23,7 @@ module Jobs def self.last_job_performed_at Sidekiq.redis do |r| - int = r.get('last_job_perform_at') + int = r.get("last_job_perform_at") int ? Time.at(int.to_i) : nil end end @@ -82,9 +81,9 @@ module Jobs if exception.present? @data["exception"] = exception # Exception - if job fails a json encoded exception - @data["status"] = 'failed' + @data["status"] = "failed" else - @data["status"] = 'success' # Status - fail, success, pending + @data["status"] = "success" # Status - fail, success, pending end write_to_log @@ -92,22 +91,27 @@ module Jobs end def self.raw_log(message) - @@logger ||= begin - f = File.open "#{Rails.root}/log/sidekiq.log", "a" - f.sync = true - Logger.new f - end + @@logger ||= + begin + f = File.open "#{Rails.root}/log/sidekiq.log", "a" + f.sync = true + Logger.new f + end @@log_queue ||= Queue.new if !defined?(@@log_thread) || !@@log_thread.alive? - @@log_thread = Thread.new do - loop do - @@logger << @@log_queue.pop - rescue Exception => e - Discourse.warn_exception(e, message: "Exception encountered while logging Sidekiq job") + @@log_thread = + Thread.new do + loop do + @@logger << @@log_queue.pop + rescue Exception => e + Discourse.warn_exception( + e, + message: "Exception encountered while logging Sidekiq job", + ) + end end - end end @@log_queue.push(message) @@ -136,18 +140,22 @@ module Jobs interval = ENV["DISCOURSE_LOG_SIDEKIQ_INTERVAL"] return if !interval interval = interval.to_i - @@interval_thread ||= Thread.new do - begin - loop do - sleep interval - mutex.synchronize do - @@active_jobs.each { |j| j.write_to_log if j.current_duration > interval } + @@interval_thread ||= + Thread.new do + begin + loop do + sleep interval + mutex.synchronize do + @@active_jobs.each { |j| j.write_to_log if j.current_duration > interval } + end end + rescue Exception => e + Discourse.warn_exception( + e, + message: "Sidekiq interval logging thread terminated unexpectedly", + ) end - rescue Exception => e - Discourse.warn_exception(e, message: "Sidekiq interval logging thread terminated unexpectedly") end - end end end @@ -192,15 +200,14 @@ module Jobs def perform(*args) opts = args.extract_options!.with_indifferent_access - if ::Jobs.run_later? - Sidekiq.redis do |r| - r.set('last_job_perform_at', Time.now.to_i) - end - end + Sidekiq.redis { |r| r.set("last_job_perform_at", Time.now.to_i) } if ::Jobs.run_later? if opts.delete(:sync_exec) - if opts.has_key?(:current_site_id) && opts[:current_site_id] != RailsMultisite::ConnectionManagement.current_db - raise ArgumentError.new("You can't connect to another database when executing a job synchronously.") + if opts.has_key?(:current_site_id) && + opts[:current_site_id] != RailsMultisite::ConnectionManagement.current_db + raise ArgumentError.new( + "You can't connect to another database when executing a job synchronously.", + ) else begin retval = execute(opts) @@ -224,9 +231,11 @@ module Jobs exception = {} RailsMultisite::ConnectionManagement.with_connection(db) do - job_instrumenter = JobInstrumenter.new(job_class: self.class, opts: opts, db: db, jid: jid) + job_instrumenter = + JobInstrumenter.new(job_class: self.class, opts: opts, db: db, jid: jid) begin - I18n.locale = SiteSetting.default_locale || SiteSettings::DefaultsProvider::DEFAULT_LOCALE + I18n.locale = + SiteSetting.default_locale || SiteSettings::DefaultsProvider::DEFAULT_LOCALE I18n.ensure_all_loaded! begin logster_env = {} @@ -256,7 +265,10 @@ module Jobs if exceptions.length > 0 exceptions.each do |exception_hash| - Discourse.handle_job_exception(exception_hash[:ex], error_context(opts, exception_hash[:code], exception_hash[:other])) + Discourse.handle_job_exception( + exception_hash[:ex], + error_context(opts, exception_hash[:code], exception_hash[:other]), + ) end raise HandledExceptionWrapper.new(exceptions[0][:ex]) end @@ -265,7 +277,6 @@ module Jobs ensure ActiveRecord::Base.connection_handler.clear_active_connections! end - end class HandledExceptionWrapper < StandardError @@ -280,9 +291,7 @@ module Jobs extend MiniScheduler::Schedule def perform(*args) - if (::Jobs::Heartbeat === self) || !Discourse.readonly_mode? - super - end + super if (::Jobs::Heartbeat === self) || !Discourse.readonly_mode? end end @@ -307,30 +316,21 @@ module Jobs # Simulate the args being dumped/parsed through JSON parsed_opts = JSON.parse(JSON.dump(opts)) - if opts != parsed_opts - Discourse.deprecate(<<~TEXT.squish, since: "2.9", drop_from: "3.0") + Discourse.deprecate(<<~TEXT.squish, since: "2.9", drop_from: "3.0") if opts != parsed_opts #{klass.name} was enqueued with argument values which do not cleanly serialize to/from JSON. This means that the job will be run with slightly different values than the ones supplied to `enqueue`. Argument values should be strings, booleans, numbers, or nil (or arrays/hashes of those value types). TEXT - end opts = parsed_opts if ::Jobs.run_later? - hash = { - 'class' => klass, - 'args' => [opts] - } + hash = { "class" => klass, "args" => [opts] } if delay - if delay.to_f > 0 - hash['at'] = Time.now.to_f + delay.to_f - end + hash["at"] = Time.now.to_f + delay.to_f if delay.to_f > 0 end - if queue - hash['queue'] = queue - end + hash["queue"] = queue if queue DB.after_commit { klass.client_push(hash) } else @@ -338,9 +338,7 @@ module Jobs opts["sync_exec"] = true if Rails.env == "development" - Scheduler::Defer.later("job") do - klass.new.perform(opts) - end + Scheduler::Defer.later("job") { klass.new.perform(opts) } else # Run the job synchronously # But never run a job inside another job @@ -368,7 +366,6 @@ module Jobs end end end - end def self.enqueue_in(secs, job_name, opts = {}) diff --git a/app/jobs/concerns/skippable.rb b/app/jobs/concerns/skippable.rb index 30d176e2312..dfc6adf2231 100644 --- a/app/jobs/concerns/skippable.rb +++ b/app/jobs/concerns/skippable.rb @@ -3,24 +3,22 @@ module Skippable extend ActiveSupport::Concern - def create_skipped_email_log(email_type:, - to_address:, - user_id:, - post_id:, - reason_type:) - + def create_skipped_email_log(email_type:, to_address:, user_id:, post_id:, reason_type:) attributes = { email_type: email_type, to_address: to_address, user_id: user_id, post_id: post_id, - reason_type: reason_type + reason_type: reason_type, } if reason_type == SkippedEmailLog.reason_types[:exceeded_emails_limit] - exists = SkippedEmailLog.exists?({ - created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day) - }.merge!(attributes.except(:post_id))) + exists = + SkippedEmailLog.exists?( + { created_at: (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day) }.merge!( + attributes.except(:post_id), + ), + ) return if exists end diff --git a/app/jobs/onceoff/clean_up_post_timings.rb b/app/jobs/onceoff/clean_up_post_timings.rb index 1399ddaa806..0e4088518c2 100644 --- a/app/jobs/onceoff/clean_up_post_timings.rb +++ b/app/jobs/onceoff/clean_up_post_timings.rb @@ -2,7 +2,6 @@ module Jobs class CleanUpPostTimings < ::Jobs::Onceoff - # Remove post timings that are remnants of previous post moves # or other shenanigans and don't reference a valid user or post anymore. def execute_onceoff(args) diff --git a/app/jobs/onceoff/clean_up_sidekiq_statistic.rb b/app/jobs/onceoff/clean_up_sidekiq_statistic.rb index 7b51cde58b5..74147a00915 100644 --- a/app/jobs/onceoff/clean_up_sidekiq_statistic.rb +++ b/app/jobs/onceoff/clean_up_sidekiq_statistic.rb @@ -3,7 +3,7 @@ module Jobs class CleanUpSidekiqStatistic < ::Jobs::Onceoff def execute_onceoff(args) - Discourse.redis.without_namespace.del('sidekiq:sidekiq:statistic') + Discourse.redis.without_namespace.del("sidekiq:sidekiq:statistic") end end end diff --git a/app/jobs/onceoff/clean_up_user_export_topics.rb b/app/jobs/onceoff/clean_up_user_export_topics.rb index 8e5be5bf27b..e6d62c87c5f 100644 --- a/app/jobs/onceoff/clean_up_user_export_topics.rb +++ b/app/jobs/onceoff/clean_up_user_export_topics.rb @@ -3,14 +3,18 @@ module Jobs class CleanUpUserExportTopics < ::Jobs::Onceoff def execute_onceoff(args) - translated_keys = I18n.available_locales.map do |l| - I18n.with_locale(:"#{l}") { I18n.t("system_messages.csv_export_succeeded.subject_template") } - end.uniq + translated_keys = + I18n + .available_locales + .map do |l| + I18n.with_locale(:"#{l}") do + I18n.t("system_messages.csv_export_succeeded.subject_template") + end + end + .uniq slugs = [] - translated_keys.each do |k| - slugs << "%-#{Slug.for(k.gsub('[%{export_title}]', ''))}" - end + translated_keys.each { |k| slugs << "%-#{Slug.for(k.gsub("[%{export_title}]", ""))}" } # "[%{export_title}] 資料匯出已完成" gets converted to "%-topic", do not match that slug. slugs = slugs.reject { |s| s == "%-topic" } diff --git a/app/jobs/onceoff/correct_missing_dualstack_urls.rb b/app/jobs/onceoff/correct_missing_dualstack_urls.rb index 6cdfa55e847..d96b8ac0227 100644 --- a/app/jobs/onceoff/correct_missing_dualstack_urls.rb +++ b/app/jobs/onceoff/correct_missing_dualstack_urls.rb @@ -9,7 +9,7 @@ module Jobs return if !base_url.match?(/s3\.dualstack/) - old = base_url.sub('s3.dualstack.', 's3-') + old = base_url.sub("s3.dualstack.", "s3-") old_like = %"#{old}%" DB.exec(<<~SQL, from: old, to: base_url, old_like: old_like) diff --git a/app/jobs/onceoff/create_tags_search_index.rb b/app/jobs/onceoff/create_tags_search_index.rb index f7617074260..a0bea42331b 100644 --- a/app/jobs/onceoff/create_tags_search_index.rb +++ b/app/jobs/onceoff/create_tags_search_index.rb @@ -3,9 +3,9 @@ module Jobs class CreateTagsSearchIndex < ::Jobs::Onceoff def execute_onceoff(args) - DB.query('select id, name from tags').each do |t| - SearchIndexer.update_tags_index(t.id, t.name) - end + DB + .query("select id, name from tags") + .each { |t| SearchIndexer.update_tags_index(t.id, t.name) } end end end diff --git a/app/jobs/onceoff/fix_encoded_category_slugs.rb b/app/jobs/onceoff/fix_encoded_category_slugs.rb index 28947125d90..2af1b6f759a 100644 --- a/app/jobs/onceoff/fix_encoded_category_slugs.rb +++ b/app/jobs/onceoff/fix_encoded_category_slugs.rb @@ -1,17 +1,18 @@ # frozen_string_literal: true module Jobs - class FixEncodedCategorySlugs < ::Jobs::Onceoff def execute_onceoff(args) - return unless SiteSetting.slug_generation_method == 'encoded' + return unless SiteSetting.slug_generation_method == "encoded" #Make custom categories slugs nil and let the app regenerate with proper encoded ones - Category.all.reject { |c| c.seeded? }.each do |c| - c.slug = nil - c.save! - end + Category + .all + .reject { |c| c.seeded? } + .each do |c| + c.slug = nil + c.save! + end end end - end diff --git a/app/jobs/onceoff/fix_encoded_topic_slugs.rb b/app/jobs/onceoff/fix_encoded_topic_slugs.rb index 4640493fb9a..f7463a58d6a 100644 --- a/app/jobs/onceoff/fix_encoded_topic_slugs.rb +++ b/app/jobs/onceoff/fix_encoded_topic_slugs.rb @@ -1,14 +1,12 @@ # frozen_string_literal: true module Jobs - class FixEncodedTopicSlugs < ::Jobs::Onceoff def execute_onceoff(args) - return unless SiteSetting.slug_generation_method == 'encoded' + return unless SiteSetting.slug_generation_method == "encoded" #Make all slugs nil and let the app regenerate with proper encoded ones Topic.update_all(slug: nil) end end - end diff --git a/app/jobs/onceoff/fix_featured_link_for_topics.rb b/app/jobs/onceoff/fix_featured_link_for_topics.rb index 337d7d2efae..0b4f67ab9f0 100644 --- a/app/jobs/onceoff/fix_featured_link_for_topics.rb +++ b/app/jobs/onceoff/fix_featured_link_for_topics.rb @@ -3,15 +3,17 @@ module Jobs class FixFeaturedLinkForTopics < ::Jobs::Onceoff def execute_onceoff(args) - Topic.where("featured_link IS NOT NULL").find_each do |topic| - featured_link = topic.featured_link + Topic + .where("featured_link IS NOT NULL") + .find_each do |topic| + featured_link = topic.featured_link - begin - URI.parse(featured_link) - rescue URI::Error - topic.update(featured_link: URI.extract(featured_link).first) + begin + URI.parse(featured_link) + rescue URI::Error + topic.update(featured_link: URI.extract(featured_link).first) + end end - end end end end diff --git a/app/jobs/onceoff/fix_invalid_gravatar_uploads.rb b/app/jobs/onceoff/fix_invalid_gravatar_uploads.rb index 515b8058717..2171c2d03fd 100644 --- a/app/jobs/onceoff/fix_invalid_gravatar_uploads.rb +++ b/app/jobs/onceoff/fix_invalid_gravatar_uploads.rb @@ -3,16 +3,23 @@ module Jobs class FixInvalidGravatarUploads < ::Jobs::Onceoff def execute_onceoff(args) - Upload.where(original_filename: "gravatar.png").find_each do |upload| - # note, this still feels pretty expensive for a once off - # we may need to re-evaluate this - extension = FastImage.type(Discourse.store.path_for(upload)) rescue nil - current_extension = upload.extension + Upload + .where(original_filename: "gravatar.png") + .find_each do |upload| + # note, this still feels pretty expensive for a once off + # we may need to re-evaluate this + extension = + begin + FastImage.type(Discourse.store.path_for(upload)) + rescue StandardError + nil + end + current_extension = upload.extension - if extension.to_s.downcase != current_extension.to_s.downcase - upload&.user&.user_avatar&.update_columns(last_gravatar_download_attempt: nil) + if extension.to_s.downcase != current_extension.to_s.downcase + upload&.user&.user_avatar&.update_columns(last_gravatar_download_attempt: nil) + end end - end end end end diff --git a/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb b/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb index 5fb9ff218ef..72f171d1943 100644 --- a/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb +++ b/app/jobs/onceoff/fix_primary_emails_for_staged_users.rb @@ -7,22 +7,22 @@ module Jobs acting_user = Discourse.system_user destroyer = UserDestroyer.new(acting_user) - users.group("email_tokens.email") + users + .group("email_tokens.email") .having("COUNT(email_tokens.email) > 1") .count .each_key do |email| + query = users.where("email_tokens.email = ?", email).order(id: :asc) - query = users.where("email_tokens.email = ?", email).order(id: :asc) + original_user = query.first - original_user = query.first - - query.offset(1).each do |user| - user.posts.each do |post| - post.set_owner(original_user, acting_user) - end - destroyer.destroy(user, context: I18n.t("user.destroy_reasons.fixed_primary_email")) + query + .offset(1) + .each do |user| + user.posts.each { |post| post.set_owner(original_user, acting_user) } + destroyer.destroy(user, context: I18n.t("user.destroy_reasons.fixed_primary_email")) + end end - end DB.exec <<~SQL INSERT INTO user_emails ( diff --git a/app/jobs/onceoff/fix_retro_anniversary.rb b/app/jobs/onceoff/fix_retro_anniversary.rb index a6ab1c566f4..92eaeb8011a 100644 --- a/app/jobs/onceoff/fix_retro_anniversary.rb +++ b/app/jobs/onceoff/fix_retro_anniversary.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class FixRetroAnniversary < ::Jobs::Onceoff def execute_onceoff(args) return unless SiteSetting.enable_badges @@ -16,19 +15,19 @@ module Jobs users.each do |u| first = u.first_granted_at - badges = UserBadge.where( - "badge_id = ? AND user_id = ? AND granted_at > ?", - Badge::Anniversary, - u.user_id, - first - ).order('granted_at') + badges = + UserBadge.where( + "badge_id = ? AND user_id = ? AND granted_at > ?", + Badge::Anniversary, + u.user_id, + first, + ).order("granted_at") badges.each_with_index do |b, idx| award_date = (first + (idx + 1).years) UserBadge.where(id: b.id).update_all(["granted_at = ?", award_date]) end end - end end end diff --git a/app/jobs/onceoff/fix_s3_etags.rb b/app/jobs/onceoff/fix_s3_etags.rb index 226b09e9372..156b3b2ebbc 100644 --- a/app/jobs/onceoff/fix_s3_etags.rb +++ b/app/jobs/onceoff/fix_s3_etags.rb @@ -2,10 +2,10 @@ module Jobs class FixS3Etags < ::Jobs::Onceoff - def execute_onceoff(args) [Upload, OptimizedImage].each do |model| - sql = "UPDATE #{model.table_name} SET etag = REGEXP_REPLACE(etag, '\"', '', 'g') WHERE etag LIKE '\"%\"'" + sql = + "UPDATE #{model.table_name} SET etag = REGEXP_REPLACE(etag, '\"', '', 'g') WHERE etag LIKE '\"%\"'" DB.exec(sql) end end diff --git a/app/jobs/onceoff/grant_emoji.rb b/app/jobs/onceoff/grant_emoji.rb index 5abdb34d5d4..40efada65ce 100644 --- a/app/jobs/onceoff/grant_emoji.rb +++ b/app/jobs/onceoff/grant_emoji.rb @@ -1,25 +1,25 @@ # frozen_string_literal: true module Jobs - class GrantEmoji < ::Jobs::Onceoff def execute_onceoff(args) return unless SiteSetting.enable_badges to_award = {} - Post.secured(Guardian.new) + Post + .secured(Guardian.new) .select(:id, :created_at, :cooked, :user_id) .visible .public_posts .where("cooked LIKE '%emoji%'") .find_in_batches do |group| - group.each do |p| - doc = Nokogiri::HTML5::fragment(p.cooked) - if (doc.css("img.emoji") - doc.css(".quote img")).size > 0 - to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } + group.each do |p| + doc = Nokogiri::HTML5.fragment(p.cooked) + if (doc.css("img.emoji") - doc.css(".quote img")).size > 0 + to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } + end end end - end to_award.each do |user_id, opts| user = User.where(id: user_id).first @@ -30,7 +30,5 @@ module Jobs def badge Badge.find(Badge::FirstEmoji) end - end - end diff --git a/app/jobs/onceoff/grant_first_reply_by_email.rb b/app/jobs/onceoff/grant_first_reply_by_email.rb index c0287205d76..0f8129077c2 100644 --- a/app/jobs/onceoff/grant_first_reply_by_email.rb +++ b/app/jobs/onceoff/grant_first_reply_by_email.rb @@ -1,24 +1,21 @@ # frozen_string_literal: true module Jobs - class GrantFirstReplyByEmail < ::Jobs::Onceoff - def execute_onceoff(args) return unless SiteSetting.enable_badges to_award = {} - Post.select(:id, :created_at, :user_id) + Post + .select(:id, :created_at, :user_id) .secured(Guardian.new) .visible .public_posts .where(via_email: true) .where("post_number > 1") .find_in_batches do |group| - group.each do |p| - to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } + group.each { |p| to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } } end - end to_award.each do |user_id, opts| user = User.where(id: user_id).first @@ -29,7 +26,5 @@ module Jobs def badge Badge.find(Badge::FirstReplyByEmail) end - end - end diff --git a/app/jobs/onceoff/grant_onebox.rb b/app/jobs/onceoff/grant_onebox.rb index 66d2cf26706..5eb6ec0538f 100644 --- a/app/jobs/onceoff/grant_onebox.rb +++ b/app/jobs/onceoff/grant_onebox.rb @@ -1,35 +1,34 @@ # frozen_string_literal: true module Jobs - class GrantOnebox < ::Jobs::Onceoff - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute_onceoff(args) return unless SiteSetting.enable_badges to_award = {} - Post.secured(Guardian.new) + Post + .secured(Guardian.new) .select(:id, :created_at, :raw, :user_id) .visible .public_posts .where("raw LIKE '%http%'") .find_in_batches do |group| - group.each do |p| - begin - # Note we can't use `p.cooked` here because oneboxes have been cooked out - cooked = PrettyText.cook(p.raw) - doc = Nokogiri::HTML5::fragment(cooked) - if doc.search('a.onebox').size > 0 - to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } + group.each do |p| + begin + # Note we can't use `p.cooked` here because oneboxes have been cooked out + cooked = PrettyText.cook(p.raw) + doc = Nokogiri::HTML5.fragment(cooked) + if doc.search("a.onebox").size > 0 + to_award[p.user_id] ||= { post_id: p.id, created_at: p.created_at } + end + rescue StandardError + nil # if there is a problem cooking we don't care end - rescue - nil # if there is a problem cooking we don't care end end - end - to_award.each do |user_id, opts| user = User.where(id: user_id).first BadgeGranter.grant(badge, user, opts) if user @@ -39,7 +38,5 @@ module Jobs def badge Badge.find(Badge::FirstOnebox) end - end - end diff --git a/app/jobs/onceoff/migrate_badge_image_to_uploads.rb b/app/jobs/onceoff/migrate_badge_image_to_uploads.rb index 5585a508c2f..b45595498ff 100644 --- a/app/jobs/onceoff/migrate_badge_image_to_uploads.rb +++ b/app/jobs/onceoff/migrate_badge_image_to_uploads.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'uri' +require "uri" module Jobs class MigrateBadgeImageToUploads < ::Jobs::Onceoff @@ -14,72 +14,79 @@ module Jobs SQL return unless column_exists - Badge.where.not(image: nil).select(:id, :image_upload_id, :image).each do |badge| - if badge.image_upload.present? - DB.exec("UPDATE badges SET image = NULL WHERE id = ?", badge.id) - next - end - - image_url = badge[:image] - next if image_url.blank? || image_url !~ URI.regexp - - count = 0 - file = nil - sleep_interval = 5 - - loop do - url = UrlHelper.absolute_without_cdn(image_url) - - begin - file = FileHelper.download( - url, - max_file_size: [ - SiteSetting.max_image_size_kb.kilobytes, - 20.megabytes - ].max, - tmp_file_name: 'tmp_badge_image_upload', - skip_rate_limit: true, - follow_redirect: true - ) - rescue OpenURI::HTTPError, - OpenSSL::SSL::SSLError, - Net::OpenTimeout, - Net::ReadTimeout, - Errno::ECONNREFUSED, - EOFError, - SocketError, - Discourse::InvalidParameters => e - - logger.error( - "Error encountered when trying to download from URL '#{image_url}' " + - "for badge '#{badge[:id]}'.\n#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}" - ) + Badge + .where.not(image: nil) + .select(:id, :image_upload_id, :image) + .each do |badge| + if badge.image_upload.present? + DB.exec("UPDATE badges SET image = NULL WHERE id = ?", badge.id) + next end - count += 1 - break if file + image_url = badge[:image] + next if image_url.blank? || image_url !~ URI.regexp - logger.warn( - "Failed to download image from #{url} for badge '#{badge[:id]}'. Retrying (#{count}/3)..." - ) - break if count >= 3 - sleep(count * sleep_interval) + count = 0 + file = nil + sleep_interval = 5 + + loop do + url = UrlHelper.absolute_without_cdn(image_url) + + begin + file = + FileHelper.download( + url, + max_file_size: [SiteSetting.max_image_size_kb.kilobytes, 20.megabytes].max, + tmp_file_name: "tmp_badge_image_upload", + skip_rate_limit: true, + follow_redirect: true, + ) + rescue OpenURI::HTTPError, + OpenSSL::SSL::SSLError, + Net::OpenTimeout, + Net::ReadTimeout, + Errno::ECONNREFUSED, + EOFError, + SocketError, + Discourse::InvalidParameters => e + logger.error( + "Error encountered when trying to download from URL '#{image_url}' " + + "for badge '#{badge[:id]}'.\n#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}", + ) + end + + count += 1 + break if file + + logger.warn( + "Failed to download image from #{url} for badge '#{badge[:id]}'. Retrying (#{count}/3)...", + ) + break if count >= 3 + sleep(count * sleep_interval) + end + + next if file.blank? + + upload = + UploadCreator.new( + file, + "image_for_badge_#{badge[:id]}", + origin: UrlHelper.absolute(image_url), + ).create_for(Discourse.system_user.id) + + if upload.errors.count > 0 || upload&.id.blank? + logger.error( + "Failed to create an upload for the image of badge '#{badge[:id]}'. Error: #{upload.errors.full_messages}", + ) + else + DB.exec( + "UPDATE badges SET image = NULL, image_upload_id = ? WHERE id = ?", + upload.id, + badge[:id], + ) + end end - - next if file.blank? - - upload = UploadCreator.new( - file, - "image_for_badge_#{badge[:id]}", - origin: UrlHelper.absolute(image_url) - ).create_for(Discourse.system_user.id) - - if upload.errors.count > 0 || upload&.id.blank? - logger.error("Failed to create an upload for the image of badge '#{badge[:id]}'. Error: #{upload.errors.full_messages}") - else - DB.exec("UPDATE badges SET image = NULL, image_upload_id = ? WHERE id = ?", upload.id, badge[:id]) - end - end end private diff --git a/app/jobs/onceoff/migrate_censored_words.rb b/app/jobs/onceoff/migrate_censored_words.rb index 5de876107be..605321d94cd 100644 --- a/app/jobs/onceoff/migrate_censored_words.rb +++ b/app/jobs/onceoff/migrate_censored_words.rb @@ -5,9 +5,10 @@ module Jobs def execute_onceoff(args) row = DB.query_single("SELECT value FROM site_settings WHERE name = 'censored_words'") if row.count > 0 - row.first.split('|').each do |word| - WatchedWord.create(word: word, action: WatchedWord.actions[:censor]) - end + row + .first + .split("|") + .each { |word| WatchedWord.create(word: word, action: WatchedWord.actions[:censor]) } end end end diff --git a/app/jobs/onceoff/migrate_custom_emojis.rb b/app/jobs/onceoff/migrate_custom_emojis.rb index f359a23c999..62fdc6da450 100644 --- a/app/jobs/onceoff/migrate_custom_emojis.rb +++ b/app/jobs/onceoff/migrate_custom_emojis.rb @@ -9,11 +9,10 @@ module Jobs name = File.basename(path, File.extname(path)) File.open(path) do |file| - upload = UploadCreator.new( - file, - File.basename(path), - type: 'custom_emoji' - ).create_for(Discourse.system_user.id) + upload = + UploadCreator.new(file, File.basename(path), type: "custom_emoji").create_for( + Discourse.system_user.id, + ) if upload.persisted? custom_emoji = CustomEmoji.new(name: name, upload: upload) @@ -22,16 +21,16 @@ module Jobs warn("Failed to create custom emoji '#{name}': #{custom_emoji.errors.full_messages}") end else - warn("Failed to create upload for '#{name}' custom emoji: #{upload.errors.full_messages}") + warn( + "Failed to create upload for '#{name}' custom emoji: #{upload.errors.full_messages}", + ) end end end Emoji.clear_cache - Post.where("cooked LIKE ?", "%#{Emoji.base_url}%").find_each do |post| - post.rebake! - end + Post.where("cooked LIKE ?", "%#{Emoji.base_url}%").find_each { |post| post.rebake! } end def warn(message) diff --git a/app/jobs/onceoff/migrate_featured_links.rb b/app/jobs/onceoff/migrate_featured_links.rb index 45f4c11defe..9139854fccf 100644 --- a/app/jobs/onceoff/migrate_featured_links.rb +++ b/app/jobs/onceoff/migrate_featured_links.rb @@ -1,30 +1,35 @@ # frozen_string_literal: true module Jobs - class MigrateFeaturedLinks < ::Jobs::Onceoff - def execute_onceoff(args) - TopicCustomField.where(name: "featured_link").find_each do |tcf| - if tcf.value.present? - Topic.where(id: tcf.topic_id).update_all(featured_link: tcf.value) + TopicCustomField + .where(name: "featured_link") + .find_each do |tcf| + Topic.where(id: tcf.topic_id).update_all(featured_link: tcf.value) if tcf.value.present? end - end # Plugin behaviour: only categories explicitly allowed to have featured links can have them. # All others implicitly DO NOT allow them. # If no categories were explicitly allowed to have them, then all implicitly DID allow them. - allowed = CategoryCustomField.where(name: "topic_featured_link_allowed").where(value: "true").pluck(:category_id) + allowed = + CategoryCustomField + .where(name: "topic_featured_link_allowed") + .where(value: "true") + .pluck(:category_id) if !allowed.empty? # all others are not allowed Category.where.not(id: allowed).update_all(topic_featured_link_allowed: false) else - not_allowed = CategoryCustomField.where(name: "topic_featured_link_allowed").where.not(value: "true").pluck(:category_id) + not_allowed = + CategoryCustomField + .where(name: "topic_featured_link_allowed") + .where.not(value: "true") + .pluck(:category_id) Category.where(id: not_allowed).update_all(topic_featured_link_allowed: false) end end end - end diff --git a/app/jobs/onceoff/migrate_tagging_plugin.rb b/app/jobs/onceoff/migrate_tagging_plugin.rb index d92d5692f48..8420b7543f9 100644 --- a/app/jobs/onceoff/migrate_tagging_plugin.rb +++ b/app/jobs/onceoff/migrate_tagging_plugin.rb @@ -1,18 +1,25 @@ # frozen_string_literal: true module Jobs - class MigrateTaggingPlugin < ::Jobs::Onceoff - def execute_onceoff(args) - all_tags = TopicCustomField.where(name: "tags").select('DISTINCT value').all.map(&:value) - tag_id_lookup = Tag.create(all_tags.map { |tag_name| { name: tag_name } }).inject({}) { |h, v| h[v.name] = v.id; h } + all_tags = TopicCustomField.where(name: "tags").select("DISTINCT value").all.map(&:value) + tag_id_lookup = + Tag + .create(all_tags.map { |tag_name| { name: tag_name } }) + .inject({}) do |h, v| + h[v.name] = v.id + h + end - TopicCustomField.where(name: "tags").find_each do |tcf| - TopicTag.create(topic_id: tcf.topic_id, tag_id: tag_id_lookup[tcf.value] || Tag.find_by_name(tcf.value).try(:id)) - end + TopicCustomField + .where(name: "tags") + .find_each do |tcf| + TopicTag.create( + topic_id: tcf.topic_id, + tag_id: tag_id_lookup[tcf.value] || Tag.find_by_name(tcf.value).try(:id), + ) + end end - end - end diff --git a/app/jobs/onceoff/migrate_upload_extensions.rb b/app/jobs/onceoff/migrate_upload_extensions.rb index 846944e285e..fd6eb72a43c 100644 --- a/app/jobs/onceoff/migrate_upload_extensions.rb +++ b/app/jobs/onceoff/migrate_upload_extensions.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class MigrateUploadExtensions < ::Jobs::Onceoff def execute_onceoff(args) Upload.find_each do |upload| diff --git a/app/jobs/onceoff/onceoff.rb b/app/jobs/onceoff/onceoff.rb index d10b1f41a66..df9c94e54d0 100644 --- a/app/jobs/onceoff/onceoff.rb +++ b/app/jobs/onceoff/onceoff.rb @@ -4,7 +4,7 @@ class Jobs::Onceoff < ::Jobs::Base sidekiq_options retry: false def self.name_for(klass) - klass.name.sub(/^Jobs\:\:/, '') + klass.name.sub(/^Jobs\:\:/, "") end def running_key_name @@ -26,18 +26,17 @@ class Jobs::Onceoff < ::Jobs::Base Discourse.redis.del(running_key_name) if has_lock end end - end def self.enqueue_all previously_ran = OnceoffLog.pluck(:job_name).uniq - ObjectSpace.each_object(Class).select { |klass| klass < self }.each do |klass| - job_name = name_for(klass) - unless previously_ran.include?(job_name) - Jobs.enqueue(job_name.underscore.to_sym) + ObjectSpace + .each_object(Class) + .select { |klass| klass < self } + .each do |klass| + job_name = name_for(klass) + Jobs.enqueue(job_name.underscore.to_sym) unless previously_ran.include?(job_name) end - end end - end diff --git a/app/jobs/onceoff/post_uploads_recovery.rb b/app/jobs/onceoff/post_uploads_recovery.rb index 5f942209eb1..75d0b42c7bc 100644 --- a/app/jobs/onceoff/post_uploads_recovery.rb +++ b/app/jobs/onceoff/post_uploads_recovery.rb @@ -6,17 +6,11 @@ module Jobs MAX_PERIOD = 120 def execute_onceoff(args) - UploadRecovery.new.recover(Post.where( - "baked_at >= ?", - grace_period.days.ago - )) + UploadRecovery.new.recover(Post.where("baked_at >= ?", grace_period.days.ago)) end def grace_period - SiteSetting.purge_deleted_uploads_grace_period_days.clamp( - MIN_PERIOD, - MAX_PERIOD - ) + SiteSetting.purge_deleted_uploads_grace_period_days.clamp(MIN_PERIOD, MAX_PERIOD) end end end diff --git a/app/jobs/onceoff/retro_grant_anniversary.rb b/app/jobs/onceoff/retro_grant_anniversary.rb index b0ca206bff9..cba25214b30 100644 --- a/app/jobs/onceoff/retro_grant_anniversary.rb +++ b/app/jobs/onceoff/retro_grant_anniversary.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true module Jobs - class RetroGrantAnniversary < ::Jobs::Onceoff def execute_onceoff(args) return unless SiteSetting.enable_badges # Fill in the years of anniversary badges we missed - (2..3).each do |year| - ::Jobs::GrantAnniversaryBadges.new.execute(start_date: year.years.ago) - end + (2..3).each { |year| ::Jobs::GrantAnniversaryBadges.new.execute(start_date: year.years.ago) } end end - end diff --git a/app/jobs/regular/admin_confirmation_email.rb b/app/jobs/regular/admin_confirmation_email.rb index 48746de4084..89ab3a4f2fc 100644 --- a/app/jobs/regular/admin_confirmation_email.rb +++ b/app/jobs/regular/admin_confirmation_email.rb @@ -2,7 +2,7 @@ module Jobs class AdminConfirmationEmail < ::Jobs::Base - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" def execute(args) to_address = args[:to_address] @@ -18,6 +18,5 @@ module Jobs message = AdminConfirmationMailer.send_email(to_address, target_email, target_username, token) Email::Sender.new(message, :admin_confirmation_message).send end - end end diff --git a/app/jobs/regular/anonymize_user.rb b/app/jobs/regular/anonymize_user.rb index 7343163e49b..7ae14c11732 100644 --- a/app/jobs/regular/anonymize_user.rb +++ b/app/jobs/regular/anonymize_user.rb @@ -2,8 +2,7 @@ module Jobs class AnonymizeUser < ::Jobs::Base - - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) @user_id = args[:user_id] @@ -22,7 +21,8 @@ module Jobs EmailLog.where(user_id: @user_id).delete_all IncomingEmail.where("user_id = ? OR from_address = ?", @user_id, @prev_email).delete_all - Post.with_deleted + Post + .with_deleted .where(user_id: @user_id) .where.not(raw_email: nil) .update_all(raw_email: nil) @@ -30,17 +30,17 @@ module Jobs anonymize_user_fields end - def ip_where(column = 'user_id') + def ip_where(column = "user_id") ["#{column} = :user_id AND ip_address IS NOT NULL", user_id: @user_id] end def anonymize_ips(new_ip) - IncomingLink.where(ip_where('current_user_id')).update_all(ip_address: new_ip) + IncomingLink.where(ip_where("current_user_id")).update_all(ip_address: new_ip) ScreenedEmail.where(email: @prev_email).update_all(ip_address: new_ip) SearchLog.where(ip_where).update_all(ip_address: new_ip) TopicLinkClick.where(ip_where).update_all(ip_address: new_ip) TopicViewItem.where(ip_where).update_all(ip_address: new_ip) - UserHistory.where(ip_where('acting_user_id')).update_all(ip_address: new_ip) + UserHistory.where(ip_where("acting_user_id")).update_all(ip_address: new_ip) UserProfileView.where(ip_where).update_all(ip_address: new_ip) # UserHistory for delete_user logs the user's IP. Note this is quite ugly but we don't diff --git a/app/jobs/regular/automatic_group_membership.rb b/app/jobs/regular/automatic_group_membership.rb index 6831078842d..d2d043a39b3 100644 --- a/app/jobs/regular/automatic_group_membership.rb +++ b/app/jobs/regular/automatic_group_membership.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class AutomaticGroupMembership < ::Jobs::Base - def execute(args) group_id = args[:group_id] raise Discourse::InvalidParameters.new(:group_id) if group_id.blank? @@ -14,13 +12,13 @@ module Jobs domains = group.automatic_membership_email_domains return if domains.blank? - Group.automatic_membership_users(domains).find_each do |user| - next unless user.email_confirmed? - group.add(user, automatic: true) - GroupActionLogger.new(Discourse.system_user, group).log_add_user_to_group(user) - end + Group + .automatic_membership_users(domains) + .find_each do |user| + next unless user.email_confirmed? + group.add(user, automatic: true) + GroupActionLogger.new(Discourse.system_user, group).log_add_user_to_group(user) + end end - end - end diff --git a/app/jobs/regular/backfill_sidebar_site_settings.rb b/app/jobs/regular/backfill_sidebar_site_settings.rb index 8e07427e83d..9313a9afcab 100644 --- a/app/jobs/regular/backfill_sidebar_site_settings.rb +++ b/app/jobs/regular/backfill_sidebar_site_settings.rb @@ -2,8 +2,10 @@ class Jobs::BackfillSidebarSiteSettings < Jobs::Base def execute(args) - SidebarSiteSettingsBackfiller - .new(args[:setting_name], previous_value: args[:previous_value], new_value: args[:new_value]) - .backfill! + SidebarSiteSettingsBackfiller.new( + args[:setting_name], + previous_value: args[:previous_value], + new_value: args[:new_value], + ).backfill! end end diff --git a/app/jobs/regular/backup_chunks_merger.rb b/app/jobs/regular/backup_chunks_merger.rb index aa359711ac6..4f6bb3e2b64 100644 --- a/app/jobs/regular/backup_chunks_merger.rb +++ b/app/jobs/regular/backup_chunks_merger.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true module Jobs - class BackupChunksMerger < ::Jobs::Base - sidekiq_options queue: 'critical', retry: false + sidekiq_options queue: "critical", retry: false def execute(args) - filename = args[:filename] + filename = args[:filename] identifier = args[:identifier] - chunks = args[:chunks].to_i + chunks = args[:chunks].to_i - raise Discourse::InvalidParameters.new(:filename) if filename.blank? + raise Discourse::InvalidParameters.new(:filename) if filename.blank? raise Discourse::InvalidParameters.new(:identifier) if identifier.blank? - raise Discourse::InvalidParameters.new(:chunks) if chunks <= 0 + raise Discourse::InvalidParameters.new(:chunks) if chunks <= 0 backup_path = "#{BackupRestore::LocalBackupStore.base_directory}/#{filename}" tmp_backup_path = "#{backup_path}.tmp" # path to tmp directory - tmp_directory = File.dirname(BackupRestore::LocalBackupStore.chunk_path(identifier, filename, 0)) + tmp_directory = + File.dirname(BackupRestore::LocalBackupStore.chunk_path(identifier, filename, 0)) # merge all chunks HandleChunkUpload.merge_chunks( @@ -26,15 +26,14 @@ module Jobs tmp_upload_path: tmp_backup_path, identifier: identifier, filename: filename, - tmp_directory: tmp_directory + tmp_directory: tmp_directory, ) # push an updated list to the clients store = BackupRestore::BackupStore.create - data = ActiveModel::ArraySerializer.new(store.files, each_serializer: BackupFileSerializer).as_json + data = + ActiveModel::ArraySerializer.new(store.files, each_serializer: BackupFileSerializer).as_json MessageBus.publish("/admin/backups", data, group_ids: [Group::AUTO_GROUPS[:staff]]) end - end - end diff --git a/app/jobs/regular/bulk_grant_trust_level.rb b/app/jobs/regular/bulk_grant_trust_level.rb index f61b747d203..5a5ccecf7ee 100644 --- a/app/jobs/regular/bulk_grant_trust_level.rb +++ b/app/jobs/regular/bulk_grant_trust_level.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class BulkGrantTrustLevel < ::Jobs::Base - def execute(args) trust_level = args[:trust_level] user_ids = args[:user_ids] @@ -11,9 +9,7 @@ module Jobs raise Discourse::InvalidParameters.new(:trust_level) if trust_level.blank? raise Discourse::InvalidParameters.new(:user_ids) if user_ids.blank? - User.where(id: user_ids).find_each do |user| - TrustLevelGranter.grant(trust_level, user) - end + User.where(id: user_ids).find_each { |user| TrustLevelGranter.grant(trust_level, user) } end end end diff --git a/app/jobs/regular/bulk_invite.rb b/app/jobs/regular/bulk_invite.rb index ed83eb381df..173a27d4fda 100644 --- a/app/jobs/regular/bulk_invite.rb +++ b/app/jobs/regular/bulk_invite.rb @@ -7,13 +7,13 @@ module Jobs def initialize super - @logs = [] - @sent = 0 - @skipped = 0 - @warnings = 0 - @failed = 0 - @groups = {} - @user_fields = {} + @logs = [] + @sent = 0 + @skipped = 0 + @warnings = 0 + @failed = 0 + @groups = {} + @user_fields = {} @valid_groups = {} end @@ -64,7 +64,7 @@ module Jobs groups = [] if group_names - group_names = group_names.split(';') + group_names = group_names.split(";") group_names.each do |group_name| group = fetch_group(group_name) @@ -101,7 +101,10 @@ module Jobs user_fields = {} fields.each do |key, value| - @user_fields[key] ||= UserField.includes(:user_field_options).where('name ILIKE ?', key).first || :nil + @user_fields[key] ||= UserField + .includes(:user_field_options) + .where("name ILIKE ?", key) + .first || :nil if @user_fields[key] == :nil save_log "Invalid User Field '#{key}'" @warnings += 1 @@ -110,7 +113,8 @@ module Jobs # Automatically correct user field value if @user_fields[key].field_type == "dropdown" - value = @user_fields[key].user_field_options.find { |ufo| ufo.value.casecmp?(value) }&.value + value = + @user_fields[key].user_field_options.find { |ufo| ufo.value.casecmp?(value) }&.value end user_fields[@user_fields[key].id] = value @@ -133,17 +137,13 @@ module Jobs groups.each do |group| group.add(user) - GroupActionLogger - .new(@current_user, group) - .log_add_user_to_group(user) + GroupActionLogger.new(@current_user, group).log_add_user_to_group(user) end end end if user_fields.present? - user_fields.each do |user_field, value| - user.set_user_field(user_field, value) - end + user_fields.each { |user_field, value| user.set_user_field(user_field, value) } user.save_custom_fields end @@ -156,26 +156,19 @@ module Jobs else if user_fields.present? || locale.present? user = User.where(staged: true).find_by_email(email) - user ||= User.new(username: UserNameSuggester.suggest(email), email: email, staged: true) + user ||= + User.new(username: UserNameSuggester.suggest(email), email: email, staged: true) if user_fields.present? - user_fields.each do |user_field, value| - user.set_user_field(user_field, value) - end + user_fields.each { |user_field, value| user.set_user_field(user_field, value) } end - if locale.present? - user.locale = locale - end + user.locale = locale if locale.present? user.save! end - invite_opts = { - email: email, - topic: topic, - group_ids: groups.map(&:id), - } + invite_opts = { email: email, topic: topic, group_ids: groups.map(&:id) } if @invites.length > Invite::BULK_INVITE_EMAIL_LIMIT invite_opts[:emailed_status] = Invite.emailed_status_types[:bulk_pending] @@ -203,7 +196,7 @@ module Jobs sent: @sent, skipped: @skipped, warnings: @warnings, - logs: @logs.join("\n") + logs: @logs.join("\n"), ) else SystemMessage.create_from_system_user( @@ -213,7 +206,7 @@ module Jobs skipped: @skipped, warnings: @warnings, failed: @failed, - logs: @logs.join("\n") + logs: @logs.join("\n"), ) end end @@ -224,7 +217,7 @@ module Jobs group = @groups[group_name] unless group - group = Group.find_by('lower(name) = ?', group_name) + group = Group.find_by("lower(name) = ?", group_name) @groups[group_name] = group end diff --git a/app/jobs/regular/bulk_user_title_update.rb b/app/jobs/regular/bulk_user_title_update.rb index 6dae5219ccb..f755f620a72 100644 --- a/app/jobs/regular/bulk_user_title_update.rb +++ b/app/jobs/regular/bulk_user_title_update.rb @@ -2,14 +2,19 @@ module Jobs class BulkUserTitleUpdate < ::Jobs::Base - UPDATE_ACTION = 'update' - RESET_ACTION = 'reset' + UPDATE_ACTION = "update" + RESET_ACTION = "reset" def execute(args) new_title = args[:new_title] granted_badge_id = args[:granted_badge_id] action = args[:action] - badge = Badge.find(granted_badge_id) rescue nil + badge = + begin + Badge.find(granted_badge_id) + rescue StandardError + nil + end return unless badge # Deleted badge protection @@ -20,6 +25,5 @@ module Jobs badge.reset_user_titles! end end - end end diff --git a/app/jobs/regular/close_topic.rb b/app/jobs/regular/close_topic.rb index 3385d63c343..c0b9035d038 100644 --- a/app/jobs/regular/close_topic.rb +++ b/app/jobs/regular/close_topic.rb @@ -23,7 +23,7 @@ module Jobs end # this handles deleting the topic timer as well, see TopicStatusUpdater - topic.update_status('autoclosed', true, user, { silent: silent }) + topic.update_status("autoclosed", true, user, { silent: silent }) MessageBus.publish("/topic/#{topic.id}", reload_topic: true) end diff --git a/app/jobs/regular/confirm_sns_subscription.rb b/app/jobs/regular/confirm_sns_subscription.rb index f68588f89cb..bd1f0dcebf0 100644 --- a/app/jobs/regular/confirm_sns_subscription.rb +++ b/app/jobs/regular/confirm_sns_subscription.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class ConfirmSnsSubscription < ::Jobs::Base sidekiq_options retry: false @@ -13,15 +12,14 @@ module Jobs require "aws-sdk-sns" return unless Aws::SNS::MessageVerifier.new.authentic?(raw) - uri = begin - URI.parse(subscribe_url) - rescue URI::Error - return - end + uri = + begin + URI.parse(subscribe_url) + rescue URI::Error + return + end Net::HTTP.get(uri) end - end - end diff --git a/app/jobs/regular/crawl_topic_link.rb b/app/jobs/regular/crawl_topic_link.rb index da7659ec538..40cf4f86474 100644 --- a/app/jobs/regular/crawl_topic_link.rb +++ b/app/jobs/regular/crawl_topic_link.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require 'open-uri' -require 'nokogiri' -require 'excon' +require "open-uri" +require "nokogiri" +require "excon" module Jobs class CrawlTopicLink < ::Jobs::Base - - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) raise Discourse::InvalidParameters.new(:topic_link_id) unless args[:topic_link_id].present? @@ -16,10 +15,13 @@ module Jobs return if topic_link.blank? # Look for a topic embed for the URL. If it exists, use its title and don't crawl - topic_embed = TopicEmbed.where(embed_url: topic_link.url).includes(:topic).references(:topic).first + topic_embed = + TopicEmbed.where(embed_url: topic_link.url).includes(:topic).references(:topic).first # topic could be deleted, so skip if topic_embed && topic_embed.topic - TopicLink.where(id: topic_link.id).update_all(['title = ?, crawled_at = CURRENT_TIMESTAMP', topic_embed.topic.title[0..255]]) + TopicLink.where(id: topic_link.id).update_all( + ["title = ?, crawled_at = CURRENT_TIMESTAMP", topic_embed.topic.title[0..255]], + ) return end @@ -31,22 +33,33 @@ module Jobs if FileHelper.is_supported_image?(topic_link.url) uri = URI(topic_link.url) filename = File.basename(uri.path) - crawled = (TopicLink.where(id: topic_link.id).update_all(["title = ?, crawled_at = CURRENT_TIMESTAMP", filename]) == 1) + crawled = + ( + TopicLink.where(id: topic_link.id).update_all( + ["title = ?, crawled_at = CURRENT_TIMESTAMP", filename], + ) == 1 + ) end unless crawled # Fetch the beginning of the document to find the title title = RetrieveTitle.crawl(topic_link.url) if title.present? - crawled = (TopicLink.where(id: topic_link.id).update_all(['title = ?, crawled_at = CURRENT_TIMESTAMP', title[0..254]]) == 1) + crawled = + ( + TopicLink.where(id: topic_link.id).update_all( + ["title = ?, crawled_at = CURRENT_TIMESTAMP", title[0..254]], + ) == 1 + ) end end rescue Exception # If there was a connection error, do nothing ensure - TopicLink.where(id: topic_link.id).update_all('crawled_at = CURRENT_TIMESTAMP') if !crawled && topic_link.present? + if !crawled && topic_link.present? + TopicLink.where(id: topic_link.id).update_all("crawled_at = CURRENT_TIMESTAMP") + end end end - end end diff --git a/app/jobs/regular/create_avatar_thumbnails.rb b/app/jobs/regular/create_avatar_thumbnails.rb index 51988a47642..e4191e9f629 100644 --- a/app/jobs/regular/create_avatar_thumbnails.rb +++ b/app/jobs/regular/create_avatar_thumbnails.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true module Jobs - class CreateAvatarThumbnails < ::Jobs::Base - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) return if Rails.env.test? @@ -13,11 +12,7 @@ module Jobs return unless upload = Upload.find_by(id: upload_id) - Discourse.avatar_sizes.each do |size| - OptimizedImage.create_for(upload, size, size) - end + Discourse.avatar_sizes.each { |size| OptimizedImage.create_for(upload, size, size) } end - end - end diff --git a/app/jobs/regular/create_backup.rb b/app/jobs/regular/create_backup.rb index dcd3a466f57..14022a7dc0b 100644 --- a/app/jobs/regular/create_backup.rb +++ b/app/jobs/regular/create_backup.rb @@ -7,7 +7,12 @@ module Jobs sidekiq_options retry: false def execute(args) - BackupRestore.backup!(Discourse.system_user.id, publish_to_message_bus: false, with_uploads: SiteSetting.backup_with_uploads, fork: false) + BackupRestore.backup!( + Discourse.system_user.id, + publish_to_message_bus: false, + with_uploads: SiteSetting.backup_with_uploads, + fork: false, + ) end end end diff --git a/app/jobs/regular/create_linked_topic.rb b/app/jobs/regular/create_linked_topic.rb index e862b82ab46..f5edca6efce 100644 --- a/app/jobs/regular/create_linked_topic.rb +++ b/app/jobs/regular/create_linked_topic.rb @@ -2,7 +2,6 @@ module Jobs class CreateLinkedTopic < ::Jobs::Base - def execute(args) reference_post = Post.find_by(id: args[:post_id]) return unless reference_post.present? @@ -16,53 +15,83 @@ module Jobs ActiveRecord::Base.transaction do linked_topic_record = parent_topic.linked_topic if linked_topic_record.present? - raw_title = parent_title.delete_suffix(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: "", count: linked_topic_record.sequence)) + raw_title = + parent_title.delete_suffix( + I18n.t( + "create_linked_topic.topic_title_with_sequence", + topic_title: "", + count: linked_topic_record.sequence, + ), + ) original_topic_id = linked_topic_record.original_topic_id sequence = linked_topic_record.sequence + 1 else raw_title = parent_title # update parent topic title to append title_suffix_locale - parent_title = I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: parent_title, count: 1) + parent_title = + I18n.t( + "create_linked_topic.topic_title_with_sequence", + topic_title: parent_title, + count: 1, + ) parent_topic.title = parent_title parent_topic.save! # create linked topic record original_topic_id = parent_topic_id - LinkedTopic.create!(topic_id: parent_topic_id, original_topic_id: original_topic_id, sequence: 1) + LinkedTopic.create!( + topic_id: parent_topic_id, + original_topic_id: original_topic_id, + sequence: 1, + ) sequence = 2 end # fetch previous topic titles previous_topics = "" linked_topic_ids = LinkedTopic.where(original_topic_id: original_topic_id).pluck(:topic_id) - Topic.where(id: linked_topic_ids).order(:id).each do |topic| - previous_topics += "- #{topic.url}\n" - end + Topic + .where(id: linked_topic_ids) + .order(:id) + .each { |topic| previous_topics += "- #{topic.url}\n" } # create new topic - new_topic_title = I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: sequence) - new_topic_raw = I18n.t('create_linked_topic.post_raw', parent_url: reference_post.full_url, previous_topics: previous_topics) + new_topic_title = + I18n.t( + "create_linked_topic.topic_title_with_sequence", + topic_title: raw_title, + count: sequence, + ) + new_topic_raw = + I18n.t( + "create_linked_topic.post_raw", + parent_url: reference_post.full_url, + previous_topics: previous_topics, + ) system_user = Discourse.system_user - @post_creator = PostCreator.new( - system_user, - title: new_topic_title, - raw: new_topic_raw, - category: parent_category_id, - skip_validations: true, - skip_jobs: true) + @post_creator = + PostCreator.new( + system_user, + title: new_topic_title, + raw: new_topic_raw, + category: parent_category_id, + skip_validations: true, + skip_jobs: true, + ) new_post = @post_creator.create new_topic = new_post.topic new_topic_id = new_topic.id # create linked_topic record - LinkedTopic.create!(topic_id: new_topic_id, original_topic_id: original_topic_id, sequence: sequence) + LinkedTopic.create!( + topic_id: new_topic_id, + original_topic_id: original_topic_id, + sequence: sequence, + ) # copy over topic tracking state from old topic - params = { - old_topic_id: parent_topic_id, - new_topic_id: new_topic_id - } + params = { old_topic_id: parent_topic_id, new_topic_id: new_topic_id } DB.exec(<<~SQL, params) INSERT INTO topic_users(user_id, topic_id, notification_level, notifications_reason_id) @@ -78,9 +107,15 @@ module Jobs SQL # update small action post on old topic to add new topic link - small_action_post = Post.where(topic_id: parent_topic_id, post_type: Post.types[:small_action], action_code: "closed.enabled").last + small_action_post = + Post.where( + topic_id: parent_topic_id, + post_type: Post.types[:small_action], + action_code: "closed.enabled", + ).last if small_action_post.present? - small_action_post.raw = "#{small_action_post.raw} #{I18n.t('create_linked_topic.small_action_post_raw', new_title: "[#{new_topic_title}](#{new_topic.url})")}" + small_action_post.raw = + "#{small_action_post.raw} #{I18n.t("create_linked_topic.small_action_post_raw", new_title: "[#{new_topic_title}](#{new_topic.url})")}" small_action_post.save! end end diff --git a/app/jobs/regular/create_user_reviewable.rb b/app/jobs/regular/create_user_reviewable.rb index b3c20267ed9..5d4a69fbded 100644 --- a/app/jobs/regular/create_user_reviewable.rb +++ b/app/jobs/regular/create_user_reviewable.rb @@ -15,24 +15,25 @@ class Jobs::CreateUserReviewable < ::Jobs::Base if user = User.find_by(id: args[:user_id]) return if user.approved? - @reviewable = ReviewableUser.needs_review!( - target: user, - created_by: Discourse.system_user, - reviewable_by_moderator: true, - payload: { - username: user.username, - name: user.name, - email: user.email, - website: user.user_profile&.website - } - ) + @reviewable = + ReviewableUser.needs_review!( + target: user, + created_by: Discourse.system_user, + reviewable_by_moderator: true, + payload: { + username: user.username, + name: user.name, + email: user.email, + website: user.user_profile&.website, + }, + ) if @reviewable.created_new @reviewable.add_score( Discourse.system_user, ReviewableScore.types[:needs_approval], reason: reason, - force_review: true + force_review: true, ) end end diff --git a/app/jobs/regular/critical_user_email.rb b/app/jobs/regular/critical_user_email.rb index 47d2ef14e99..6911ff174aa 100644 --- a/app/jobs/regular/critical_user_email.rb +++ b/app/jobs/regular/critical_user_email.rb @@ -2,8 +2,7 @@ module Jobs class CriticalUserEmail < UserEmail - - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" def quit_email_early? false diff --git a/app/jobs/regular/delete_inaccessible_notifications.rb b/app/jobs/regular/delete_inaccessible_notifications.rb index e33bfd6fb13..e0f0e55d4db 100644 --- a/app/jobs/regular/delete_inaccessible_notifications.rb +++ b/app/jobs/regular/delete_inaccessible_notifications.rb @@ -5,13 +5,13 @@ module Jobs def execute(args) raise Discourse::InvalidParameters.new(:topic_id) if args[:topic_id].blank? - Notification.where(topic_id: args[:topic_id]).find_each do |notification| - next unless notification.user && notification.topic + Notification + .where(topic_id: args[:topic_id]) + .find_each do |notification| + next unless notification.user && notification.topic - if !Guardian.new(notification.user).can_see?(notification.topic) - notification.destroy + notification.destroy if !Guardian.new(notification.user).can_see?(notification.topic) end - end end end end diff --git a/app/jobs/regular/delete_replies.rb b/app/jobs/regular/delete_replies.rb index 7208f4e561f..d235c39bcfd 100644 --- a/app/jobs/regular/delete_replies.rb +++ b/app/jobs/regular/delete_replies.rb @@ -9,15 +9,25 @@ module Jobs end replies = topic.posts.where("posts.post_number > 1") - replies = replies.where("like_count < ?", SiteSetting.skip_auto_delete_reply_likes) if SiteSetting.skip_auto_delete_reply_likes > 0 + replies = + replies.where( + "like_count < ?", + SiteSetting.skip_auto_delete_reply_likes, + ) if SiteSetting.skip_auto_delete_reply_likes > 0 - replies.where('posts.created_at < ?', topic_timer.duration_minutes.minutes.ago).each do |post| - PostDestroyer.new(topic_timer.user, post, context: I18n.t("topic_statuses.auto_deleted_by_timer")).destroy - end + replies + .where("posts.created_at < ?", topic_timer.duration_minutes.minutes.ago) + .each do |post| + PostDestroyer.new( + topic_timer.user, + post, + context: I18n.t("topic_statuses.auto_deleted_by_timer"), + ).destroy + end - topic_timer.execute_at = (replies.minimum(:created_at) || Time.zone.now) + topic_timer.duration_minutes.minutes + topic_timer.execute_at = + (replies.minimum(:created_at) || Time.zone.now) + topic_timer.duration_minutes.minutes topic_timer.save end - end end diff --git a/app/jobs/regular/delete_topic.rb b/app/jobs/regular/delete_topic.rb index af109d8b101..d3a35d5d980 100644 --- a/app/jobs/regular/delete_topic.rb +++ b/app/jobs/regular/delete_topic.rb @@ -7,7 +7,9 @@ module Jobs first_post = topic.ordered_posts.first PostDestroyer.new( - topic_timer.user, first_post, context: I18n.t("topic_statuses.auto_deleted_by_timer") + topic_timer.user, + first_post, + context: I18n.t("topic_statuses.auto_deleted_by_timer"), ).destroy topic_timer.trash!(Discourse.system_user) diff --git a/app/jobs/regular/download_avatar_from_url.rb b/app/jobs/regular/download_avatar_from_url.rb index e1864c80b33..06ef524a25e 100644 --- a/app/jobs/regular/download_avatar_from_url.rb +++ b/app/jobs/regular/download_avatar_from_url.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class DownloadAvatarFromUrl < ::Jobs::Base sidekiq_options retry: false @@ -15,16 +14,10 @@ module Jobs return unless user = User.find_by(id: user_id) begin - UserAvatar.import_url_for_user( - url, - user, - override_gravatar: args[:override_gravatar] - ) + UserAvatar.import_url_for_user(url, user, override_gravatar: args[:override_gravatar]) rescue Discourse::InvalidParameters => e - raise e unless e.message == 'url' + raise e unless e.message == "url" end end - end - end diff --git a/app/jobs/regular/download_backup_email.rb b/app/jobs/regular/download_backup_email.rb index 44e13495f29..000d0591f2d 100644 --- a/app/jobs/regular/download_backup_email.rb +++ b/app/jobs/regular/download_backup_email.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true module Jobs - class DownloadBackupEmail < ::Jobs::Base - - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" def execute(args) user_id = args[:user_id] @@ -20,7 +18,5 @@ module Jobs message = DownloadBackupMailer.send_email(user.email, backup_file_path.to_s) Email::Sender.new(message, :download_backup_message).send end - end - end diff --git a/app/jobs/regular/download_profile_background_from_url.rb b/app/jobs/regular/download_profile_background_from_url.rb index 5d39c3d8c86..bf8e15ff036 100644 --- a/app/jobs/regular/download_profile_background_from_url.rb +++ b/app/jobs/regular/download_profile_background_from_url.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class DownloadProfileBackgroundFromUrl < ::Jobs::Base sidekiq_options retry: false @@ -15,16 +14,10 @@ module Jobs return unless user = User.find_by(id: user_id) begin - UserProfile.import_url_for_user( - url, - user, - is_card_background: args[:is_card_background], - ) + UserProfile.import_url_for_user(url, user, is_card_background: args[:is_card_background]) rescue Discourse::InvalidParameters => e - raise e unless e.message == 'url' + raise e unless e.message == "url" end end - end - end diff --git a/app/jobs/regular/emit_web_hook_event.rb b/app/jobs/regular/emit_web_hook_event.rb index 806765309bc..ab648e6572c 100644 --- a/app/jobs/regular/emit_web_hook_event.rb +++ b/app/jobs/regular/emit_web_hook_event.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'excon' +require "excon" module Jobs class EmitWebHookEvent < ::Jobs::Base - sidekiq_options queue: 'low' + sidekiq_options queue: "low" - PING_EVENT = 'ping' + PING_EVENT = "ping" MAX_RETRY_COUNT = 4 RETRY_BACKOFF = 5 @@ -63,9 +63,10 @@ module Jobs if @retry_count >= MAX_RETRY_COUNT @web_hook.update!(active: false) - StaffActionLogger - .new(Discourse.system_user) - .log_web_hook_deactivate(@web_hook, web_hook_response.status) + StaffActionLogger.new(Discourse.system_user).log_web_hook_deactivate( + @web_hook, + web_hook_response.status, + ) end else retry_web_hook @@ -83,10 +84,11 @@ module Jobs end def publish_webhook_event(web_hook_event) - MessageBus.publish("/web_hook_events/#{@web_hook.id}", { - web_hook_event_id: web_hook_event.id, - event_type: @arguments[:event_type] - }, group_ids: [Group::AUTO_GROUPS[:staff]]) + MessageBus.publish( + "/web_hook_events/#{@web_hook.id}", + { web_hook_event_id: web_hook_event.id, event_type: @arguments[:event_type] }, + group_ids: [Group::AUTO_GROUPS[:staff]], + ) end def ping_event?(event_type) @@ -98,45 +100,50 @@ module Jobs end def group_webhook_invalid? - @web_hook.group_ids.present? && (@arguments[:group_ids].blank? || - (@web_hook.group_ids & @arguments[:group_ids]).blank?) + @web_hook.group_ids.present? && + (@arguments[:group_ids].blank? || (@web_hook.group_ids & @arguments[:group_ids]).blank?) end def category_webhook_invalid? - @web_hook.category_ids.present? && (!@arguments[:category_id].present? || - !@web_hook.category_ids.include?(@arguments[:category_id])) + @web_hook.category_ids.present? && + ( + !@arguments[:category_id].present? || + !@web_hook.category_ids.include?(@arguments[:category_id]) + ) end def tag_webhook_invalid? - @web_hook.tag_ids.present? && (@arguments[:tag_ids].blank? || - (@web_hook.tag_ids & @arguments[:tag_ids]).blank?) + @web_hook.tag_ids.present? && + (@arguments[:tag_ids].blank? || (@web_hook.tag_ids & @arguments[:tag_ids]).blank?) end def build_webhook_headers(uri, web_hook_body, web_hook_event) content_type = case @web_hook.content_type - when WebHook.content_types['application/x-www-form-urlencoded'] - 'application/x-www-form-urlencoded' + when WebHook.content_types["application/x-www-form-urlencoded"] + "application/x-www-form-urlencoded" else - 'application/json' + "application/json" end headers = { - 'Accept' => '*/*', - 'Connection' => 'close', - 'Content-Length' => web_hook_body.bytesize.to_s, - 'Content-Type' => content_type, - 'Host' => uri.host, - 'User-Agent' => "Discourse/#{Discourse::VERSION::STRING}", - 'X-Discourse-Instance' => Discourse.base_url, - 'X-Discourse-Event-Id' => web_hook_event.id.to_s, - 'X-Discourse-Event-Type' => @arguments[:event_type] + "Accept" => "*/*", + "Connection" => "close", + "Content-Length" => web_hook_body.bytesize.to_s, + "Content-Type" => content_type, + "Host" => uri.host, + "User-Agent" => "Discourse/#{Discourse::VERSION::STRING}", + "X-Discourse-Instance" => Discourse.base_url, + "X-Discourse-Event-Id" => web_hook_event.id.to_s, + "X-Discourse-Event-Type" => @arguments[:event_type], } - headers['X-Discourse-Event'] = @arguments[:event_name] if @arguments[:event_name].present? + headers["X-Discourse-Event"] = @arguments[:event_name] if @arguments[:event_name].present? if @web_hook.secret.present? - headers['X-Discourse-Event-Signature'] = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", @web_hook.secret, web_hook_body)}" + headers[ + "X-Discourse-Event-Signature" + ] = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", @web_hook.secret, web_hook_body)}" end headers @@ -146,7 +153,7 @@ module Jobs body = {} if ping_event?(@arguments[:event_type]) - body['ping'] = "OK" + body["ping"] = "OK" else body[@arguments[:event_type]] = JSON.parse(@arguments[:payload]) end @@ -158,6 +165,5 @@ module Jobs def create_webhook_event(web_hook_body) WebHookEvent.create!(web_hook: @web_hook, payload: web_hook_body) end - end end diff --git a/app/jobs/regular/enable_bootstrap_mode.rb b/app/jobs/regular/enable_bootstrap_mode.rb index bf804e24dbc..8b7d456f4cc 100644 --- a/app/jobs/regular/enable_bootstrap_mode.rb +++ b/app/jobs/regular/enable_bootstrap_mode.rb @@ -2,7 +2,7 @@ module Jobs class EnableBootstrapMode < ::Jobs::Base - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" def execute(args) raise Discourse::InvalidParameters.new(:user_id) unless args[:user_id].present? @@ -13,14 +13,14 @@ module Jobs # let's enable bootstrap mode settings if SiteSetting.default_trust_level == TrustLevel[0] - SiteSetting.set_and_log('default_trust_level', TrustLevel[1]) + SiteSetting.set_and_log("default_trust_level", TrustLevel[1]) end - if SiteSetting.default_email_digest_frequency == 10080 - SiteSetting.set_and_log('default_email_digest_frequency', 1440) + if SiteSetting.default_email_digest_frequency == 10_080 + SiteSetting.set_and_log("default_email_digest_frequency", 1440) end - SiteSetting.set_and_log('bootstrap_mode_enabled', true) + SiteSetting.set_and_log("bootstrap_mode_enabled", true) end end end diff --git a/app/jobs/regular/export_csv_file.rb b/app/jobs/regular/export_csv_file.rb index 44305b44720..b725ab5dd2f 100644 --- a/app/jobs/regular/export_csv_file.rb +++ b/app/jobs/regular/export_csv_file.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'csv' +require "csv" module Jobs - class ExportCsvFile < ::Jobs::Base sidekiq_options retry: false @@ -11,17 +10,53 @@ module Jobs attr_accessor :current_user attr_accessor :entity - HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new( - user_list: ['id', 'name', 'username', 'email', 'title', 'created_at', 'last_seen_at', 'last_posted_at', 'last_emailed_at', 'trust_level', 'approved', 'suspended_at', 'suspended_till', 'silenced_till', 'active', 'admin', 'moderator', 'ip_address', 'staged', 'secondary_emails'], - user_stats: ['topics_entered', 'posts_read_count', 'time_read', 'topic_count', 'post_count', 'likes_given', 'likes_received'], - user_profile: ['location', 'website', 'views'], - user_sso: ['external_id', 'external_email', 'external_username', 'external_name', 'external_avatar_url'], - staff_action: ['staff_user', 'action', 'subject', 'created_at', 'details', 'context'], - screened_email: ['email', 'action', 'match_count', 'last_match_at', 'created_at', 'ip_address'], - screened_ip: ['ip_address', 'action', 'match_count', 'last_match_at', 'created_at'], - screened_url: ['domain', 'action', 'match_count', 'last_match_at', 'created_at'], - report: ['date', 'value'] - ) + HEADER_ATTRS_FOR ||= + HashWithIndifferentAccess.new( + user_list: %w[ + id + name + username + email + title + created_at + last_seen_at + last_posted_at + last_emailed_at + trust_level + approved + suspended_at + suspended_till + silenced_till + active + admin + moderator + ip_address + staged + secondary_emails + ], + user_stats: %w[ + topics_entered + posts_read_count + time_read + topic_count + post_count + likes_given + likes_received + ], + user_profile: %w[location website views], + user_sso: %w[ + external_id + external_email + external_username + external_name + external_avatar_url + ], + staff_action: %w[staff_user action subject created_at details context], + screened_email: %w[email action match_count last_match_at created_at ip_address], + screened_ip: %w[ip_address action match_count last_match_at created_at], + screened_url: %w[domain action match_count last_match_at created_at], + report: %w[date value], + ) def execute(args) @entity = args[:entity] @@ -35,19 +70,19 @@ module Jobs raise Discourse::InvalidParameters.new(:entity) unless respond_to?(entity[:method]) @timestamp ||= Time.now.strftime("%y%m%d-%H%M%S") - entity[:filename] = - if entity[:name] == "report" && @extra[:name].present? - "#{@extra[:name].dasherize}-#{@timestamp}" - else - "#{entity[:name].dasherize}-#{@timestamp}" - end + entity[:filename] = if entity[:name] == "report" && @extra[:name].present? + "#{@extra[:name].dasherize}-#{@timestamp}" + else + "#{entity[:name].dasherize}-#{@timestamp}" + end end - export_title = if @entity == "report" && @extra[:name].present? - I18n.t("reports.#{@extra[:name]}.title") - else - @entity.gsub('_', ' ').titleize - end + export_title = + if @entity == "report" && @extra[:name].present? + I18n.t("reports.#{@extra[:name]}.title") + else + @entity.gsub("_", " ").titleize + end filename = entities[0][:filename] # use first entity as a name for this export user_export = UserExport.create(file_name: filename, user_id: @current_user.id) @@ -77,12 +112,13 @@ module Jobs if File.exist?(zip_filename) File.open(zip_filename) do |file| - upload = UploadCreator.new( - file, - File.basename(zip_filename), - type: 'csv_export', - for_export: 'true' - ).create_for(@current_user.id) + upload = + UploadCreator.new( + file, + File.basename(zip_filename), + type: "csv_export", + for_export: "true", + ).create_for(@current_user.id) if upload.persisted? user_export.update_columns(upload_id: upload.id) @@ -99,7 +135,7 @@ module Jobs if user_export.present? && post.present? topic = post.topic user_export.update_columns(topic_id: topic.id) - topic.update_status('closed', true, Discourse.system_user) + topic.update_status("closed", true, Discourse.system_user) end end @@ -109,66 +145,67 @@ module Jobs user_field_ids = UserField.pluck(:id) condition = {} - if @extra && @extra[:trust_level] && trust_level = TrustLevel.levels[@extra[:trust_level].to_sym] + if @extra && @extra[:trust_level] && + trust_level = TrustLevel.levels[@extra[:trust_level].to_sym] condition = { trust_level: trust_level } end - includes = [:user_profile, :user_stat, :groups, :user_emails] - if SiteSetting.enable_discourse_connect - includes << [:single_sign_on_record] - end + includes = %i[user_profile user_stat groups user_emails] + includes << [:single_sign_on_record] if SiteSetting.enable_discourse_connect - User.where(condition).includes(*includes).find_each do |user| - user_info_array = get_base_user_array(user) - if SiteSetting.enable_discourse_connect - user_info_array = add_single_sign_on(user, user_info_array) + User + .where(condition) + .includes(*includes) + .find_each do |user| + user_info_array = get_base_user_array(user) + if SiteSetting.enable_discourse_connect + user_info_array = add_single_sign_on(user, user_info_array) + end + user_info_array = add_custom_fields(user, user_info_array, user_field_ids) + user_info_array = add_group_names(user, user_info_array) + yield user_info_array end - user_info_array = add_custom_fields(user, user_info_array, user_field_ids) - user_info_array = add_group_names(user, user_info_array) - yield user_info_array - end - end def staff_action_export return enum_for(:staff_action_export) unless block_given? - staff_action_data = if @current_user.admin? - UserHistory.only_staff_actions.order('id DESC') - else - UserHistory.where(admin_only: false).only_staff_actions.order('id DESC') - end + staff_action_data = + if @current_user.admin? + UserHistory.only_staff_actions.order("id DESC") + else + UserHistory.where(admin_only: false).only_staff_actions.order("id DESC") + end - staff_action_data.each do |staff_action| - yield get_staff_action_fields(staff_action) - end + staff_action_data.each { |staff_action| yield get_staff_action_fields(staff_action) } end def screened_email_export return enum_for(:screened_email_export) unless block_given? - ScreenedEmail.order('last_match_at DESC').each do |screened_email| - yield get_screened_email_fields(screened_email) - end + ScreenedEmail + .order("last_match_at DESC") + .each { |screened_email| yield get_screened_email_fields(screened_email) } end def screened_ip_export return enum_for(:screened_ip_export) unless block_given? - ScreenedIpAddress.order('id DESC').each do |screened_ip| - yield get_screened_ip_fields(screened_ip) - end + ScreenedIpAddress + .order("id DESC") + .each { |screened_ip| yield get_screened_ip_fields(screened_ip) } end def screened_url_export return enum_for(:screened_url_export) unless block_given? - ScreenedUrl.select("domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at") + ScreenedUrl + .select( + "domain, sum(match_count) as match_count, max(last_match_at) as last_match_at, min(created_at) as created_at", + ) .group(:domain) - .order('last_match_at DESC') - .each do |screened_url| - yield get_screened_url_fields(screened_url) - end + .order("last_match_at DESC") + .each { |screened_url| yield get_screened_url_fields(screened_url) } end def report_export @@ -176,16 +213,26 @@ module Jobs # If dates are invalid consider then `nil` if @extra[:start_date].is_a?(String) - @extra[:start_date] = @extra[:start_date].to_date.beginning_of_day rescue nil + @extra[:start_date] = begin + @extra[:start_date].to_date.beginning_of_day + rescue StandardError + nil + end end if @extra[:end_date].is_a?(String) - @extra[:end_date] = @extra[:end_date].to_date.end_of_day rescue nil + @extra[:end_date] = begin + @extra[:end_date].to_date.end_of_day + rescue StandardError + nil + end end @extra[:filters] = {} @extra[:filters][:category] = @extra[:category].to_i if @extra[:category].present? @extra[:filters][:group] = @extra[:group].to_i if @extra[:group].present? - @extra[:filters][:include_subcategories] = !!ActiveRecord::Type::Boolean.new.cast(@extra[:include_subcategories]) if @extra[:include_subcategories].present? + @extra[:filters][:include_subcategories] = !!ActiveRecord::Type::Boolean.new.cast( + @extra[:include_subcategories], + ) if @extra[:include_subcategories].present? report = Report.find(@extra[:name], @extra) @@ -227,9 +274,11 @@ module Jobs end def get_header(entity) - if entity == 'user_list' - header_array = HEADER_ATTRS_FOR['user_list'] + HEADER_ATTRS_FOR['user_stats'] + HEADER_ATTRS_FOR['user_profile'] - header_array.concat(HEADER_ATTRS_FOR['user_sso']) if SiteSetting.enable_discourse_connect + if entity == "user_list" + header_array = + HEADER_ATTRS_FOR["user_list"] + HEADER_ATTRS_FOR["user_stats"] + + HEADER_ATTRS_FOR["user_profile"] + header_array.concat(HEADER_ATTRS_FOR["user_sso"]) if SiteSetting.enable_discourse_connect user_custom_fields = UserField.all if user_custom_fields.present? user_custom_fields.each do |custom_field| @@ -299,7 +348,13 @@ module Jobs def add_single_sign_on(user, user_info_array) if user.single_sign_on_record - user_info_array.push(user.single_sign_on_record.external_id, user.single_sign_on_record.external_email, user.single_sign_on_record.external_username, escape_comma(user.single_sign_on_record.external_name), user.single_sign_on_record.external_avatar_url) + user_info_array.push( + user.single_sign_on_record.external_id, + user.single_sign_on_record.external_email, + user.single_sign_on_record.external_username, + escape_comma(user.single_sign_on_record.external_name), + user.single_sign_on_record.external_avatar_url, + ) else user_info_array.push(nil, nil, nil, nil, nil) end @@ -308,9 +363,7 @@ module Jobs def add_custom_fields(user, user_info_array, user_field_ids) if user_field_ids.present? - user.user_fields.each do |custom_field| - user_info_array << escape_comma(custom_field[1]) - end + user.user_fields.each { |custom_field| user_info_array << escape_comma(custom_field[1]) } end user_info_array end @@ -328,21 +381,25 @@ module Jobs def get_staff_action_fields(staff_action) staff_action_array = [] - HEADER_ATTRS_FOR['staff_action'].each do |attr| + HEADER_ATTRS_FOR["staff_action"].each do |attr| data = - if attr == 'action' + if attr == "action" UserHistory.actions.key(staff_action.attributes[attr]).to_s - elsif attr == 'staff_user' - user = User.find_by(id: staff_action.attributes['acting_user_id']) + elsif attr == "staff_user" + user = User.find_by(id: staff_action.attributes["acting_user_id"]) user.username if !user.nil? - elsif attr == 'subject' - user = User.find_by(id: staff_action.attributes['target_user_id']) - user.nil? ? staff_action.attributes[attr] : "#{user.username} #{staff_action.attributes[attr]}" + elsif attr == "subject" + user = User.find_by(id: staff_action.attributes["target_user_id"]) + if user.nil? + staff_action.attributes[attr] + else + "#{user.username} #{staff_action.attributes[attr]}" + end else staff_action.attributes[attr] end - staff_action_array.push(data) + staff_action_array.push(data) end staff_action_array end @@ -350,10 +407,10 @@ module Jobs def get_screened_email_fields(screened_email) screened_email_array = [] - HEADER_ATTRS_FOR['screened_email'].each do |attr| + HEADER_ATTRS_FOR["screened_email"].each do |attr| data = - if attr == 'action' - ScreenedEmail.actions.key(screened_email.attributes['action_type']).to_s + if attr == "action" + ScreenedEmail.actions.key(screened_email.attributes["action_type"]).to_s else screened_email.attributes[attr] end @@ -367,10 +424,10 @@ module Jobs def get_screened_ip_fields(screened_ip) screened_ip_array = [] - HEADER_ATTRS_FOR['screened_ip'].each do |attr| + HEADER_ATTRS_FOR["screened_ip"].each do |attr| data = - if attr == 'action' - ScreenedIpAddress.actions.key(screened_ip.attributes['action_type']).to_s + if attr == "action" + ScreenedIpAddress.actions.key(screened_ip.attributes["action_type"]).to_s else screened_ip.attributes[attr] end @@ -384,10 +441,10 @@ module Jobs def get_screened_url_fields(screened_url) screened_url_array = [] - HEADER_ATTRS_FOR['screened_url'].each do |attr| + HEADER_ATTRS_FOR["screened_url"].each do |attr| data = - if attr == 'action' - action = ScreenedUrl.actions.key(screened_url.attributes['action_type']).to_s + if attr == "action" + action = ScreenedUrl.actions.key(screened_url.attributes["action_type"]).to_s action = "do nothing" if action.blank? else screened_url.attributes[attr] @@ -403,16 +460,17 @@ module Jobs post = nil if @current_user - post = if upload - SystemMessage.create_from_system_user( - @current_user, - :csv_export_succeeded, - download_link: UploadMarkdown.new(upload).attachment_markdown, - export_title: export_title - ) - else - SystemMessage.create_from_system_user(@current_user, :csv_export_failed) - end + post = + if upload + SystemMessage.create_from_system_user( + @current_user, + :csv_export_succeeded, + download_link: UploadMarkdown.new(upload).attachment_markdown, + export_title: export_title, + ) + else + SystemMessage.create_from_system_user(@current_user, :csv_export_failed) + end end post diff --git a/app/jobs/regular/export_user_archive.rb b/app/jobs/regular/export_user_archive.rb index 657cc4aafec..59c60740b37 100644 --- a/app/jobs/regular/export_user_archive.rb +++ b/app/jobs/regular/export_user_archive.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'csv' +require "csv" module Jobs class ExportUserArchive < ::Jobs::Base @@ -10,7 +10,7 @@ module Jobs # note: contents provided entirely by user attr_accessor :extra - COMPONENTS ||= %w( + COMPONENTS ||= %w[ user_archive preferences auth_tokens @@ -23,22 +23,98 @@ module Jobs post_actions queued_posts visits - ) + ] - HEADER_ATTRS_FOR ||= HashWithIndifferentAccess.new( - user_archive: ['topic_title', 'categories', 'is_pm', 'post_raw', 'post_cooked', 'like_count', 'reply_count', 'url', 'created_at'], - user_archive_profile: ['location', 'website', 'bio', 'views'], - auth_tokens: ['id', 'auth_token_hash', 'prev_auth_token_hash', 'auth_token_seen', 'client_ip', 'user_agent', 'seen_at', 'rotated_at', 'created_at', 'updated_at'], - auth_token_logs: ['id', 'action', 'user_auth_token_id', 'client_ip', 'auth_token_hash', 'created_at', 'path', 'user_agent'], - badges: ['badge_id', 'badge_name', 'granted_at', 'post_id', 'seq', 'granted_manually', 'notification_id', 'featured_rank'], - bookmarks: ['bookmarkable_id', 'bookmarkable_type', 'link', 'name', 'created_at', 'updated_at', 'reminder_at', 'reminder_last_sent_at', 'reminder_set_at', 'auto_delete_preference'], - category_preferences: ['category_id', 'category_names', 'notification_level', 'dismiss_new_timestamp'], - flags: ['id', 'post_id', 'flag_type', 'created_at', 'updated_at', 'deleted_at', 'deleted_by', 'related_post_id', 'targets_topic', 'was_take_action'], - likes: ['id', 'post_id', 'topic_id', 'post_number', 'created_at', 'updated_at', 'deleted_at', 'deleted_by'], - post_actions: ['id', 'post_id', 'post_action_type', 'created_at', 'updated_at', 'deleted_at', 'deleted_by', 'related_post_id'], - queued_posts: ['id', 'verdict', 'category_id', 'topic_id', 'post_raw', 'other_json'], - visits: ['visited_at', 'posts_read', 'mobile', 'time_read'], - ) + HEADER_ATTRS_FOR ||= + HashWithIndifferentAccess.new( + user_archive: %w[ + topic_title + categories + is_pm + post_raw + post_cooked + like_count + reply_count + url + created_at + ], + user_archive_profile: %w[location website bio views], + auth_tokens: %w[ + id + auth_token_hash + prev_auth_token_hash + auth_token_seen + client_ip + user_agent + seen_at + rotated_at + created_at + updated_at + ], + auth_token_logs: %w[ + id + action + user_auth_token_id + client_ip + auth_token_hash + created_at + path + user_agent + ], + badges: %w[ + badge_id + badge_name + granted_at + post_id + seq + granted_manually + notification_id + featured_rank + ], + bookmarks: %w[ + bookmarkable_id + bookmarkable_type + link + name + created_at + updated_at + reminder_at + reminder_last_sent_at + reminder_set_at + auto_delete_preference + ], + category_preferences: %w[ + category_id + category_names + notification_level + dismiss_new_timestamp + ], + flags: %w[ + id + post_id + flag_type + created_at + updated_at + deleted_at + deleted_by + related_post_id + targets_topic + was_take_action + ], + likes: %w[id post_id topic_id post_number created_at updated_at deleted_at deleted_by], + post_actions: %w[ + id + post_id + post_action_type + created_at + updated_at + deleted_at + deleted_by + related_post_id + ], + queued_posts: %w[id verdict category_id topic_id post_raw other_json], + visits: %w[visited_at posts_read mobile time_read], + ) def execute(args) @current_user = User.find_by(id: args[:user_id]) @@ -52,18 +128,14 @@ module Jobs h = { name: name, method: :"#{export_method}" } h[:filetype] = :csv filetype_method = :"#{name}_filetype" - if respond_to? filetype_method - h[:filetype] = public_send(filetype_method) - end + h[:filetype] = public_send(filetype_method) if respond_to? filetype_method condition_method = :"include_#{name}?" - if respond_to? condition_method - h[:skip] = !public_send(condition_method) - end + h[:skip] = !public_send(condition_method) if respond_to? condition_method h[:filename] = name components.push(h) end - export_title = 'user_archive'.titleize + export_title = "user_archive".titleize filename = "user_archive-#{@current_user.username}-#{@timestamp}" user_export = UserExport.create(file_name: filename, user_id: @current_user.id) @@ -89,7 +161,7 @@ module Jobs file.write MultiJson.dump(public_send(component[:method]), indent: 4) end else - raise 'unknown export filetype' + raise "unknown export filetype" end end @@ -103,17 +175,20 @@ module Jobs if File.exist?(zip_filename) File.open(zip_filename) do |file| - upload = UploadCreator.new( - file, - File.basename(zip_filename), - type: 'csv_export', - for_export: 'true' - ).create_for(@current_user.id) + upload = + UploadCreator.new( + file, + File.basename(zip_filename), + type: "csv_export", + for_export: "true", + ).create_for(@current_user.id) if upload.persisted? user_export.update_columns(upload_id: upload.id) else - Rails.logger.warn("Failed to upload the file #{zip_filename}: #{upload.errors.full_messages}") + Rails.logger.warn( + "Failed to upload the file #{zip_filename}: #{upload.errors.full_messages}", + ) end end @@ -125,21 +200,20 @@ module Jobs if user_export.present? && post.present? topic = post.topic user_export.update_columns(topic_id: topic.id) - topic.update_status('closed', true, Discourse.system_user) + topic.update_status("closed", true, Discourse.system_user) end end def user_archive_export return enum_for(:user_archive_export) unless block_given? - Post.includes(topic: :category) + Post + .includes(topic: :category) .where(user_id: @current_user.id) .select(:topic_id, :post_number, :raw, :cooked, :like_count, :reply_count, :created_at) .order(:created_at) .with_deleted - .each do |user_archive| - yield get_user_archive_fields(user_archive) - end + .each { |user_archive| yield get_user_archive_fields(user_archive) } end def user_archive_profile_export @@ -148,9 +222,7 @@ module Jobs UserProfile .where(user_id: @current_user.id) .select(:location, :website, :bio_raw, :views) - .each do |user_profile| - yield get_user_archive_profile_fields(user_profile) - end + .each { |user_profile| yield get_user_archive_profile_fields(user_profile) } end def preferences_export @@ -167,19 +239,21 @@ module Jobs UserAuthToken .where(user_id: @current_user.id) .each do |token| - yield [ - token.id, - token.auth_token.to_s[0..4] + "...", # hashed and truncated - token.prev_auth_token[0..4] + "...", - token.auth_token_seen, - token.client_ip, - token.user_agent, - token.seen_at, - token.rotated_at, - token.created_at, - token.updated_at, - ] - end + yield( + [ + token.id, + token.auth_token.to_s[0..4] + "...", # hashed and truncated + token.prev_auth_token[0..4] + "...", + token.auth_token_seen, + token.client_ip, + token.user_agent, + token.seen_at, + token.rotated_at, + token.created_at, + token.updated_at, + ] + ) + end end def include_auth_token_logs? @@ -193,17 +267,19 @@ module Jobs UserAuthTokenLog .where(user_id: @current_user.id) .each do |log| - yield [ - log.id, - log.action, - log.user_auth_token_id, - log.client_ip, - log.auth_token.to_s[0..4] + "...", # hashed and truncated - log.created_at, - log.path, - log.user_agent, - ] - end + yield( + [ + log.id, + log.action, + log.user_auth_token_id, + log.client_ip, + log.auth_token.to_s[0..4] + "...", # hashed and truncated + log.created_at, + log.path, + log.user_agent, + ] + ) + end end def badges_export @@ -212,49 +288,65 @@ module Jobs UserBadge .where(user_id: @current_user.id) .joins(:badge) - .select(:badge_id, :granted_at, :post_id, :seq, :granted_by_id, :notification_id, :featured_rank) + .select( + :badge_id, + :granted_at, + :post_id, + :seq, + :granted_by_id, + :notification_id, + :featured_rank, + ) .order(:granted_at) .each do |ub| - yield [ - ub.badge_id, - ub.badge.display_name, - ub.granted_at, - ub.post_id, - ub.seq, - # Hide the admin's identity, simply indicate human or system - User.human_user_id?(ub.granted_by_id), - ub.notification_id, - ub.featured_rank, - ] - end + yield( + [ + ub.badge_id, + ub.badge.display_name, + ub.granted_at, + ub.post_id, + ub.seq, + # Hide the admin's identity, simply indicate human or system + User.human_user_id?(ub.granted_by_id), + ub.notification_id, + ub.featured_rank, + ] + ) + end end def bookmarks_export return enum_for(:bookmarks_export) unless block_given? - @current_user.bookmarks.where.not(bookmarkable_type: nil).order(:id).each do |bookmark| - link = '' - if guardian.can_see_bookmarkable?(bookmark) - if bookmark.bookmarkable.respond_to?(:full_url) - link = bookmark.bookmarkable.full_url - else - link = bookmark.bookmarkable.url + @current_user + .bookmarks + .where.not(bookmarkable_type: nil) + .order(:id) + .each do |bookmark| + link = "" + if guardian.can_see_bookmarkable?(bookmark) + if bookmark.bookmarkable.respond_to?(:full_url) + link = bookmark.bookmarkable.full_url + else + link = bookmark.bookmarkable.url + end end - end - yield [ - bookmark.bookmarkable_id, - bookmark.bookmarkable_type, - link, - bookmark.name, - bookmark.created_at, - bookmark.updated_at, - bookmark.reminder_at, - bookmark.reminder_last_sent_at, - bookmark.reminder_set_at, - Bookmark.auto_delete_preferences[bookmark.auto_delete_preference], - ] - end + yield( + [ + bookmark.bookmarkable_id, + bookmark.bookmarkable_type, + link, + bookmark.name, + bookmark.created_at, + bookmark.updated_at, + bookmark.reminder_at, + bookmark.reminder_last_sent_at, + bookmark.reminder_set_at, + Bookmark.auto_delete_preferences[bookmark.auto_delete_preference], + ] + ) + end end def category_preferences_export @@ -265,12 +357,14 @@ module Jobs .includes(:category) .merge(Category.secured(guardian)) .each do |cu| - yield [ - cu.category_id, - piped_category_name(cu.category_id, cu.category), - NotificationLevels.all[cu.notification_level], - cu.last_seen_at - ] + yield( + [ + cu.category_id, + piped_category_name(cu.category_id, cu.category), + NotificationLevels.all[cu.notification_level], + cu.last_seen_at, + ] + ) end end @@ -283,20 +377,22 @@ module Jobs .where(post_action_type_id: PostActionType.flag_types.values) .order(:created_at) .each do |pa| - yield [ - pa.id, - pa.post_id, - PostActionType.flag_types[pa.post_action_type_id], - pa.created_at, - pa.updated_at, - pa.deleted_at, - self_or_other(pa.deleted_by_id), - pa.related_post_id, - pa.targets_topic, - # renamed to 'was_take_action' to avoid possibility of thinking this is a synonym of agreed_at - pa.staff_took_action, - ] - end + yield( + [ + pa.id, + pa.post_id, + PostActionType.flag_types[pa.post_action_type_id], + pa.created_at, + pa.updated_at, + pa.deleted_at, + self_or_other(pa.deleted_by_id), + pa.related_post_id, + pa.targets_topic, + # renamed to 'was_take_action' to avoid possibility of thinking this is a synonym of agreed_at + pa.staff_took_action, + ] + ) + end end def likes_export @@ -307,25 +403,29 @@ module Jobs .where(post_action_type_id: PostActionType.types[:like]) .order(:created_at) .each do |pa| - post = Post.with_deleted.find_by(id: pa.post_id) - yield [ - pa.id, - pa.post_id, - post&.topic_id, - post&.post_number, - pa.created_at, - pa.updated_at, - pa.deleted_at, - self_or_other(pa.deleted_by_id), - ] - end + post = Post.with_deleted.find_by(id: pa.post_id) + yield( + [ + pa.id, + pa.post_id, + post&.topic_id, + post&.post_number, + pa.created_at, + pa.updated_at, + pa.deleted_at, + self_or_other(pa.deleted_by_id), + ] + ) + end end def include_post_actions? # Most forums should not have post_action records other than flags and likes, but they are possible in historical oddities. PostAction .where(user_id: @current_user.id) - .where.not(post_action_type_id: PostActionType.flag_types.values + [PostActionType.types[:like]]) + .where.not( + post_action_type_id: PostActionType.flag_types.values + [PostActionType.types[:like]], + ) .exists? end @@ -334,20 +434,24 @@ module Jobs PostAction .with_deleted .where(user_id: @current_user.id) - .where.not(post_action_type_id: PostActionType.flag_types.values + [PostActionType.types[:like]]) + .where.not( + post_action_type_id: PostActionType.flag_types.values + [PostActionType.types[:like]], + ) .order(:created_at) .each do |pa| - yield [ - pa.id, - pa.post_id, - PostActionType.types[pa.post_action_type] || pa.post_action_type, - pa.created_at, - pa.updated_at, - pa.deleted_at, - self_or_other(pa.deleted_by_id), - pa.related_post_id, - ] - end + yield( + [ + pa.id, + pa.post_id, + PostActionType.types[pa.post_action_type] || pa.post_action_type, + pa.created_at, + pa.updated_at, + pa.deleted_at, + self_or_other(pa.deleted_by_id), + pa.related_post_id, + ] + ) + end end def queued_posts_export @@ -358,16 +462,17 @@ module Jobs .where(created_by: @current_user.id) .order(:created_at) .each do |rev| - - yield [ - rev.id, - rev.status, - rev.category_id, - rev.topic_id, - rev.payload['raw'], - MultiJson.dump(rev.payload.slice(*queued_posts_payload_permitted_keys)), - ] - end + yield( + [ + rev.id, + rev.status, + rev.category_id, + rev.topic_id, + rev.payload["raw"], + MultiJson.dump(rev.payload.slice(*queued_posts_payload_permitted_keys)), + ] + ) + end end def visits_export @@ -376,20 +481,15 @@ module Jobs UserVisit .where(user_id: @current_user.id) .order(visited_at: :asc) - .each do |uv| - yield [ - uv.visited_at, - uv.posts_read, - uv.mobile, - uv.time_read, - ] - end + .each { |uv| yield [uv.visited_at, uv.posts_read, uv.mobile, uv.time_read] } end def get_header(entity) - if entity == 'user_list' - header_array = HEADER_ATTRS_FOR['user_list'] + HEADER_ATTRS_FOR['user_stats'] + HEADER_ATTRS_FOR['user_profile'] - header_array.concat(HEADER_ATTRS_FOR['user_sso']) if SiteSetting.enable_discourse_connect + if entity == "user_list" + header_array = + HEADER_ATTRS_FOR["user_list"] + HEADER_ATTRS_FOR["user_stats"] + + HEADER_ATTRS_FOR["user_profile"] + header_array.concat(HEADER_ATTRS_FOR["user_sso"]) if SiteSetting.enable_discourse_connect user_custom_fields = UserField.all if user_custom_fields.present? user_custom_fields.each do |custom_field| @@ -424,9 +524,9 @@ module Jobs if user_id.nil? nil elsif user_id == @current_user.id - 'self' + "self" else - 'other' + "other" end end @@ -434,27 +534,37 @@ module Jobs user_archive_array = [] topic_data = user_archive.topic user_archive = user_archive.as_json - topic_data = Topic.with_deleted.includes(:category).find_by(id: user_archive['topic_id']) if topic_data.nil? + topic_data = + Topic + .with_deleted + .includes(:category) + .find_by(id: user_archive["topic_id"]) if topic_data.nil? return user_archive_array if topic_data.nil? categories = piped_category_name(topic_data.category_id, topic_data.category) - is_pm = topic_data.archetype == "private_message" ? I18n.t("csv_export.boolean_yes") : I18n.t("csv_export.boolean_no") - url = "#{Discourse.base_url}/t/#{topic_data.slug}/#{topic_data.id}/#{user_archive['post_number']}" + is_pm = + ( + if topic_data.archetype == "private_message" + I18n.t("csv_export.boolean_yes") + else + I18n.t("csv_export.boolean_no") + end + ) + url = + "#{Discourse.base_url}/t/#{topic_data.slug}/#{topic_data.id}/#{user_archive["post_number"]}" topic_hash = { - "post_raw" => user_archive['raw'], + "post_raw" => user_archive["raw"], "post_cooked" => user_archive["cooked"], "topic_title" => topic_data.title, "categories" => categories, "is_pm" => is_pm, - "url" => url + "url" => url, } user_archive.merge!(topic_hash) - HEADER_ATTRS_FOR['user_archive'].each do |attr| - user_archive_array.push(user_archive[attr]) - end + HEADER_ATTRS_FOR["user_archive"].each { |attr| user_archive_array.push(user_archive[attr]) } user_archive_array end @@ -462,15 +572,15 @@ module Jobs def get_user_archive_profile_fields(user_profile) user_archive_profile = [] - HEADER_ATTRS_FOR['user_archive_profile'].each do |attr| + HEADER_ATTRS_FOR["user_archive_profile"].each do |attr| data = - if attr == 'bio' - user_profile.attributes['bio_raw'] + if attr == "bio" + user_profile.attributes["bio_raw"] else user_profile.attributes[attr] end - user_archive_profile.push(data) + user_archive_profile.push(data) end user_archive_profile @@ -483,30 +593,24 @@ module Jobs # where type = 'ReviewableQueuedPost' and (payload->'old_queued_post_id') IS NULL # # except raw, created_topic_id, created_post_id - %w{ - composer_open_duration_msecs - is_poll - reply_to_post_number - tags - title - typing_duration_msecs - } + %w[composer_open_duration_msecs is_poll reply_to_post_number tags title typing_duration_msecs] end def notify_user(upload, export_title) post = nil if @current_user - post = if upload.persisted? - SystemMessage.create_from_system_user( - @current_user, - :csv_export_succeeded, - download_link: UploadMarkdown.new(upload).attachment_markdown, - export_title: export_title - ) - else - SystemMessage.create_from_system_user(@current_user, :csv_export_failed) - end + post = + if upload.persisted? + SystemMessage.create_from_system_user( + @current_user, + :csv_export_succeeded, + download_link: UploadMarkdown.new(upload).attachment_markdown, + export_title: export_title, + ) + else + SystemMessage.create_from_system_user(@current_user, :csv_export_failed) + end end post diff --git a/app/jobs/regular/feature_topic_users.rb b/app/jobs/regular/feature_topic_users.rb index 84210a6bafa..83266c725b2 100644 --- a/app/jobs/regular/feature_topic_users.rb +++ b/app/jobs/regular/feature_topic_users.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class FeatureTopicUsers < ::Jobs::Base - def execute(args) topic_id = args[:topic_id] raise Discourse::InvalidParameters.new(:topic_id) unless topic_id.present? @@ -17,7 +15,5 @@ module Jobs topic.feature_topic_users(args) end - end - end diff --git a/app/jobs/regular/generate_topic_thumbnails.rb b/app/jobs/regular/generate_topic_thumbnails.rb index e670e0fe41b..2ad13a2d71e 100644 --- a/app/jobs/regular/generate_topic_thumbnails.rb +++ b/app/jobs/regular/generate_topic_thumbnails.rb @@ -2,7 +2,7 @@ module Jobs class GenerateTopicThumbnails < ::Jobs::Base - sidekiq_options queue: 'ultra_low' + sidekiq_options queue: "ultra_low" def execute(args) topic_id = args[:topic_id] @@ -13,6 +13,5 @@ module Jobs topic = Topic.find_by(id: topic_id) topic&.generate_thumbnails!(extra_sizes: extra_sizes) end - end end diff --git a/app/jobs/regular/group_pm_alert.rb b/app/jobs/regular/group_pm_alert.rb index 530560e16d7..3f562801a98 100644 --- a/app/jobs/regular/group_pm_alert.rb +++ b/app/jobs/regular/group_pm_alert.rb @@ -12,12 +12,10 @@ module Jobs alerter = PostAlerter.new - group.users.where( - "group_users.notification_level = :level", - level: NotificationLevels.all[:tracking] - ).find_each do |u| - alerter.notify_group_summary(u, topic) - end + group + .users + .where("group_users.notification_level = :level", level: NotificationLevels.all[:tracking]) + .find_each { |u| alerter.notify_group_summary(u, topic) } notification_data = { notification_type: Notification.types[:invited_to_private_message], @@ -26,17 +24,18 @@ module Jobs data: { topic_title: topic.title, display_username: user.username, - group_id: group.id - }.to_json + group_id: group.id, + }.to_json, } - group.users.where( - "group_users.notification_level in (:levels) AND user_id != :id", - levels: [NotificationLevels.all[:watching], NotificationLevels.all[:watching_first_post]], - id: user.id - ).find_each do |u| - u.notifications.create!(notification_data) - end + group + .users + .where( + "group_users.notification_level in (:levels) AND user_id != :id", + levels: [NotificationLevels.all[:watching], NotificationLevels.all[:watching_first_post]], + id: user.id, + ) + .find_each { |u| u.notifications.create!(notification_data) } end end end diff --git a/app/jobs/regular/group_pm_update_summary.rb b/app/jobs/regular/group_pm_update_summary.rb index cb6c5a61f10..74f151c77ca 100644 --- a/app/jobs/regular/group_pm_update_summary.rb +++ b/app/jobs/regular/group_pm_update_summary.rb @@ -10,13 +10,12 @@ module Jobs alerter = PostAlerter.new - group.users.where( - "group_users.notification_level = :level", - level: NotificationLevels.all[:tracking] - ).find_each do |u| - alerter.notify_group_summary(u, topic, acting_user_id: args[:acting_user_id]) - end - + group + .users + .where("group_users.notification_level = :level", level: NotificationLevels.all[:tracking]) + .find_each do |u| + alerter.notify_group_summary(u, topic, acting_user_id: args[:acting_user_id]) + end end end end diff --git a/app/jobs/regular/group_smtp_email.rb b/app/jobs/regular/group_smtp_email.rb index b0fc4a890e9..6970c673ff9 100644 --- a/app/jobs/regular/group_smtp_email.rb +++ b/app/jobs/regular/group_smtp_email.rb @@ -4,7 +4,7 @@ module Jobs class GroupSmtpEmail < ::Jobs::Base include Skippable - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" sidekiq_retry_in do |count, exception| # retry in an hour when SMTP server is busy @@ -25,9 +25,7 @@ module Jobs recipient_user = User.find_by_email(email, primary: true) post = Post.find_by(id: args[:post_id]) - if post.blank? - return skip(email, nil, recipient_user, :group_smtp_post_deleted) - end + return skip(email, nil, recipient_user, :group_smtp_post_deleted) if post.blank? group = Group.find_by(id: args[:group_id]) return if group.blank? @@ -40,9 +38,8 @@ module Jobs return skip(email, post, recipient_user, :group_smtp_topic_deleted) end - cc_addresses = args[:cc_emails].filter do |address| - EmailAddressValidator.valid_value?(address) - end + cc_addresses = + args[:cc_emails].filter { |address| EmailAddressValidator.valid_value?(address) } # Mask the email addresses of non-staged users so # they are not revealed unnecessarily when we are sending @@ -63,22 +60,29 @@ module Jobs # for example in cases where we are creating a new topic to reply to another # group PM and we need to send the participants the group OP email. if post.is_first_post? && group.imap_enabled - ImapSyncLog.warn("Aborting SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}, the post is the OP and should not send an email.", group) + ImapSyncLog.warn( + "Aborting SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}, the post is the OP and should not send an email.", + group, + ) return end - ImapSyncLog.debug("Sending SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}.", group) + ImapSyncLog.debug( + "Sending SMTP email for post #{post.id} in topic #{post.topic_id} to #{email}.", + group, + ) # The EmailLog record created by the sender will have the raw email # stored, the group smtp ID, and any cc addresses recorded for later # cross referencing. - message = GroupSmtpMailer.send_mail( - group, - email, - post, - cc_addresses: cc_addresses, - bcc_addresses: bcc_addresses - ) + message = + GroupSmtpMailer.send_mail( + group, + email, + post, + cc_addresses: cc_addresses, + bcc_addresses: bcc_addresses, + ) Email::Sender.new(message, :group_smtp, recipient_user).send # Create an incoming email record to avoid importing again from IMAP @@ -95,12 +99,12 @@ module Jobs to_addresses: message.to, cc_addresses: message.cc, from_address: message.from, - created_via: IncomingEmail.created_via_types[:group_smtp] + created_via: IncomingEmail.created_via_types[:group_smtp], ) end def quit_email_early? - SiteSetting.disable_emails == 'yes' || !SiteSetting.enable_smtp + SiteSetting.disable_emails == "yes" || !SiteSetting.enable_smtp end def skip(email, post, recipient_user, reason) @@ -109,7 +113,7 @@ module Jobs to_address: email, user_id: recipient_user&.id, post_id: post&.id, - reason_type: SkippedEmailLog.reason_types[reason] + reason_type: SkippedEmailLog.reason_types[reason], ) end end diff --git a/app/jobs/regular/invite_email.rb b/app/jobs/regular/invite_email.rb index 5d0b0e68df6..ad33a9ee5ce 100644 --- a/app/jobs/regular/invite_email.rb +++ b/app/jobs/regular/invite_email.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true module Jobs - # Asynchronously send an email class InviteEmail < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:invite_id) unless args[:invite_id].present? diff --git a/app/jobs/regular/invite_password_instructions_email.rb b/app/jobs/regular/invite_password_instructions_email.rb index 14ccec97c45..8989772ba8d 100644 --- a/app/jobs/regular/invite_password_instructions_email.rb +++ b/app/jobs/regular/invite_password_instructions_email.rb @@ -1,17 +1,13 @@ # frozen_string_literal: true module Jobs - # Asynchronously send an email class InvitePasswordInstructionsEmail < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:username) unless args[:username].present? user = User.find_by_username_or_email(args[:username]) message = InviteMailer.send_password_instructions(user) Email::Sender.new(message, :invite_password_instructions).send end - end - end diff --git a/app/jobs/regular/make_embedded_topic_visible.rb b/app/jobs/regular/make_embedded_topic_visible.rb index c3c4121b5f4..0d8e90a6185 100644 --- a/app/jobs/regular/make_embedded_topic_visible.rb +++ b/app/jobs/regular/make_embedded_topic_visible.rb @@ -2,14 +2,12 @@ module Jobs class MakeEmbeddedTopicVisible < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:topic_id) if args[:topic_id].blank? if topic = Topic.find_by(id: args[:topic_id]) - topic.update_status('visible', true, topic.user) + topic.update_status("visible", true, topic.user) end end - end end diff --git a/app/jobs/regular/merge_user.rb b/app/jobs/regular/merge_user.rb index e759b08d18d..abfb3d10ab6 100644 --- a/app/jobs/regular/merge_user.rb +++ b/app/jobs/regular/merge_user.rb @@ -2,7 +2,6 @@ module Jobs class MergeUser < ::Jobs::Base - def execute(args) target_user_id = args[:target_user_id] current_user_id = args[:current_user_id] @@ -15,9 +14,16 @@ module Jobs if user = UserMerger.new(user, target_user, current_user).merge! user_json = AdminDetailedUserSerializer.new(user, serializer_opts).as_json - ::MessageBus.publish '/merge_user', { success: 'OK' }.merge(merged: true, user: user_json), user_ids: [current_user.id] + ::MessageBus.publish "/merge_user", + { success: "OK" }.merge(merged: true, user: user_json), + user_ids: [current_user.id] else - ::MessageBus.publish '/merge_user', { failed: 'FAILED' }.merge(user: AdminDetailedUserSerializer.new(@user, serializer_opts).as_json), user_ids: [current_user.id] + ::MessageBus.publish "/merge_user", + { failed: "FAILED" }.merge( + user: + AdminDetailedUserSerializer.new(@user, serializer_opts).as_json, + ), + user_ids: [current_user.id] end end end diff --git a/app/jobs/regular/notify_category_change.rb b/app/jobs/regular/notify_category_change.rb index fbad7582e4a..f1f6c020f04 100644 --- a/app/jobs/regular/notify_category_change.rb +++ b/app/jobs/regular/notify_category_change.rb @@ -7,7 +7,11 @@ module Jobs if post&.topic&.visible? post_alerter = PostAlerter.new - post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), include_tag_watchers: false) + post_alerter.notify_post_users( + post, + User.where(id: args[:notified_user_ids]), + include_tag_watchers: false, + ) post_alerter.notify_first_post_watchers(post, post_alerter.category_watchers(post.topic)) end end diff --git a/app/jobs/regular/notify_mailing_list_subscribers.rb b/app/jobs/regular/notify_mailing_list_subscribers.rb index 03a431ab3f4..467af7cff79 100644 --- a/app/jobs/regular/notify_mailing_list_subscribers.rb +++ b/app/jobs/regular/notify_mailing_list_subscribers.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true module Jobs - class NotifyMailingListSubscribers < ::Jobs::Base include Skippable RETRY_TIMES = [5.minute, 15.minute, 30.minute, 45.minute, 90.minute, 180.minute, 300.minute] - sidekiq_options queue: 'low' + sidekiq_options queue: "low" sidekiq_options retry: RETRY_TIMES.size @@ -28,72 +27,97 @@ module Jobs post_id = args[:post_id] post = post_id ? Post.with_deleted.find_by(id: post_id) : nil - return if !post || post.trashed? || post.user_deleted? || - !post.topic || post.raw.blank? || post.topic.private_message? + if !post || post.trashed? || post.user_deleted? || !post.topic || post.raw.blank? || + post.topic.private_message? + return + end users = - User.activated.not_silenced.not_suspended.real - .joins(:user_option) - .where('user_options.mailing_list_mode AND user_options.mailing_list_mode_frequency > 0') - .where('NOT EXISTS ( + User + .activated + .not_silenced + .not_suspended + .real + .joins(:user_option) + .where("user_options.mailing_list_mode AND user_options.mailing_list_mode_frequency > 0") + .where( + "NOT EXISTS ( SELECT 1 FROM muted_users mu WHERE mu.muted_user_id = ? AND mu.user_id = users.id - )', post.user_id) - .where('NOT EXISTS ( + )", + post.user_id, + ) + .where( + "NOT EXISTS ( SELECT 1 FROM ignored_users iu WHERE iu.ignored_user_id = ? AND iu.user_id = users.id - )', post.user_id) - .where('NOT EXISTS ( + )", + post.user_id, + ) + .where( + "NOT EXISTS ( SELECT 1 FROM topic_users tu WHERE tu.topic_id = ? AND tu.user_id = users.id AND tu.notification_level = ? - )', post.topic_id, TopicUser.notification_levels[:muted]) - .where('NOT EXISTS ( + )", + post.topic_id, + TopicUser.notification_levels[:muted], + ) + .where( + "NOT EXISTS ( SELECT 1 FROM category_users cu WHERE cu.category_id = ? AND cu.user_id = users.id AND cu.notification_level = ? - )', post.topic.category_id, CategoryUser.notification_levels[:muted]) + )", + post.topic.category_id, + CategoryUser.notification_levels[:muted], + ) if SiteSetting.tagging_enabled? - users = users.where('NOT EXISTS ( + users = + users.where( + "NOT EXISTS ( SELECT 1 FROM tag_users tu WHERE tu.tag_id in (:tag_ids) AND tu.user_id = users.id AND tu.notification_level = :muted - )', tag_ids: post.topic.tag_ids, muted: TagUser.notification_levels[:muted]) + )", + tag_ids: post.topic.tag_ids, + muted: TagUser.notification_levels[:muted], + ) end - if SiteSetting.must_approve_users - users = users.where(approved: true) - end + users = users.where(approved: true) if SiteSetting.must_approve_users - if SiteSetting.mute_all_categories_by_default - users = users.watching_topic(post.topic) - end + users = users.watching_topic(post.topic) if SiteSetting.mute_all_categories_by_default DiscourseEvent.trigger(:notify_mailing_list_subscribers, users, post) users.find_each do |user| if Guardian.new(user).can_see?(post) if EmailLog.reached_max_emails?(user) - skip(user.email, user.id, post.id, - SkippedEmailLog.reason_types[:exceeded_emails_limit] - ) + skip(user.email, user.id, post.id, SkippedEmailLog.reason_types[:exceeded_emails_limit]) next end if user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold - skip(user.email, user.id, post.id, - SkippedEmailLog.reason_types[:exceeded_bounces_limit] + skip( + user.email, + user.id, + post.id, + SkippedEmailLog.reason_types[:exceeded_bounces_limit], ) next end if (user.id == post.user_id) && (user.user_option.mailing_list_mode_frequency == 2) - skip(user.email, user.id, post.id, - SkippedEmailLog.reason_types[:mailing_list_no_echo_mode] + skip( + user.email, + user.id, + post.id, + SkippedEmailLog.reason_types[:mailing_list_no_echo_mode], ) next @@ -106,20 +130,27 @@ module Jobs end end rescue => e - Discourse.handle_job_exception(e, error_context(args, "Sending post to mailing list subscribers", user_id: user.id, user_email: user.email)) + Discourse.handle_job_exception( + e, + error_context( + args, + "Sending post to mailing list subscribers", + user_id: user.id, + user_email: user.email, + ), + ) end end end - end def skip(to_address, user_id, post_id, reason_type) create_skipped_email_log( - email_type: 'mailing_list', + email_type: "mailing_list", to_address: to_address, user_id: user_id, post_id: post_id, - reason_type: reason_type + reason_type: reason_type, ) end end diff --git a/app/jobs/regular/notify_moved_posts.rb b/app/jobs/regular/notify_moved_posts.rb index f02feec7a67..d97b52f2168 100644 --- a/app/jobs/regular/notify_moved_posts.rb +++ b/app/jobs/regular/notify_moved_posts.rb @@ -1,33 +1,33 @@ # frozen_string_literal: true module Jobs - class NotifyMovedPosts < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:post_ids) if args[:post_ids].blank? raise Discourse::InvalidParameters.new(:moved_by_id) if args[:moved_by_id].blank? # Make sure we don't notify the same user twice (in case multiple posts were moved at once.) users_notified = Set.new - posts = Post.where(id: args[:post_ids]).where('user_id <> ?', args[:moved_by_id]).includes(:user, :topic) + posts = + Post + .where(id: args[:post_ids]) + .where("user_id <> ?", args[:moved_by_id]) + .includes(:user, :topic) if posts.present? moved_by = User.find_by(id: args[:moved_by_id]) posts.each do |p| unless users_notified.include?(p.user_id) - p.user.notifications.create(notification_type: Notification.types[:moved_post], - topic_id: p.topic_id, - post_number: p.post_number, - data: { topic_title: p.topic.title, - display_username: moved_by.username }.to_json) + p.user.notifications.create( + notification_type: Notification.types[:moved_post], + topic_id: p.topic_id, + post_number: p.post_number, + data: { topic_title: p.topic.title, display_username: moved_by.username }.to_json, + ) users_notified << p.user_id end end end - end - end - end diff --git a/app/jobs/regular/notify_post_revision.rb b/app/jobs/regular/notify_post_revision.rb index f4b0262e06a..f37111bc25d 100644 --- a/app/jobs/regular/notify_post_revision.rb +++ b/app/jobs/regular/notify_post_revision.rb @@ -9,18 +9,20 @@ module Jobs return if post_revision.nil? ActiveRecord::Base.transaction do - User.where(id: args[:user_ids]).find_each do |user| - next if post_revision.hidden && !user.staff? + User + .where(id: args[:user_ids]) + .find_each do |user| + next if post_revision.hidden && !user.staff? - PostActionNotifier.alerter.create_notification( - user, - Notification.types[:edited], - post_revision.post, - display_username: post_revision.user.username, - acting_user_id: post_revision&.user_id, - revision_number: post_revision.number - ) - end + PostActionNotifier.alerter.create_notification( + user, + Notification.types[:edited], + post_revision.post, + display_username: post_revision.user.username, + acting_user_id: post_revision&.user_id, + revision_number: post_revision.number, + ) + end end end end diff --git a/app/jobs/regular/notify_reviewable.rb b/app/jobs/regular/notify_reviewable.rb index 5b71ac557e1..a59adc4ac19 100644 --- a/app/jobs/regular/notify_reviewable.rb +++ b/app/jobs/regular/notify_reviewable.rb @@ -11,16 +11,15 @@ class Jobs::NotifyReviewable < ::Jobs::Base all_updates = Hash.new { |h, k| h[k] = {} } if args[:updated_reviewable_ids].present? - Reviewable.where(id: args[:updated_reviewable_ids]).each do |r| - payload = { - last_performing_username: args[:performing_username], - status: r.status - } + Reviewable + .where(id: args[:updated_reviewable_ids]) + .each do |r| + payload = { last_performing_username: args[:performing_username], status: r.status } - all_updates[:admins][r.id] = payload - all_updates[:moderators][r.id] = payload if r.reviewable_by_moderator? - all_updates[r.reviewable_by_group_id][r.id] = payload if r.reviewable_by_group_id - end + all_updates[:admins][r.id] = payload + all_updates[:moderators][r.id] = payload if r.reviewable_by_moderator? + all_updates[r.reviewable_by_group_id][r.id] = payload if r.reviewable_by_group_id + end end counts = Hash.new(0) @@ -38,10 +37,7 @@ class Jobs::NotifyReviewable < ::Jobs::Base updates: all_updates[:admins], ) else - notify_users( - User.real.admins, - all_updates[:admins] - ) + notify_users(User.real.admins, all_updates[:admins]) end if reviewable.reviewable_by_moderator? @@ -54,7 +50,7 @@ class Jobs::NotifyReviewable < ::Jobs::Base else notify_users( User.real.moderators.where("id NOT IN (?)", @contacted), - all_updates[:moderators] + all_updates[:moderators], ) end end diff --git a/app/jobs/regular/notify_tag_change.rb b/app/jobs/regular/notify_tag_change.rb index 0fcbbc621e8..0038dba193c 100644 --- a/app/jobs/regular/notify_tag_change.rb +++ b/app/jobs/regular/notify_tag_change.rb @@ -9,10 +9,12 @@ module Jobs if post&.topic&.visible? post_alerter = PostAlerter.new - post_alerter.notify_post_users(post, User.where(id: args[:notified_user_ids]), + post_alerter.notify_post_users( + post, + User.where(id: args[:notified_user_ids]), group_ids: all_tags_in_hidden_groups?(args) ? tag_group_ids(args) : nil, include_topic_watchers: !post.topic.private_message?, - include_category_watchers: false + include_category_watchers: false, ) post_alerter.notify_first_post_watchers(post, post_alerter.tag_watchers(post.topic)) end @@ -32,7 +34,10 @@ module Jobs end def tag_group_ids(args) - Tag.where(name: args[:diff_tags]).joins(tag_groups: :tag_group_permissions).pluck("tag_group_permissions.group_id") + Tag + .where(name: args[:diff_tags]) + .joins(tag_groups: :tag_group_permissions) + .pluck("tag_group_permissions.group_id") end end end diff --git a/app/jobs/regular/open_topic.rb b/app/jobs/regular/open_topic.rb index f1e9fe0d2ff..8726d2c09f8 100644 --- a/app/jobs/regular/open_topic.rb +++ b/app/jobs/regular/open_topic.rb @@ -21,13 +21,12 @@ module Jobs topic.set_or_create_timer( TopicTimer.types[:open], SiteSetting.num_hours_to_close_topic, - by_user: Discourse.system_user + by_user: Discourse.system_user, ) else - # autoclosed, false is just another way of saying open. # this handles deleting the topic timer as well, see TopicStatusUpdater - topic.update_status('autoclosed', false, user) + topic.update_status("autoclosed", false, user) end topic.inherit_auto_close_from_category(timer_type: :close) diff --git a/app/jobs/regular/post_alert.rb b/app/jobs/regular/post_alert.rb index dc6b8b1440b..b3aa1c1f00b 100644 --- a/app/jobs/regular/post_alert.rb +++ b/app/jobs/regular/post_alert.rb @@ -2,7 +2,6 @@ module Jobs class PostAlert < ::Jobs::Base - def execute(args) post = Post.find_by(id: args[:post_id]) if post&.topic && post.raw.present? @@ -11,6 +10,5 @@ module Jobs PostAlerter.new(opts).after_save_post(post, new_record) end end - end end diff --git a/app/jobs/regular/post_update_topic_tracking_state.rb b/app/jobs/regular/post_update_topic_tracking_state.rb index c3d4610f802..da3a11268d8 100644 --- a/app/jobs/regular/post_update_topic_tracking_state.rb +++ b/app/jobs/regular/post_update_topic_tracking_state.rb @@ -2,7 +2,6 @@ module Jobs class PostUpdateTopicTrackingState < ::Jobs::Base - def execute(args) post = Post.find_by(id: args[:post_id]) return if !post&.topic @@ -10,15 +9,9 @@ module Jobs topic = post.topic if topic.private_message? - if post.post_number > 1 - PrivateMessageTopicTrackingState.publish_unread(post) - end + PrivateMessageTopicTrackingState.publish_unread(post) if post.post_number > 1 - TopicGroup.new_message_update( - topic.last_poster, - topic.id, - post.post_number - ) + TopicGroup.new_message_update(topic.last_poster, topic.id, post.post_number) else TopicTrackingState.publish_unmuted(post.topic) if post.post_number > 1 @@ -28,6 +21,5 @@ module Jobs TopicTrackingState.publish_latest(post.topic, post.whisper?) end end - end end diff --git a/app/jobs/regular/process_bulk_invite_emails.rb b/app/jobs/regular/process_bulk_invite_emails.rb index f8b8a13846e..26152b8a873 100644 --- a/app/jobs/regular/process_bulk_invite_emails.rb +++ b/app/jobs/regular/process_bulk_invite_emails.rb @@ -1,17 +1,19 @@ # frozen_string_literal: true module Jobs - class ProcessBulkInviteEmails < ::Jobs::Base - def execute(args) - pending_invite_ids = Invite.where(emailed_status: Invite.emailed_status_types[:bulk_pending]).limit(Invite::BULK_INVITE_EMAIL_LIMIT).pluck(:id) + pending_invite_ids = + Invite + .where(emailed_status: Invite.emailed_status_types[:bulk_pending]) + .limit(Invite::BULK_INVITE_EMAIL_LIMIT) + .pluck(:id) if pending_invite_ids.length > 0 - Invite.where(id: pending_invite_ids).update_all(emailed_status: Invite.emailed_status_types[:sending]) - pending_invite_ids.each do |invite_id| - ::Jobs.enqueue(:invite_email, invite_id: invite_id) - end + Invite.where(id: pending_invite_ids).update_all( + emailed_status: Invite.emailed_status_types[:sending], + ) + pending_invite_ids.each { |invite_id| ::Jobs.enqueue(:invite_email, invite_id: invite_id) } ::Jobs.enqueue_in(1.minute, :process_bulk_invite_emails) end end diff --git a/app/jobs/regular/process_email.rb b/app/jobs/regular/process_email.rb index 2ac10eddd15..0bad7eade23 100644 --- a/app/jobs/regular/process_email.rb +++ b/app/jobs/regular/process_email.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class ProcessEmail < ::Jobs::Base sidekiq_options retry: 3 @@ -9,14 +8,14 @@ module Jobs Email::Processor.process!( args[:mail], retry_on_rate_limit: args[:retry_on_rate_limit] || false, - source: args[:source]&.to_sym + source: args[:source]&.to_sym, ) end sidekiq_retries_exhausted do |msg| - Rails.logger.warn("Incoming email could not be processed after 3 retries.\n\n#{msg["args"][:mail]}") + Rails.logger.warn( + "Incoming email could not be processed after 3 retries.\n\n#{msg["args"][:mail]}", + ) end - end - end diff --git a/app/jobs/regular/process_post.rb b/app/jobs/regular/process_post.rb index 60f444fe981..976d2f75bbf 100644 --- a/app/jobs/regular/process_post.rb +++ b/app/jobs/regular/process_post.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require 'image_sizer' +require "image_sizer" module Jobs - class ProcessPost < ::Jobs::Base - def execute(args) DistributedMutex.synchronize("process_post_#{args[:post_id]}", validity: 10.minutes) do post = Post.find_by(id: args[:post_id]) @@ -19,7 +17,11 @@ module Jobs cooking_options = args[:cooking_options] || {} cooking_options[:topic_id] = post.topic_id recooked = post.cook(post.raw, cooking_options.symbolize_keys) - post.update_columns(cooked: recooked, baked_at: Time.zone.now, baked_version: Post::BAKED_VERSION) + post.update_columns( + cooked: recooked, + baked_at: Time.zone.now, + baked_version: Post::BAKED_VERSION, + ) end cp = CookedPostProcessor.new(post, args) @@ -31,7 +33,9 @@ module Jobs if cooked != (recooked || orig_cooked) if orig_cooked.present? && cooked.blank? # TODO stop/restart the worker if needed, let's gather a few here first - Rails.logger.warn("Cooked post processor in FATAL state, bypassing. You need to urgently restart sidekiq\norig: #{orig_cooked}\nrecooked: #{recooked}\ncooked: #{cooked}\npost id: #{post.id}") + Rails.logger.warn( + "Cooked post processor in FATAL state, bypassing. You need to urgently restart sidekiq\norig: #{orig_cooked}\nrecooked: #{recooked}\ncooked: #{cooked}\npost id: #{post.id}", + ) else post.update_column(:cooked, cp.html) post.topic.update_excerpt(post.excerpt_for_topic) if post.is_first_post? @@ -50,7 +54,7 @@ module Jobs Discourse.system_user, post, :inappropriate, - reason: :watched_word + reason: :watched_word, ) end end @@ -68,5 +72,4 @@ module Jobs Jobs.enqueue(:pull_hotlinked_images, post_id: post.id) end end - end diff --git a/app/jobs/regular/process_sns_notification.rb b/app/jobs/regular/process_sns_notification.rb index 67e9b0e3273..09e965a6ea4 100644 --- a/app/jobs/regular/process_sns_notification.rb +++ b/app/jobs/regular/process_sns_notification.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class ProcessSnsNotification < ::Jobs::Base sidekiq_options retry: false @@ -10,11 +9,12 @@ module Jobs return unless json = args[:json].presence return unless message = json["Message"].presence - message = begin - JSON.parse(message) - rescue JSON::ParserError - nil - end + message = + begin + JSON.parse(message) + rescue JSON::ParserError + nil + end return unless message && message["notificationType"] == "Bounce" return unless message_id = message.dig("mail", "messageId").presence @@ -23,21 +23,29 @@ module Jobs require "aws-sdk-sns" return unless Aws::SNS::MessageVerifier.new.authentic?(raw) - message.dig("bounce", "bouncedRecipients").each do |r| - if email_log = EmailLog.order("created_at DESC").where(to_address: r["emailAddress"]).first - email_log.update_columns(bounced: true, bounce_error_code: r["status"]) + message + .dig("bounce", "bouncedRecipients") + .each do |r| + if email_log = + EmailLog.order("created_at DESC").where(to_address: r["emailAddress"]).first + email_log.update_columns(bounced: true, bounce_error_code: r["status"]) - if email_log.user&.email.present? - if email_log.user.user_stat.bounce_score.to_s.start_with?("4.") || bounce_type == "Transient" - Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.soft_bounce_score) - else - Email::Receiver.update_bounce_score(email_log.user.email, SiteSetting.hard_bounce_score) + if email_log.user&.email.present? + if email_log.user.user_stat.bounce_score.to_s.start_with?("4.") || + bounce_type == "Transient" + Email::Receiver.update_bounce_score( + email_log.user.email, + SiteSetting.soft_bounce_score, + ) + else + Email::Receiver.update_bounce_score( + email_log.user.email, + SiteSetting.hard_bounce_score, + ) + end end end end - end end - end - end diff --git a/app/jobs/regular/publish_group_membership_updates.rb b/app/jobs/regular/publish_group_membership_updates.rb index 6b93e87a0b2..5a5727717bb 100644 --- a/app/jobs/regular/publish_group_membership_updates.rb +++ b/app/jobs/regular/publish_group_membership_updates.rb @@ -11,13 +11,16 @@ module Jobs added_members = args[:type] == Group::AUTO_GROUPS_ADD - User.human_users.where(id: args[:user_ids]).each do |user| - if added_members - group.trigger_user_added_event(user, group.automatic?) - else - group.trigger_user_removed_event(user) + User + .human_users + .where(id: args[:user_ids]) + .each do |user| + if added_members + group.trigger_user_added_event(user, group.automatic?) + else + group.trigger_user_removed_event(user) + end end - end end end end diff --git a/app/jobs/regular/pull_hotlinked_images.rb b/app/jobs/regular/pull_hotlinked_images.rb index 7c1accdb89a..43a64f519eb 100644 --- a/app/jobs/regular/pull_hotlinked_images.rb +++ b/app/jobs/regular/pull_hotlinked_images.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true module Jobs - class PullHotlinkedImages < ::Jobs::Base - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def initialize @max_size = SiteSetting.max_image_size_kb.kilobytes @@ -23,8 +22,12 @@ module Jobs changed_hotlink_records = false extract_images_from(post.cooked).each do |node| - download_src = original_src = node['src'] || node[PrettyText::BLOCKED_HOTLINKED_SRC_ATTR] || node['href'] - download_src = "#{SiteSetting.force_https ? "https" : "http"}:#{original_src}" if original_src.start_with?("//") + download_src = + original_src = node["src"] || node[PrettyText::BLOCKED_HOTLINKED_SRC_ATTR] || node["href"] + download_src = + "#{SiteSetting.force_https ? "https" : "http"}:#{original_src}" if original_src.start_with?( + "//", + ) normalized_src = normalize_src(download_src) next if !should_download_image?(download_src, post) @@ -32,10 +35,8 @@ module Jobs hotlink_record = hotlinked_map[normalized_src] if hotlink_record.nil? - hotlinked_map[normalized_src] = hotlink_record = PostHotlinkedMedia.new( - post: post, - url: normalized_src - ) + hotlinked_map[normalized_src] = hotlink_record = + PostHotlinkedMedia.new(post: post, url: normalized_src) begin hotlink_record.upload = attempt_download(download_src, post.user_id) hotlink_record.status = :downloaded @@ -54,13 +55,17 @@ module Jobs end rescue => e raise e if Rails.env.test? - log(:error, "Failed to pull hotlinked image (#{download_src}) post: #{@post_id}\n" + e.message + "\n" + e.backtrace.join("\n")) + log( + :error, + "Failed to pull hotlinked image (#{download_src}) post: #{@post_id}\n" + e.message + + "\n" + e.backtrace.join("\n"), + ) end if changed_hotlink_records post.trigger_post_process( bypass_bump: true, - skip_pull_hotlinked_images: true # Avoid an infinite loop of job scheduling + skip_pull_hotlinked_images: true, # Avoid an infinite loop of job scheduling ) end @@ -81,14 +86,15 @@ module Jobs Rails.logger.warn("Verbose Upload Logging: Downloading hotlinked image from #{src}") end - downloaded = FileHelper.download( - src, - max_file_size: @max_size, - retain_on_max_file_size_exceeded: true, - tmp_file_name: "discourse-hotlinked", - follow_redirect: true, - read_timeout: 15 - ) + downloaded = + FileHelper.download( + src, + max_file_size: @max_size, + retain_on_max_file_size_exceeded: true, + tmp_file_name: "discourse-hotlinked", + follow_redirect: true, + read_timeout: 15, + ) rescue => e if SiteSetting.verbose_upload_logging Rails.logger.warn("Verbose Upload Logging: Error '#{e.message}' while downloading #{src}") @@ -103,9 +109,12 @@ module Jobs downloaded end - class ImageTooLargeError < StandardError; end - class ImageBrokenError < StandardError; end - class UploadCreateError < StandardError; end + class ImageTooLargeError < StandardError + end + class ImageBrokenError < StandardError + end + class UploadCreateError < StandardError + end def attempt_download(src, user_id) # secure-uploads endpoint prevents anonymous downloads, so we @@ -123,31 +132,32 @@ module Jobs if upload.persisted? upload else - log(:info, "Failed to persist downloaded hotlinked image for post: #{@post_id}: #{src} - #{upload.errors.full_messages.join("\n")}") + log( + :info, + "Failed to persist downloaded hotlinked image for post: #{@post_id}: #{src} - #{upload.errors.full_messages.join("\n")}", + ) raise UploadCreateError end end def extract_images_from(html) - doc = Nokogiri::HTML5::fragment(html) + doc = Nokogiri::HTML5.fragment(html) doc.css("img[src], [#{PrettyText::BLOCKED_HOTLINKED_SRC_ATTR}], a.lightbox[href]") - - doc.css("img.avatar") - - doc.css(".lightbox img[src]") + doc.css("img.avatar") - doc.css(".lightbox img[src]") end def should_download_image?(src, post = nil) # make sure we actually have a url return false unless src.present? - local_bases = [ - Discourse.base_url, - Discourse.asset_host, - SiteSetting.external_emoji_url.presence - ].compact.map { |s| normalize_src(s) } + local_bases = + [Discourse.base_url, Discourse.asset_host, SiteSetting.external_emoji_url.presence].compact + .map { |s| normalize_src(s) } - if Discourse.store.has_been_uploaded?(src) || normalize_src(src).start_with?(*local_bases) || src =~ /\A\/[^\/]/i - return false if !(src =~ /\/uploads\// || Upload.secure_uploads_url?(src)) + if Discourse.store.has_been_uploaded?(src) || normalize_src(src).start_with?(*local_bases) || + src =~ %r{\A/[^/]}i + return false if !(src =~ %r{/uploads/} || Upload.secure_uploads_url?(src)) # Someone could hotlink a file from a different site on the same CDN, # so check whether we have it in this database @@ -182,7 +192,7 @@ module Jobs def log(log_level, message) Rails.logger.public_send( log_level, - "#{RailsMultisite::ConnectionManagement.current_db}: #{message}" + "#{RailsMultisite::ConnectionManagement.current_db}: #{message}", ) end @@ -202,19 +212,26 @@ module Jobs # log the site setting change reason = I18n.t("disable_remote_images_download_reason") staff_action_logger = StaffActionLogger.new(Discourse.system_user) - staff_action_logger.log_site_setting_change("download_remote_images_to_local", true, false, details: reason) + staff_action_logger.log_site_setting_change( + "download_remote_images_to_local", + true, + false, + details: reason, + ) # also send a private message to the site contact user notify_about_low_disk_space notify_about_low_disk_space end def notify_about_low_disk_space - SystemMessage.create_from_system_user(Discourse.site_contact_user, :download_remote_images_disabled) + SystemMessage.create_from_system_user( + Discourse.site_contact_user, + :download_remote_images_disabled, + ) end def available_disk_space 100 - DiskSpace.percent_free("#{Rails.root}/public/uploads") end end - end diff --git a/app/jobs/regular/pull_user_profile_hotlinked_images.rb b/app/jobs/regular/pull_user_profile_hotlinked_images.rb index 22c7b21e9dc..89d0f910862 100644 --- a/app/jobs/regular/pull_user_profile_hotlinked_images.rb +++ b/app/jobs/regular/pull_user_profile_hotlinked_images.rb @@ -14,14 +14,20 @@ module Jobs downloaded_images = {} extract_images_from(user_profile.bio_cooked).each do |node| - download_src = original_src = node['src'] || node['href'] - download_src = "#{SiteSetting.force_https ? "https" : "http"}:#{original_src}" if original_src.start_with?("//") + download_src = original_src = node["src"] || node["href"] + download_src = + "#{SiteSetting.force_https ? "https" : "http"}:#{original_src}" if original_src.start_with?( + "//", + ) normalized_src = normalize_src(download_src) next if !should_download_image?(download_src) begin - already_attempted_download = downloaded_images.include?(normalized_src) || large_image_urls.include?(normalized_src) || broken_image_urls.include?(normalized_src) + already_attempted_download = + downloaded_images.include?(normalized_src) || + large_image_urls.include?(normalized_src) || + broken_image_urls.include?(normalized_src) if !already_attempted_download downloaded_images[normalized_src] = attempt_download(download_src, @user_id) end @@ -32,13 +38,18 @@ module Jobs end rescue => e raise e if Rails.env.test? - log(:error, "Failed to pull hotlinked image (#{download_src}) user: #{@user_id}\n" + e.message + "\n" + e.backtrace.join("\n")) + log( + :error, + "Failed to pull hotlinked image (#{download_src}) user: #{@user_id}\n" + e.message + + "\n" + e.backtrace.join("\n"), + ) end - user_profile.bio_raw = InlineUploads.replace_hotlinked_image_urls(raw: user_profile.bio_raw) do |match_src| - normalized_match_src = PostHotlinkedMedia.normalize_src(match_src) - downloaded_images[normalized_match_src] - end + user_profile.bio_raw = + InlineUploads.replace_hotlinked_image_urls(raw: user_profile.bio_raw) do |match_src| + normalized_match_src = PostHotlinkedMedia.normalize_src(match_src) + downloaded_images[normalized_match_src] + end user_profile.skip_pull_hotlinked_image = true user_profile.save! diff --git a/app/jobs/regular/push_notification.rb b/app/jobs/regular/push_notification.rb index 16026323e74..1459bebc2b8 100644 --- a/app/jobs/regular/push_notification.rb +++ b/app/jobs/regular/push_notification.rb @@ -4,7 +4,9 @@ module Jobs class PushNotification < ::Jobs::Base def execute(args) notification = args["payload"] - notification["url"] = UrlHelper.absolute_without_cdn(Discourse.base_path + notification["post_url"]) + notification["url"] = UrlHelper.absolute_without_cdn( + Discourse.base_path + notification["post_url"], + ) notification.delete("post_url") payload = { @@ -15,24 +17,30 @@ module Jobs } clients = args["clients"] - clients.group_by { |r| r[1] }.each do |push_url, group| - notifications = group.map do |client_id, _| - notification.merge(client_id: client_id) + clients + .group_by { |r| r[1] } + .each do |push_url, group| + notifications = group.map { |client_id, _| notification.merge(client_id: client_id) } + + next unless push_url.present? + + result = + Excon.post( + push_url, + body: payload.merge(notifications: notifications).to_json, + headers: { + "Content-Type" => "application/json", + "Accept" => "application/json", + }, + ) + + if result.status != 200 + # we failed to push a notification ... log it + Rails.logger.warn( + "Failed to push a notification to #{push_url} Status: #{result.status}: #{result.status_line}", + ) + end end - - next unless push_url.present? - - result = Excon.post(push_url, - body: payload.merge(notifications: notifications).to_json, - headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' } - ) - - if result.status != 200 - # we failed to push a notification ... log it - Rails.logger.warn("Failed to push a notification to #{push_url} Status: #{result.status}: #{result.status_line}") - end - end - end end end diff --git a/app/jobs/regular/refresh_users_reviewable_counts.rb b/app/jobs/regular/refresh_users_reviewable_counts.rb index b16f72ef64c..10fa49467ff 100644 --- a/app/jobs/regular/refresh_users_reviewable_counts.rb +++ b/app/jobs/regular/refresh_users_reviewable_counts.rb @@ -4,8 +4,8 @@ class Jobs::RefreshUsersReviewableCounts < ::Jobs::Base def execute(args) group_ids = args[:group_ids] return if group_ids.blank? - User.where( - id: GroupUser.where(group_id: group_ids).distinct.pluck(:user_id) - ).each(&:publish_reviewable_counts) + User.where(id: GroupUser.where(group_id: group_ids).distinct.pluck(:user_id)).each( + &:publish_reviewable_counts + ) end end diff --git a/app/jobs/regular/remove_banner.rb b/app/jobs/regular/remove_banner.rb index 880c8696c94..a8c1405a688 100644 --- a/app/jobs/regular/remove_banner.rb +++ b/app/jobs/regular/remove_banner.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class RemoveBanner < ::Jobs::Base - def execute(args) topic_id = args[:topic_id] @@ -12,7 +10,5 @@ module Jobs topic = Topic.find_by(id: topic_id) topic.remove_banner!(Discourse.system_user) if topic.present? end - end - end diff --git a/app/jobs/regular/retrieve_topic.rb b/app/jobs/regular/retrieve_topic.rb index ac3f0aacad7..5cdcd3496b0 100644 --- a/app/jobs/regular/retrieve_topic.rb +++ b/app/jobs/regular/retrieve_topic.rb @@ -1,20 +1,18 @@ # frozen_string_literal: true module Jobs - # Asynchronously retrieve a topic from an embedded site class RetrieveTopic < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:embed_url) unless args[:embed_url].present? user = nil - if args[:user_id] - user = User.find_by(id: args[:user_id]) - end - TopicRetriever.new(args[:embed_url], author_username: args[:author_username], no_throttle: user.try(:staff?)).retrieve + user = User.find_by(id: args[:user_id]) if args[:user_id] + TopicRetriever.new( + args[:embed_url], + author_username: args[:author_username], + no_throttle: user.try(:staff?), + ).retrieve end - end - end diff --git a/app/jobs/regular/run_heartbeat.rb b/app/jobs/regular/run_heartbeat.rb index 4a0691171ad..549c57ac965 100644 --- a/app/jobs/regular/run_heartbeat.rb +++ b/app/jobs/regular/run_heartbeat.rb @@ -2,11 +2,10 @@ module Jobs class RunHeartbeat < ::Jobs::Base - - sidekiq_options queue: 'critical' + sidekiq_options queue: "critical" def self.heartbeat_key - 'heartbeat_last_run' + "heartbeat_last_run" end def execute(args) diff --git a/app/jobs/regular/send_push_notification.rb b/app/jobs/regular/send_push_notification.rb index 057e32dafb0..d73013fb8b9 100644 --- a/app/jobs/regular/send_push_notification.rb +++ b/app/jobs/regular/send_push_notification.rb @@ -4,7 +4,9 @@ module Jobs class SendPushNotification < ::Jobs::Base def execute(args) user = User.find_by(id: args[:user_id]) - return if !user || user.seen_since?(SiteSetting.push_notification_time_window_mins.minutes.ago) + if !user || user.seen_since?(SiteSetting.push_notification_time_window_mins.minutes.ago) + return + end PushNotificationPusher.push(user, args[:payload]) end diff --git a/app/jobs/regular/send_system_message.rb b/app/jobs/regular/send_system_message.rb index 23ebe656b20..2ee6559dca6 100644 --- a/app/jobs/regular/send_system_message.rb +++ b/app/jobs/regular/send_system_message.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require 'image_sizer' +require "image_sizer" module Jobs - class SendSystemMessage < ::Jobs::Base - def execute(args) raise Discourse::InvalidParameters.new(:user_id) unless args[:user_id].present? raise Discourse::InvalidParameters.new(:message_type) unless args[:message_type].present? @@ -16,7 +14,5 @@ module Jobs system_message = SystemMessage.new(user) system_message.create(args[:message_type], args[:message_options]&.symbolize_keys || {}) end - end - end diff --git a/app/jobs/regular/suspicious_login.rb b/app/jobs/regular/suspicious_login.rb index a45b820de01..0301a8cc3ec 100644 --- a/app/jobs/regular/suspicious_login.rb +++ b/app/jobs/regular/suspicious_login.rb @@ -1,25 +1,24 @@ # frozen_string_literal: true module Jobs - class SuspiciousLogin < ::Jobs::Base - def execute(args) if UserAuthToken.is_suspicious(args[:user_id], args[:client_ip]) + UserAuthToken.log( + action: "suspicious", + user_id: args[:user_id], + user_agent: args[:user_agent], + client_ip: args[:client_ip], + ) - UserAuthToken.log(action: 'suspicious', - user_id: args[:user_id], - user_agent: args[:user_agent], - client_ip: args[:client_ip]) - - ::Jobs.enqueue(:critical_user_email, - type: "suspicious_login", - user_id: args[:user_id], - client_ip: args[:client_ip], - user_agent: args[:user_agent]) + ::Jobs.enqueue( + :critical_user_email, + type: "suspicious_login", + user_id: args[:user_id], + client_ip: args[:client_ip], + user_agent: args[:user_agent], + ) end end - end - end diff --git a/app/jobs/regular/sync_acls_for_uploads.rb b/app/jobs/regular/sync_acls_for_uploads.rb index 237795cb5b6..435c9dbc5a8 100644 --- a/app/jobs/regular/sync_acls_for_uploads.rb +++ b/app/jobs/regular/sync_acls_for_uploads.rb @@ -14,26 +14,32 @@ module Jobs # Note...these log messages are set to warn to ensure this is working # as intended in initial production trials, this will be set to debug # after an acl_stale column is added to uploads. - time = Benchmark.measure do - Rails.logger.warn("Syncing ACL for upload ids: #{args[:upload_ids].join(", ")}") - Upload.includes(:optimized_images).where(id: args[:upload_ids]).find_in_batches do |uploads| - uploads.each do |upload| - begin - Discourse.store.update_upload_ACL(upload, optimized_images_preloaded: true) - rescue => err - Discourse.warn_exception( - err, - message: "Failed to update upload ACL", - env: { - upload_id: upload.id, - filename: upload.original_filename - } - ) + time = + Benchmark.measure do + Rails.logger.warn("Syncing ACL for upload ids: #{args[:upload_ids].join(", ")}") + Upload + .includes(:optimized_images) + .where(id: args[:upload_ids]) + .find_in_batches do |uploads| + uploads.each do |upload| + begin + Discourse.store.update_upload_ACL(upload, optimized_images_preloaded: true) + rescue => err + Discourse.warn_exception( + err, + message: "Failed to update upload ACL", + env: { + upload_id: upload.id, + filename: upload.original_filename, + }, + ) + end + end end - end + Rails.logger.warn( + "Completed syncing ACL for upload ids in #{time.to_s}. IDs: #{args[:upload_ids].join(", ")}", + ) end - Rails.logger.warn("Completed syncing ACL for upload ids in #{time.to_s}. IDs: #{args[:upload_ids].join(", ")}") - end end end end diff --git a/app/jobs/regular/toggle_topic_closed.rb b/app/jobs/regular/toggle_topic_closed.rb index 55f7bce88dd..a02831ca61c 100644 --- a/app/jobs/regular/toggle_topic_closed.rb +++ b/app/jobs/regular/toggle_topic_closed.rb @@ -12,9 +12,7 @@ module Jobs state = !!args[:state] timer_type = args[:silent] ? :silent_close : :close - if topic_timer.blank? || topic_timer.execute_at > Time.zone.now - return - end + return if topic_timer.blank? || topic_timer.execute_at > Time.zone.now if (topic = topic_timer.topic).blank? || topic.closed == state topic_timer.destroy! @@ -28,10 +26,10 @@ module Jobs topic.set_or_create_timer( TopicTimer.types[:open], SiteSetting.num_hours_to_close_topic, - by_user: Discourse.system_user + by_user: Discourse.system_user, ) else - topic.update_status('autoclosed', state, user, { silent: args[:silent] }) + topic.update_status("autoclosed", state, user, { silent: args[:silent] }) end topic.inherit_auto_close_from_category(timer_type: timer_type) if state == false diff --git a/app/jobs/regular/topic_action_converter.rb b/app/jobs/regular/topic_action_converter.rb index 370e8cbb029..1d64ff378db 100644 --- a/app/jobs/regular/topic_action_converter.rb +++ b/app/jobs/regular/topic_action_converter.rb @@ -1,20 +1,29 @@ # frozen_string_literal: true class Jobs::TopicActionConverter < ::Jobs::Base - # Re-creating all the user actions could be very slow, so let's do it in a job # to avoid a N+1 query on a front facing operation. def execute(args) topic = Topic.find_by(id: args[:topic_id]) return if topic.blank? - UserAction.where( - target_topic_id: topic.id, - action_type: [UserAction::GOT_PRIVATE_MESSAGE, UserAction::NEW_PRIVATE_MESSAGE]).find_each do |ua| - UserAction.remove_action!(ua.attributes.symbolize_keys.slice(:action_type, :user_id, :acting_user_id, :target_topic_id, :target_post_id)) + UserAction + .where( + target_topic_id: topic.id, + action_type: [UserAction::GOT_PRIVATE_MESSAGE, UserAction::NEW_PRIVATE_MESSAGE], + ) + .find_each do |ua| + UserAction.remove_action!( + ua.attributes.symbolize_keys.slice( + :action_type, + :user_id, + :acting_user_id, + :target_topic_id, + :target_post_id, + ), + ) end topic.posts.find_each { |post| UserActionManager.post_created(post) } UserActionManager.topic_created(topic) end - end diff --git a/app/jobs/regular/truncate_user_flag_stats.rb b/app/jobs/regular/truncate_user_flag_stats.rb index d75f7bb5aa8..95a00ede7d7 100644 --- a/app/jobs/regular/truncate_user_flag_stats.rb +++ b/app/jobs/regular/truncate_user_flag_stats.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class Jobs::TruncateUserFlagStats < ::Jobs::Base - def self.truncate_to 100 end @@ -17,8 +16,11 @@ class Jobs::TruncateUserFlagStats < ::Jobs::Base total = user_stat.flags_agreed + user_stat.flags_disagreed + user_stat.flags_ignored next if total < self.class.truncate_to - params = ReviewableScore.statuses.slice(:agreed, :disagreed, :ignored). - merge(user_id: u, truncate_to: self.class.truncate_to) + params = + ReviewableScore + .statuses + .slice(:agreed, :disagreed, :ignored) + .merge(user_id: u, truncate_to: self.class.truncate_to) result = DB.query(<<~SQL, params) SELECT SUM(CASE WHEN x.status = :agreed THEN 1 ELSE 0 END) AS agreed, @@ -44,7 +46,5 @@ class Jobs::TruncateUserFlagStats < ::Jobs::Base flags_ignored: result[0].ignored || 0, ) end - end - end diff --git a/app/jobs/regular/unpin_topic.rb b/app/jobs/regular/unpin_topic.rb index 9d4f6b93eed..8e804b1a94d 100644 --- a/app/jobs/regular/unpin_topic.rb +++ b/app/jobs/regular/unpin_topic.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class UnpinTopic < ::Jobs::Base - def execute(args) topic_id = args[:topic_id] @@ -12,7 +10,5 @@ module Jobs topic = Topic.find_by(id: topic_id) topic.update_pinned(false) if topic.present? end - end - end diff --git a/app/jobs/regular/update_gravatar.rb b/app/jobs/regular/update_gravatar.rb index de954b41eea..6133a92c0ed 100644 --- a/app/jobs/regular/update_gravatar.rb +++ b/app/jobs/regular/update_gravatar.rb @@ -2,8 +2,7 @@ module Jobs class UpdateGravatar < ::Jobs::Base - - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) user = User.find_by(id: args[:user_id]) @@ -17,5 +16,4 @@ module Jobs end end end - end diff --git a/app/jobs/regular/update_group_mentions.rb b/app/jobs/regular/update_group_mentions.rb index 4df6b9145f3..50147a3c0ca 100644 --- a/app/jobs/regular/update_group_mentions.rb +++ b/app/jobs/regular/update_group_mentions.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class UpdateGroupMentions < ::Jobs::Base - def execute(args) group = Group.find_by(id: args[:group_id]) return unless group diff --git a/app/jobs/regular/update_hotlinked_raw.rb b/app/jobs/regular/update_hotlinked_raw.rb index 1fb425839bf..e2e99cc7f85 100644 --- a/app/jobs/regular/update_hotlinked_raw.rb +++ b/app/jobs/regular/update_hotlinked_raw.rb @@ -2,7 +2,7 @@ module Jobs class UpdateHotlinkedRaw < ::Jobs::Base - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) @post_id = args[:post_id] @@ -15,10 +15,11 @@ module Jobs hotlinked_map = post.post_hotlinked_media.preload(:upload).map { |r| [r.url, r] }.to_h - raw = InlineUploads.replace_hotlinked_image_urls(raw: post.raw) do |match_src| - normalized_match_src = PostHotlinkedMedia.normalize_src(match_src) - hotlinked_map[normalized_match_src]&.upload - end + raw = + InlineUploads.replace_hotlinked_image_urls(raw: post.raw) do |match_src| + normalized_match_src = PostHotlinkedMedia.normalize_src(match_src) + hotlinked_map[normalized_match_src]&.upload + end if post.raw != raw changes = { raw: raw, edit_reason: I18n.t("upload.edit_reason") } diff --git a/app/jobs/regular/update_post_uploads_secure_status.rb b/app/jobs/regular/update_post_uploads_secure_status.rb index b27b4c92b48..a7faa53c4a3 100644 --- a/app/jobs/regular/update_post_uploads_secure_status.rb +++ b/app/jobs/regular/update_post_uploads_secure_status.rb @@ -6,9 +6,7 @@ module Jobs post = Post.find_by(id: args[:post_id]) return if post.blank? - post.uploads.each do |upload| - upload.update_secure_status(source: args[:source]) - end + post.uploads.each { |upload| upload.update_secure_status(source: args[:source]) } end end end diff --git a/app/jobs/regular/update_s3_inventory.rb b/app/jobs/regular/update_s3_inventory.rb index 46c34a59bf4..699db498860 100644 --- a/app/jobs/regular/update_s3_inventory.rb +++ b/app/jobs/regular/update_s3_inventory.rb @@ -5,13 +5,13 @@ require "s3_inventory" module Jobs # if upload bucket changes or inventory bucket changes we want to update s3 bucket policy and inventory configuration class UpdateS3Inventory < ::Jobs::Base - def execute(args) - return unless SiteSetting.enable_s3_inventory? && - SiteSetting.Upload.enable_s3_uploads && - SiteSetting.s3_configure_inventory_policy + unless SiteSetting.enable_s3_inventory? && SiteSetting.Upload.enable_s3_uploads && + SiteSetting.s3_configure_inventory_policy + return + end - [:upload, :optimized].each do |type| + %i[upload optimized].each do |type| s3_inventory = S3Inventory.new(Discourse.store.s3_helper, type) s3_inventory.update_bucket_policy if type == :upload s3_inventory.update_bucket_inventory_configuration diff --git a/app/jobs/regular/update_top_redirection.rb b/app/jobs/regular/update_top_redirection.rb index 80ad6acefb5..dae128799be 100644 --- a/app/jobs/regular/update_top_redirection.rb +++ b/app/jobs/regular/update_top_redirection.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true module Jobs - class UpdateTopRedirection < ::Jobs::Base - def execute(args) return if args[:user_id].blank? || args[:redirected_at].blank? @@ -13,5 +11,4 @@ module Jobs .update_all(last_redirected_to_top_at: args[:redirected_at]) end end - end diff --git a/app/jobs/regular/update_topic_upload_security.rb b/app/jobs/regular/update_topic_upload_security.rb index 2ac3deb2bd7..dd2eceffe56 100644 --- a/app/jobs/regular/update_topic_upload_security.rb +++ b/app/jobs/regular/update_topic_upload_security.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module Jobs - class UpdateTopicUploadSecurity < ::Jobs::Base - def execute(args) topic = Topic.find_by(id: args[:topic_id]) if topic.blank? - Rails.logger.info("Could not find topic #{args[:topic_id]} for topic upload security updater.") + Rails.logger.info( + "Could not find topic #{args[:topic_id]} for topic upload security updater.", + ) return end TopicUploadSecurityManager.new(topic).run diff --git a/app/jobs/regular/update_username.rb b/app/jobs/regular/update_username.rb index f9660c2358c..3a0ed3194ef 100644 --- a/app/jobs/regular/update_username.rb +++ b/app/jobs/regular/update_username.rb @@ -2,8 +2,7 @@ module Jobs class UpdateUsername < ::Jobs::Base - - sidekiq_options queue: 'low' + sidekiq_options queue: "low" def execute(args) @user_id = args[:user_id] @@ -14,7 +13,8 @@ module Jobs @new_username = args[:new_username].unicode_normalize @avatar_img = PrettyText.avatar_img(args[:avatar_template], "tiny") - @raw_mention_regex = / + @raw_mention_regex = + / (?: (? - (user.user_option&.digest_after_minutes || SiteSetting.default_email_digest_frequency.to_i).minutes.ago + if user.last_emailed_at && + user.last_emailed_at > + ( + user.user_option&.digest_after_minutes || + SiteSetting.default_email_digest_frequency.to_i + ).minutes.ago + return + end end - seen_recently = (user.last_seen_at.present? && user.last_seen_at > SiteSetting.email_time_window_mins.minutes.ago) + seen_recently = + ( + user.last_seen_at.present? && + user.last_seen_at > SiteSetting.email_time_window_mins.minutes.ago + ) if !args[:force_respect_seen_recently] && - (always_email_regular?(user, type) || always_email_private_message?(user, type) || user.staged) + ( + always_email_regular?(user, type) || always_email_private_message?(user, type) || + user.staged + ) seen_recently = false end email_args = {} if (post || notification || notification_type || args[:force_respect_seen_recently]) && - (seen_recently && !user.suspended?) - + (seen_recently && !user.suspended?) return skip_message(SkippedEmailLog.reason_types[:user_email_seen_recently]) end email_args[:post] = post if post if notification || notification_type - email_args[:notification_type] ||= notification_type || notification.try(:notification_type) - email_args[:notification_data_hash] ||= notification_data_hash || notification.try(:data_hash) + email_args[:notification_type] ||= notification_type || notification.try(:notification_type) + email_args[:notification_data_hash] ||= notification_data_hash || + notification.try(:data_hash) unless String === email_args[:notification_type] if Numeric === email_args[:notification_type] @@ -157,13 +158,12 @@ module Jobs email_args[:notification_type] = email_args[:notification_type].to_s end - if !SiteSetting.disable_mailing_list_mode && - user.user_option.mailing_list_mode? && - user.user_option.mailing_list_mode_frequency > 0 && # don't catch notifications for users on daily mailing list mode - (!post.try(:topic).try(:private_message?)) && - NOTIFICATIONS_SENT_BY_MAILING_LIST.include?(email_args[:notification_type]) + if !SiteSetting.disable_mailing_list_mode && user.user_option.mailing_list_mode? && + user.user_option.mailing_list_mode_frequency > 0 && # don't catch notifications for users on daily mailing list mode + (!post.try(:topic).try(:private_message?)) && + NOTIFICATIONS_SENT_BY_MAILING_LIST.include?(email_args[:notification_type]) # no need to log a reason when the mail was already sent via the mailing list job - return [nil, nil] + return nil, nil end unless always_email_regular?(user, type) || always_email_private_message?(user, type) @@ -177,7 +177,9 @@ module Jobs return skip_message(skip_reason_type) if skip_reason_type.present? # Make sure that mailer exists - raise Discourse::InvalidParameters.new("type=#{type}") unless UserNotifications.respond_to?(type) + unless UserNotifications.respond_to?(type) + raise Discourse::InvalidParameters.new("type=#{type}") + end if email_token.present? email_args[:email_token] = email_token @@ -185,13 +187,12 @@ module Jobs if type.to_s == "confirm_new_email" change_req = EmailChangeRequest.find_by_new_token(email_token) - if change_req - email_args[:requested_by_admin] = change_req.requested_by_admin? - end + email_args[:requested_by_admin] = change_req.requested_by_admin? if change_req end end - email_args[:new_email] = args[:new_email] || user.email if type.to_s == "notify_old_email" || type.to_s == "notify_old_email_add" + email_args[:new_email] = args[:new_email] || user.email if type.to_s == "notify_old_email" || + type.to_s == "notify_old_email_add" if args[:client_ip] && args[:user_agent] email_args[:client_ip] = args[:client_ip] @@ -202,7 +203,8 @@ module Jobs return skip_message(SkippedEmailLog.reason_types[:exceeded_emails_limit]) end - if !EmailLog::CRITICAL_EMAIL_TYPES.include?(type.to_s) && user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold + if !EmailLog::CRITICAL_EMAIL_TYPES.include?(type.to_s) && + user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold return skip_message(SkippedEmailLog.reason_types[:exceeded_bounces_limit]) end @@ -212,9 +214,10 @@ module Jobs email_args[:reject_reason] = args[:reject_reason] - message = EmailLog.unique_email_per_post(post, user) do - UserNotifications.public_send(type, user, email_args) - end + message = + EmailLog.unique_email_per_post(post, user) do + UserNotifications.public_send(type, user, email_args) + end # Update the to address if we have a custom one message.to = to_address if message && to_address.present? @@ -232,26 +235,24 @@ module Jobs def skip_email_for_post(post, user) return false unless post - if post.topic.blank? - return SkippedEmailLog.reason_types[:user_email_topic_nil] - end + return SkippedEmailLog.reason_types[:user_email_topic_nil] if post.topic.blank? - if post.user.blank? - return SkippedEmailLog.reason_types[:user_email_post_user_deleted] - end + return SkippedEmailLog.reason_types[:user_email_post_user_deleted] if post.user.blank? - if post.user_deleted? - return SkippedEmailLog.reason_types[:user_email_post_deleted] - end + return SkippedEmailLog.reason_types[:user_email_post_deleted] if post.user_deleted? if user.suspended? && (!post.user&.staff? || !post.user&.human?) return SkippedEmailLog.reason_types[:user_email_user_suspended] end - already_read = user.user_option.email_level != UserOption.email_level_types[:always] && PostTiming.exists?(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id) - if already_read - SkippedEmailLog.reason_types[:user_email_already_read] - end + already_read = + user.user_option.email_level != UserOption.email_level_types[:always] && + PostTiming.exists?( + topic_id: post.topic_id, + post_number: post.post_number, + user_id: user.id, + ) + SkippedEmailLog.reason_types[:user_email_already_read] if already_read end def skip(reason_type) @@ -260,17 +261,18 @@ module Jobs to_address: @skip_context[:to_address], user_id: @skip_context[:user_id], post_id: @skip_context[:post_id], - reason_type: reason_type + reason_type: reason_type, ) end def always_email_private_message?(user, type) - type.to_s == "user_private_message" && user.user_option.email_messages_level == UserOption.email_level_types[:always] + type.to_s == "user_private_message" && + user.user_option.email_messages_level == UserOption.email_level_types[:always] end def always_email_regular?(user, type) - type.to_s != "user_private_message" && user.user_option.email_level == UserOption.email_level_types[:always] + type.to_s != "user_private_message" && + user.user_option.email_level == UserOption.email_level_types[:always] end end - end diff --git a/app/jobs/scheduled/activation_reminder_emails.rb b/app/jobs/scheduled/activation_reminder_emails.rb index 5bfbf9e9f3a..f3f4470ad90 100644 --- a/app/jobs/scheduled/activation_reminder_emails.rb +++ b/app/jobs/scheduled/activation_reminder_emails.rb @@ -5,22 +5,25 @@ module Jobs every 2.hours def execute(args) - User.joins("LEFT JOIN user_custom_fields ON users.id = user_id AND user_custom_fields.name = 'activation_reminder'") - .where(active: false, staged: false, user_custom_fields: { value: nil }) - .where('users.created_at BETWEEN ? AND ?', 3.days.ago, 2.days.ago) - .find_each do |user| - - user.custom_fields['activation_reminder'] = true - user.save_custom_fields - - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) - ::Jobs.enqueue( - :user_email, - type: "activation_reminder", - user_id: user.id, - email_token: email_token.token + User + .joins( + "LEFT JOIN user_custom_fields ON users.id = user_id AND user_custom_fields.name = 'activation_reminder'", ) - end + .where(active: false, staged: false, user_custom_fields: { value: nil }) + .where("users.created_at BETWEEN ? AND ?", 3.days.ago, 2.days.ago) + .find_each do |user| + user.custom_fields["activation_reminder"] = true + user.save_custom_fields + + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:signup]) + ::Jobs.enqueue( + :user_email, + type: "activation_reminder", + user_id: user.id, + email_token: email_token.token, + ) + end end end end diff --git a/app/jobs/scheduled/auto_expire_user_api_keys.rb b/app/jobs/scheduled/auto_expire_user_api_keys.rb index 4a4e041f7ce..60bd24418d2 100644 --- a/app/jobs/scheduled/auto_expire_user_api_keys.rb +++ b/app/jobs/scheduled/auto_expire_user_api_keys.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class AutoExpireUserApiKeys < ::Jobs::Scheduled every 1.day @@ -9,9 +8,10 @@ module Jobs if SiteSetting.expire_user_api_keys_days > 0 expire_user_api_keys_days = SiteSetting.expire_user_api_keys_days.days.ago - UserApiKey.where("last_used_at < ?", expire_user_api_keys_days).update_all(revoked_at: Time.zone.now) + UserApiKey.where("last_used_at < ?", expire_user_api_keys_days).update_all( + revoked_at: Time.zone.now, + ) end end end - end diff --git a/app/jobs/scheduled/auto_queue_handler.rb b/app/jobs/scheduled/auto_queue_handler.rb index 6bf75924c96..66150fb8c97 100644 --- a/app/jobs/scheduled/auto_queue_handler.rb +++ b/app/jobs/scheduled/auto_queue_handler.rb @@ -4,7 +4,6 @@ # queue for a long time. module Jobs class AutoQueueHandler < ::Jobs::Scheduled - every 1.day def execute(args) @@ -12,17 +11,16 @@ module Jobs Reviewable .pending - .where('created_at < ?', SiteSetting.auto_handle_queued_age.to_i.days.ago) + .where("created_at < ?", SiteSetting.auto_handle_queued_age.to_i.days.ago) .each do |reviewable| - - if reviewable.is_a?(ReviewableFlaggedPost) - reviewable.perform(Discourse.system_user, :ignore, expired: true) - elsif reviewable.is_a?(ReviewableQueuedPost) - reviewable.perform(Discourse.system_user, :reject_post) - elsif reviewable.is_a?(ReviewableUser) - reviewable.perform(Discourse.system_user, :delete_user) + if reviewable.is_a?(ReviewableFlaggedPost) + reviewable.perform(Discourse.system_user, :ignore, expired: true) + elsif reviewable.is_a?(ReviewableQueuedPost) + reviewable.perform(Discourse.system_user, :reject_post) + elsif reviewable.is_a?(ReviewableUser) + reviewable.perform(Discourse.system_user, :delete_user) + end end - end end end end diff --git a/app/jobs/scheduled/badge_grant.rb b/app/jobs/scheduled/badge_grant.rb index e9ad67a9b1d..b9bc1ad4e82 100644 --- a/app/jobs/scheduled/badge_grant.rb +++ b/app/jobs/scheduled/badge_grant.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class BadgeGrant < ::Jobs::Scheduled def self.run self.new.execute(nil) @@ -17,7 +16,10 @@ module Jobs BadgeGranter.backfill(b) rescue => ex # TODO - expose errors in UI - Discourse.handle_job_exception(ex, error_context({}, code_desc: 'Exception granting badges', extra: { badge_id: b.id })) + Discourse.handle_job_exception( + ex, + error_context({}, code_desc: "Exception granting badges", extra: { badge_id: b.id }), + ) end end @@ -25,7 +27,5 @@ module Jobs UserBadge.ensure_consistency! # Badge granter sometimes uses raw SQL, so hooks do not run. Clean up data UserStat.update_distinct_badge_count end - end - end diff --git a/app/jobs/scheduled/bookmark_reminder_notifications.rb b/app/jobs/scheduled/bookmark_reminder_notifications.rb index 712a650fc02..5443b247dad 100644 --- a/app/jobs/scheduled/bookmark_reminder_notifications.rb +++ b/app/jobs/scheduled/bookmark_reminder_notifications.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - # Runs periodically to send out bookmark reminders, capped at 300 at a time. # Any leftovers will be caught in the next run, because the reminder_at column # is set to NULL once a reminder has been sent. @@ -18,10 +17,10 @@ module Jobs end def execute(args = nil) - bookmarks = Bookmark.pending_reminders.includes(:user).order('reminder_at ASC') - bookmarks.limit(BookmarkReminderNotifications.max_reminder_notifications_per_run).each do |bookmark| - BookmarkReminderNotificationHandler.new(bookmark).send_notification - end + bookmarks = Bookmark.pending_reminders.includes(:user).order("reminder_at ASC") + bookmarks + .limit(BookmarkReminderNotifications.max_reminder_notifications_per_run) + .each { |bookmark| BookmarkReminderNotificationHandler.new(bookmark).send_notification } end end end diff --git a/app/jobs/scheduled/category_stats.rb b/app/jobs/scheduled/category_stats.rb index b1de99224e2..81ff69031fd 100644 --- a/app/jobs/scheduled/category_stats.rb +++ b/app/jobs/scheduled/category_stats.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true module Jobs - class CategoryStats < ::Jobs::Scheduled every 24.hours def execute(args) Category.update_stats end - end - end diff --git a/app/jobs/scheduled/check_new_features.rb b/app/jobs/scheduled/check_new_features.rb index c3f98bf4885..b69410f3516 100644 --- a/app/jobs/scheduled/check_new_features.rb +++ b/app/jobs/scheduled/check_new_features.rb @@ -11,10 +11,7 @@ module Jobs if prev_most_recent admin_ids.each do |admin_id| if DiscourseUpdates.get_last_viewed_feature_date(admin_id).blank? - DiscourseUpdates.bump_last_viewed_feature_date( - admin_id, - prev_most_recent["created_at"] - ) + DiscourseUpdates.bump_last_viewed_feature_date(admin_id, prev_most_recent["created_at"]) end end end @@ -32,16 +29,15 @@ module Jobs most_recent_feature_date = Time.zone.parse(new_most_recent["created_at"]) admin_ids.each do |admin_id| admin_last_viewed_feature_date = DiscourseUpdates.get_last_viewed_feature_date(admin_id) - if admin_last_viewed_feature_date.blank? || admin_last_viewed_feature_date < most_recent_feature_date + if admin_last_viewed_feature_date.blank? || + admin_last_viewed_feature_date < most_recent_feature_date Notification.consolidate_or_create!( user_id: admin_id, notification_type: Notification.types[:new_features], - data: {} - ) - DiscourseUpdates.bump_last_viewed_feature_date( - admin_id, - new_most_recent["created_at"] + data: { + }, ) + DiscourseUpdates.bump_last_viewed_feature_date(admin_id, new_most_recent["created_at"]) end end end diff --git a/app/jobs/scheduled/check_out_of_date_themes.rb b/app/jobs/scheduled/check_out_of_date_themes.rb index 1003e25a906..35592292c7b 100644 --- a/app/jobs/scheduled/check_out_of_date_themes.rb +++ b/app/jobs/scheduled/check_out_of_date_themes.rb @@ -5,9 +5,10 @@ module Jobs every 1.day def execute(args) - target_themes = RemoteTheme - .joins("JOIN themes ON themes.remote_theme_id = remote_themes.id") - .where.not(remote_url: "") + target_themes = + RemoteTheme + .joins("JOIN themes ON themes.remote_theme_id = remote_themes.id") + .where.not(remote_url: "") target_themes.each do |remote| remote.update_remote_version diff --git a/app/jobs/scheduled/clean_dismissed_topic_users.rb b/app/jobs/scheduled/clean_dismissed_topic_users.rb index 72109ffbfcc..7b21fb54ea4 100644 --- a/app/jobs/scheduled/clean_dismissed_topic_users.rb +++ b/app/jobs/scheduled/clean_dismissed_topic_users.rb @@ -27,12 +27,15 @@ module Jobs END, users.created_at, :min_date) AND dtu1.id = dtu2.id SQL - sql = DB.sql_fragment(sql, - now: DateTime.now, - last_visit: User::NewTopicDuration::LAST_VISIT, - always: User::NewTopicDuration::ALWAYS, - default_duration: SiteSetting.default_other_new_topic_duration_minutes, - min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime) + sql = + DB.sql_fragment( + sql, + now: DateTime.now, + last_visit: User::NewTopicDuration::LAST_VISIT, + always: User::NewTopicDuration::ALWAYS, + default_duration: SiteSetting.default_other_new_topic_duration_minutes, + min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime, + ) DB.exec(sql) end diff --git a/app/jobs/scheduled/clean_up_associated_accounts.rb b/app/jobs/scheduled/clean_up_associated_accounts.rb index ac2ba4802b5..861b0bca1f0 100644 --- a/app/jobs/scheduled/clean_up_associated_accounts.rb +++ b/app/jobs/scheduled/clean_up_associated_accounts.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true module Jobs - class CleanUpAssociatedAccounts < ::Jobs::Scheduled every 1.day def execute(args) UserAssociatedAccount.cleanup! end - end - end diff --git a/app/jobs/scheduled/clean_up_crawler_stats.rb b/app/jobs/scheduled/clean_up_crawler_stats.rb index 09f704bd34d..e333082b174 100644 --- a/app/jobs/scheduled/clean_up_crawler_stats.rb +++ b/app/jobs/scheduled/clean_up_crawler_stats.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true module Jobs - class CleanUpCrawlerStats < ::Jobs::Scheduled every 1.day def execute(args) - WebCrawlerRequest.where('date < ?', WebCrawlerRequest.max_record_age.ago).delete_all + WebCrawlerRequest.where("date < ?", WebCrawlerRequest.max_record_age.ago).delete_all # keep count of only the top user agents DB.exec <<~SQL @@ -24,5 +23,4 @@ module Jobs SQL end end - end diff --git a/app/jobs/scheduled/clean_up_email_change_requests.rb b/app/jobs/scheduled/clean_up_email_change_requests.rb index cd572b5e45c..d2e1926a991 100644 --- a/app/jobs/scheduled/clean_up_email_change_requests.rb +++ b/app/jobs/scheduled/clean_up_email_change_requests.rb @@ -5,7 +5,7 @@ module Jobs every 1.day def execute(args) - EmailChangeRequest.where('updated_at < ?', 1.month.ago).delete_all + EmailChangeRequest.where("updated_at < ?", 1.month.ago).delete_all end end end diff --git a/app/jobs/scheduled/clean_up_email_logs.rb b/app/jobs/scheduled/clean_up_email_logs.rb index 9799764e711..9ddc1f0125a 100644 --- a/app/jobs/scheduled/clean_up_email_logs.rb +++ b/app/jobs/scheduled/clean_up_email_logs.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class CleanUpEmailLogs < ::Jobs::Scheduled every 1.day @@ -13,7 +12,5 @@ module Jobs EmailLog.where("created_at < ?", threshold).delete_all SkippedEmailLog.where("created_at < ?", threshold).delete_all end - end - end diff --git a/app/jobs/scheduled/clean_up_email_tokens.rb b/app/jobs/scheduled/clean_up_email_tokens.rb index 0781115fd89..daff838eb8b 100644 --- a/app/jobs/scheduled/clean_up_email_tokens.rb +++ b/app/jobs/scheduled/clean_up_email_tokens.rb @@ -5,10 +5,7 @@ module Jobs every 1.day def execute(args) - EmailToken - .where('NOT confirmed AND expired') - .where('created_at < ?', 1.month.ago) - .delete_all + EmailToken.where("NOT confirmed AND expired").where("created_at < ?", 1.month.ago).delete_all end end end diff --git a/app/jobs/scheduled/clean_up_inactive_users.rb b/app/jobs/scheduled/clean_up_inactive_users.rb index a54e1ee6579..b52ae8d5e21 100644 --- a/app/jobs/scheduled/clean_up_inactive_users.rb +++ b/app/jobs/scheduled/clean_up_inactive_users.rb @@ -1,30 +1,32 @@ # frozen_string_literal: true module Jobs - class CleanUpInactiveUsers < ::Jobs::Scheduled every 1.day def execute(args) return if SiteSetting.clean_up_inactive_users_after_days <= 0 - User.joins("LEFT JOIN posts ON posts.user_id = users.id") - .where(last_posted_at: nil, trust_level: TrustLevel.levels[:newuser], admin: false, moderator: false) + User + .joins("LEFT JOIN posts ON posts.user_id = users.id") + .where( + last_posted_at: nil, + trust_level: TrustLevel.levels[:newuser], + admin: false, + moderator: false, + ) .where( "posts.user_id IS NULL AND users.last_seen_at < ?", - SiteSetting.clean_up_inactive_users_after_days.days.ago - ) + SiteSetting.clean_up_inactive_users_after_days.days.ago, + ) .limit(1000) - .pluck(:id).each_slice(50) do |slice| - destroy(slice) - end - + .pluck(:id) + .each_slice(50) { |slice| destroy(slice) } end private def destroy(ids) - destroyer = UserDestroyer.new(Discourse.system_user) User.transaction do @@ -32,11 +34,18 @@ module Jobs begin user = User.find_by(id: id) next unless user - destroyer.destroy(user, transaction: false, context: I18n.t("user.destroy_reasons.inactive_user")) + destroyer.destroy( + user, + transaction: false, + context: I18n.t("user.destroy_reasons.inactive_user"), + ) rescue => e - Discourse.handle_job_exception(e, - message: "Cleaning up inactive users", - extra: { user_id: id } + Discourse.handle_job_exception( + e, + message: "Cleaning up inactive users", + extra: { + user_id: id, + }, ) raise e end diff --git a/app/jobs/scheduled/clean_up_post_reply_keys.rb b/app/jobs/scheduled/clean_up_post_reply_keys.rb index 748da603f34..a3088101c8f 100644 --- a/app/jobs/scheduled/clean_up_post_reply_keys.rb +++ b/app/jobs/scheduled/clean_up_post_reply_keys.rb @@ -9,7 +9,7 @@ module Jobs PostReplyKey.where( "created_at < ?", - SiteSetting.disallow_reply_by_email_after_days.days.ago + SiteSetting.disallow_reply_by_email_after_days.days.ago, ).delete_all end end diff --git a/app/jobs/scheduled/clean_up_unmatched_emails.rb b/app/jobs/scheduled/clean_up_unmatched_emails.rb index 58fb112e17b..abcb85f172a 100644 --- a/app/jobs/scheduled/clean_up_unmatched_emails.rb +++ b/app/jobs/scheduled/clean_up_unmatched_emails.rb @@ -1,18 +1,16 @@ # frozen_string_literal: true module Jobs - class CleanUpUnmatchedEmails < ::Jobs::Scheduled every 1.day def execute(args) last_match_threshold = SiteSetting.max_age_unmatched_emails.days.ago - ScreenedEmail.where(action_type: ScreenedEmail.actions[:block]) + ScreenedEmail + .where(action_type: ScreenedEmail.actions[:block]) .where("last_match_at < ?", last_match_threshold) .destroy_all end - end - end diff --git a/app/jobs/scheduled/clean_up_unmatched_ips.rb b/app/jobs/scheduled/clean_up_unmatched_ips.rb index fa16315f2de..fa1c097eef1 100644 --- a/app/jobs/scheduled/clean_up_unmatched_ips.rb +++ b/app/jobs/scheduled/clean_up_unmatched_ips.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class CleanUpUnmatchedIPs < ::Jobs::Scheduled every 1.day @@ -12,11 +11,14 @@ module Jobs last_match_threshold = SiteSetting.max_age_unmatched_ips.days.ago # remove old unmatched IP addresses - ScreenedIpAddress.where(action_type: ScreenedIpAddress.actions[:block]) - .where("last_match_at < ? OR (last_match_at IS NULL AND created_at < ?)", last_match_threshold, last_match_threshold) + ScreenedIpAddress + .where(action_type: ScreenedIpAddress.actions[:block]) + .where( + "last_match_at < ? OR (last_match_at IS NULL AND created_at < ?)", + last_match_threshold, + last_match_threshold, + ) .destroy_all end - end - end diff --git a/app/jobs/scheduled/clean_up_unsubscribe_keys.rb b/app/jobs/scheduled/clean_up_unsubscribe_keys.rb index ed8dfb55667..91d22aaf6bf 100644 --- a/app/jobs/scheduled/clean_up_unsubscribe_keys.rb +++ b/app/jobs/scheduled/clean_up_unsubscribe_keys.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true module Jobs - class CleanUpUnsubscribeKeys < ::Jobs::Scheduled every 1.day def execute(args) - UnsubscribeKey.where('created_at < ?', 2.months.ago).delete_all + UnsubscribeKey.where("created_at < ?", 2.months.ago).delete_all end - end - end diff --git a/app/jobs/scheduled/clean_up_unused_api_keys.rb b/app/jobs/scheduled/clean_up_unused_api_keys.rb index 4b9e756e631..81266c7050a 100644 --- a/app/jobs/scheduled/clean_up_unused_api_keys.rb +++ b/app/jobs/scheduled/clean_up_unused_api_keys.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true module Jobs - class CleanUpUnusedApiKeys < ::Jobs::Scheduled every 1.day def execute(args) ApiKey.revoke_unused_keys! end - end - end diff --git a/app/jobs/scheduled/clean_up_unused_staged_users.rb b/app/jobs/scheduled/clean_up_unused_staged_users.rb index be2cd173132..301de4efeaf 100644 --- a/app/jobs/scheduled/clean_up_unused_staged_users.rb +++ b/app/jobs/scheduled/clean_up_unused_staged_users.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class CleanUpUnusedStagedUsers < ::Jobs::Scheduled every 1.day @@ -11,23 +10,24 @@ module Jobs destroyer = UserDestroyer.new(Discourse.system_user) - User.joins("LEFT JOIN posts ON posts.user_id = users.id") + User + .joins("LEFT JOIN posts ON posts.user_id = users.id") .where("posts.user_id IS NULL") .where(staged: true, admin: false, moderator: false) .where("users.created_at < ?", clean_up_after_days.days.ago) .find_each do |user| - - begin - destroyer.destroy(user, context: I18n.t("user.destroy_reasons.unused_staged_user")) - rescue => e - Discourse.handle_job_exception(e, - message: "Cleaning up unused staged user", - extra: { user_id: user.id } - ) + begin + destroyer.destroy(user, context: I18n.t("user.destroy_reasons.unused_staged_user")) + rescue => e + Discourse.handle_job_exception( + e, + message: "Cleaning up unused staged user", + extra: { + user_id: user.id, + }, + ) + end end - end end - end - end diff --git a/app/jobs/scheduled/clean_up_uploads.rb b/app/jobs/scheduled/clean_up_uploads.rb index ecaa5d3876e..4f8c89c11eb 100644 --- a/app/jobs/scheduled/clean_up_uploads.rb +++ b/app/jobs/scheduled/clean_up_uploads.rb @@ -10,7 +10,9 @@ module Jobs # always remove invalid upload records Upload .by_users - .where("retain_hours IS NULL OR created_at < current_timestamp - interval '1 hour' * retain_hours") + .where( + "retain_hours IS NULL OR created_at < current_timestamp - interval '1 hour' * retain_hours", + ) .where("created_at < ?", grace_period.hour.ago) .where(url: "") .find_each(&:destroy!) @@ -21,19 +23,29 @@ module Jobs return if (Time.zone.now.to_i - c) < (grace_period / 2).hours end - base_url = Discourse.store.internal? ? Discourse.store.relative_base_url : Discourse.store.absolute_base_url + base_url = + ( + if Discourse.store.internal? + Discourse.store.relative_base_url + else + Discourse.store.absolute_base_url + end + ) s3_hostname = URI.parse(base_url).hostname s3_cdn_hostname = URI.parse(SiteSetting.Upload.s3_cdn_url || "").hostname result = Upload.by_users Upload.unused_callbacks&.each { |handler| result = handler.call(result) } - result = result - .where("uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours") - .where("uploads.created_at < ?", grace_period.hour.ago) - .where("uploads.access_control_post_id IS NULL") - .joins("LEFT JOIN upload_references ON upload_references.upload_id = uploads.id") - .where("upload_references.upload_id IS NULL") - .with_no_non_post_relations + result = + result + .where( + "uploads.retain_hours IS NULL OR uploads.created_at < current_timestamp - interval '1 hour' * uploads.retain_hours", + ) + .where("uploads.created_at < ?", grace_period.hour.ago) + .where("uploads.access_control_post_id IS NULL") + .joins("LEFT JOIN upload_references ON upload_references.upload_id = uploads.id") + .where("upload_references.upload_id IS NULL") + .with_no_non_post_relations result.find_each do |upload| next if Upload.in_use_callbacks&.any? { |callback| callback.call(upload) } @@ -41,9 +53,30 @@ module Jobs if upload.sha1.present? # TODO: Remove this check after UploadReferences records were created encoded_sha = Base62.encode(upload.sha1.hex) - next if ReviewableQueuedPost.pending.where("payload->>'raw' LIKE ? OR payload->>'raw' LIKE ?", "%#{upload.sha1}%", "%#{encoded_sha}%").exists? - next if Draft.where("data LIKE ? OR data LIKE ?", "%#{upload.sha1}%", "%#{encoded_sha}%").exists? - next if UserProfile.where("bio_raw LIKE ? OR bio_raw LIKE ?", "%#{upload.sha1}%", "%#{encoded_sha}%").exists? + if ReviewableQueuedPost + .pending + .where( + "payload->>'raw' LIKE ? OR payload->>'raw' LIKE ?", + "%#{upload.sha1}%", + "%#{encoded_sha}%", + ) + .exists? + next + end + if Draft.where( + "data LIKE ? OR data LIKE ?", + "%#{upload.sha1}%", + "%#{encoded_sha}%", + ).exists? + next + end + if UserProfile.where( + "bio_raw LIKE ? OR bio_raw LIKE ?", + "%#{upload.sha1}%", + "%#{encoded_sha}%", + ).exists? + next + end upload.destroy else @@ -74,6 +107,5 @@ module Jobs def last_cleanup_key "LAST_UPLOAD_CLEANUP" end - end end diff --git a/app/jobs/scheduled/create_missing_avatars.rb b/app/jobs/scheduled/create_missing_avatars.rb index 0847b84f2e8..2043bafe0b3 100644 --- a/app/jobs/scheduled/create_missing_avatars.rb +++ b/app/jobs/scheduled/create_missing_avatars.rb @@ -6,14 +6,13 @@ module Jobs def execute(args) # backfill in batches of 5000 an hour - UserAvatar.includes(:user) + UserAvatar + .includes(:user) .joins(:user) .where(last_gravatar_download_attempt: nil) .order("users.last_posted_at DESC") .limit(5000) - .each do |u| - u.user.refresh_avatar - end + .each { |u| u.user.refresh_avatar } end end end diff --git a/app/jobs/scheduled/create_recent_post_search_indexes.rb b/app/jobs/scheduled/create_recent_post_search_indexes.rb index 4f0ab41cd9e..95d181024a5 100644 --- a/app/jobs/scheduled/create_recent_post_search_indexes.rb +++ b/app/jobs/scheduled/create_recent_post_search_indexes.rb @@ -4,7 +4,7 @@ module Jobs class CreateRecentPostSearchIndexes < ::Jobs::Scheduled every 1.day - REGULAR_POST_SEARCH_DATA_INDEX_NAME = 'idx_recent_regular_post_search_data' + REGULAR_POST_SEARCH_DATA_INDEX_NAME = "idx_recent_regular_post_search_data" def execute(_) create_recent_regular_post_search_index @@ -13,7 +13,11 @@ module Jobs private def create_recent_regular_post_search_index - if !PostSearchData.where(private_message: false).offset(SiteSetting.search_enable_recent_regular_posts_offset_size - 1).limit(1).exists? + if !PostSearchData + .where(private_message: false) + .offset(SiteSetting.search_enable_recent_regular_posts_offset_size - 1) + .limit(1) + .exists? return end @@ -24,22 +28,22 @@ module Jobs SQL DB.exec(<<~SQL, post_id: SiteSetting.search_recent_regular_posts_offset_post_id) - CREATE INDEX #{Rails.env.test? ? '' : 'CONCURRENTLY'} temp_idx_recent_regular_post_search_data + CREATE INDEX #{Rails.env.test? ? "" : "CONCURRENTLY"} temp_idx_recent_regular_post_search_data ON post_search_data USING GIN(search_data) WHERE NOT private_message AND post_id >= :post_id SQL DB.exec(<<~SQL) - #{Rails.env.test? ? '' : "BEGIN;"} + #{Rails.env.test? ? "" : "BEGIN;"} DROP INDEX IF EXISTS #{REGULAR_POST_SEARCH_DATA_INDEX_NAME}; ALTER INDEX temp_idx_recent_regular_post_search_data RENAME TO #{REGULAR_POST_SEARCH_DATA_INDEX_NAME}; - #{Rails.env.test? ? '' : "COMMIT;"} + #{Rails.env.test? ? "" : "COMMIT;"} SQL end def regular_offset_post_id PostSearchData - .order('post_id DESC') + .order("post_id DESC") .where(private_message: false) .offset(SiteSetting.search_recent_posts_size - 1) .limit(1) diff --git a/app/jobs/scheduled/dashboard_stats.rb b/app/jobs/scheduled/dashboard_stats.rb index 2c1bd74fa32..a81d0b5d7b3 100644 --- a/app/jobs/scheduled/dashboard_stats.rb +++ b/app/jobs/scheduled/dashboard_stats.rb @@ -8,7 +8,8 @@ module Jobs if persistent_problems? # If there have been problems reported on the dashboard for a while, # send a message to admins no more often than once per week. - group_message = GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) + group_message = + GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) Topic.transaction do group_message.delete_previous! group_message.create diff --git a/app/jobs/scheduled/destroy_old_hidden_posts.rb b/app/jobs/scheduled/destroy_old_hidden_posts.rb index fa282fcbdb1..fc2ed504946 100644 --- a/app/jobs/scheduled/destroy_old_hidden_posts.rb +++ b/app/jobs/scheduled/destroy_old_hidden_posts.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class DestroyOldHiddenPosts < ::Jobs::Scheduled every 1.day @@ -9,7 +8,5 @@ module Jobs return unless SiteSetting.delete_old_hidden_posts PostDestroyer.destroy_old_hidden_posts end - end - end diff --git a/app/jobs/scheduled/disable_bootstrap_mode.rb b/app/jobs/scheduled/disable_bootstrap_mode.rb index 61650b3094c..e9d1db42881 100644 --- a/app/jobs/scheduled/disable_bootstrap_mode.rb +++ b/app/jobs/scheduled/disable_bootstrap_mode.rb @@ -8,16 +8,17 @@ module Jobs return unless SiteSetting.bootstrap_mode_enabled total_users = User.human_users.count - if SiteSetting.bootstrap_mode_min_users == 0 || total_users > SiteSetting.bootstrap_mode_min_users + if SiteSetting.bootstrap_mode_min_users == 0 || + total_users > SiteSetting.bootstrap_mode_min_users if SiteSetting.default_trust_level == TrustLevel[1] - SiteSetting.set_and_log('default_trust_level', TrustLevel[0]) + SiteSetting.set_and_log("default_trust_level", TrustLevel[0]) end if SiteSetting.default_email_digest_frequency == 1440 - SiteSetting.set_and_log('default_email_digest_frequency', 10080) + SiteSetting.set_and_log("default_email_digest_frequency", 10_080) end - SiteSetting.set_and_log('bootstrap_mode_enabled', false) + SiteSetting.set_and_log("bootstrap_mode_enabled", false) end end end diff --git a/app/jobs/scheduled/enqueue_digest_emails.rb b/app/jobs/scheduled/enqueue_digest_emails.rb index 7d9ad9fa16b..f700c31025f 100644 --- a/app/jobs/scheduled/enqueue_digest_emails.rb +++ b/app/jobs/scheduled/enqueue_digest_emails.rb @@ -1,35 +1,45 @@ # frozen_string_literal: true module Jobs - class EnqueueDigestEmails < ::Jobs::Scheduled every 30.minutes def execute(args) - return if SiteSetting.disable_digest_emails? || SiteSetting.private_email? || SiteSetting.disable_emails == 'yes' + if SiteSetting.disable_digest_emails? || SiteSetting.private_email? || + SiteSetting.disable_emails == "yes" + return + end users = target_user_ids - users.each do |user_id| - ::Jobs.enqueue(:user_email, type: "digest", user_id: user_id) - end + users.each { |user_id| ::Jobs.enqueue(:user_email, type: "digest", user_id: user_id) } end def target_user_ids # Users who want to receive digest email within their chosen digest email frequency - query = User - .real - .activated - .not_suspended - .where(staged: false) - .joins(:user_option, :user_stat, :user_emails) - .where("user_options.email_digests") - .where("user_stats.bounce_score < ?", SiteSetting.bounce_score_threshold) - .where("user_emails.primary") - .where("COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") - .where("COALESCE(user_stats.digest_attempted_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") - .where("COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)") - .where("COALESCE(last_seen_at, '2010-01-01') >= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * ?)", SiteSetting.suppress_digest_email_after_days) - .order("user_stats.digest_attempted_at ASC NULLS FIRST") + query = + User + .real + .activated + .not_suspended + .where(staged: false) + .joins(:user_option, :user_stat, :user_emails) + .where("user_options.email_digests") + .where("user_stats.bounce_score < ?", SiteSetting.bounce_score_threshold) + .where("user_emails.primary") + .where( + "COALESCE(last_emailed_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)", + ) + .where( + "COALESCE(user_stats.digest_attempted_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)", + ) + .where( + "COALESCE(last_seen_at, '2010-01-01') <= CURRENT_TIMESTAMP - ('1 MINUTE'::INTERVAL * user_options.digest_after_minutes)", + ) + .where( + "COALESCE(last_seen_at, '2010-01-01') >= CURRENT_TIMESTAMP - ('1 DAY'::INTERVAL * ?)", + SiteSetting.suppress_digest_email_after_days, + ) + .order("user_stats.digest_attempted_at ASC NULLS FIRST") # If the site requires approval, make sure the user is approved query = query.where("approved OR moderator OR admin") if SiteSetting.must_approve_users? @@ -38,7 +48,5 @@ module Jobs query.pluck(:id) end - end - end diff --git a/app/jobs/scheduled/enqueue_onceoffs.rb b/app/jobs/scheduled/enqueue_onceoffs.rb index 29692c63112..e0b3a4cedf0 100644 --- a/app/jobs/scheduled/enqueue_onceoffs.rb +++ b/app/jobs/scheduled/enqueue_onceoffs.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class EnqueueOnceoffs < ::Jobs::Scheduled every 10.minutes @@ -9,5 +8,4 @@ module Jobs ::Jobs::Onceoff.enqueue_all end end - end diff --git a/app/jobs/scheduled/enqueue_suspect_users.rb b/app/jobs/scheduled/enqueue_suspect_users.rb index d6f568335c7..765728e3f0f 100644 --- a/app/jobs/scheduled/enqueue_suspect_users.rb +++ b/app/jobs/scheduled/enqueue_suspect_users.rb @@ -8,51 +8,56 @@ module Jobs return unless SiteSetting.approve_suspect_users return if SiteSetting.must_approve_users - users = User - .distinct - .activated - .human_users - .where(approved: false) - .joins(:user_profile, :user_stat) - .where("users.created_at <= ? AND users.created_at >= ?", 1.day.ago, 6.months.ago) - .where("LENGTH(COALESCE(user_profiles.bio_raw, user_profiles.website, '')) > 0") - .where("user_stats.posts_read_count <= 1 OR user_stats.topics_entered <= 1 OR user_stats.time_read < ?", 1.minute.to_i) - .joins("LEFT OUTER JOIN reviewables r ON r.target_id = users.id AND r.target_type = 'User'") - .where('r.id IS NULL') - .joins( - <<~SQL + users = + User + .distinct + .activated + .human_users + .where(approved: false) + .joins(:user_profile, :user_stat) + .where("users.created_at <= ? AND users.created_at >= ?", 1.day.ago, 6.months.ago) + .where("LENGTH(COALESCE(user_profiles.bio_raw, user_profiles.website, '')) > 0") + .where( + "user_stats.posts_read_count <= 1 OR user_stats.topics_entered <= 1 OR user_stats.time_read < ?", + 1.minute.to_i, + ) + .joins( + "LEFT OUTER JOIN reviewables r ON r.target_id = users.id AND r.target_type = 'User'", + ) + .where("r.id IS NULL") + .joins(<<~SQL) LEFT OUTER JOIN ( SELECT user_id FROM user_custom_fields WHERE user_custom_fields.name = 'import_id' ) AS ucf ON ucf.user_id = users.id SQL - ) - .where('ucf.user_id IS NULL') - .limit(10) + .where("ucf.user_id IS NULL") + .limit(10) users.each do |user| user_profile = user.user_profile - reviewable = ReviewableUser.needs_review!( - target: user, - created_by: Discourse.system_user, - reviewable_by_moderator: true, - payload: { - username: user.username, - name: user.name, - email: user.email, - bio: user_profile.bio_raw, - website: user_profile.website, - } - ) + reviewable = + ReviewableUser.needs_review!( + target: user, + created_by: Discourse.system_user, + reviewable_by_moderator: true, + payload: { + username: user.username, + name: user.name, + email: user.email, + bio: user_profile.bio_raw, + website: user_profile.website, + }, + ) if reviewable.created_new reviewable.add_score( Discourse.system_user, ReviewableScore.types[:needs_approval], reason: :suspect_user, - force_review: true + force_review: true, ) end end diff --git a/app/jobs/scheduled/ensure_db_consistency.rb b/app/jobs/scheduled/ensure_db_consistency.rb index 6ecb928d079..dd38439845e 100644 --- a/app/jobs/scheduled/ensure_db_consistency.rb +++ b/app/jobs/scheduled/ensure_db_consistency.rb @@ -23,7 +23,7 @@ module Jobs User, UserAvatar, Category, - TopicThumbnail + TopicThumbnail, ].each do |klass| klass.ensure_consistency! measure(klass) @@ -46,9 +46,7 @@ module Jobs def format_measure result = +"EnsureDbConsistency Times\n" - result << @measure_times.map do |name, duration| - " #{name}: #{duration}" - end.join("\n") + result << @measure_times.map { |name, duration| " #{name}: #{duration}" }.join("\n") result end @@ -59,11 +57,8 @@ module Jobs def measure(step = nil) @measure_now = Process.clock_gettime(Process::CLOCK_MONOTONIC) - if @measure_start - @measure_times << [step, @measure_now - @measure_start] - end + @measure_times << [step, @measure_now - @measure_start] if @measure_start @measure_start = @measure_now end - end end diff --git a/app/jobs/scheduled/ensure_s3_uploads_existence.rb b/app/jobs/scheduled/ensure_s3_uploads_existence.rb index 8a8e90643b2..cf71f4253a3 100644 --- a/app/jobs/scheduled/ensure_s3_uploads_existence.rb +++ b/app/jobs/scheduled/ensure_s3_uploads_existence.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class EnsureS3UploadsExistence < ::Jobs::Scheduled every 1.day @@ -11,7 +10,10 @@ module Jobs super ensure if @db_inventories - @db_inventories.values.each { |f| f.close; f.unlink } + @db_inventories.values.each do |f| + f.close + f.unlink + end end end @@ -27,18 +29,20 @@ module Jobs def execute(args) return if !executable? - require 's3_inventory' + require "s3_inventory" if !@db_inventories && Rails.configuration.multisite && GlobalSetting.use_s3? prepare_for_all_sites end - if @db_inventories && preloaded_inventory_file = @db_inventories[RailsMultisite::ConnectionManagement.current_db] + if @db_inventories && + preloaded_inventory_file = + @db_inventories[RailsMultisite::ConnectionManagement.current_db] S3Inventory.new( s3_helper, :upload, preloaded_inventory_file: preloaded_inventory_file, - preloaded_inventory_date: @inventory_date + preloaded_inventory_date: @inventory_date, ).backfill_etags_and_list_missing else S3Inventory.new(s3_helper, :upload).backfill_etags_and_list_missing diff --git a/app/jobs/scheduled/fix_user_usernames_and_groups_names_clash.rb b/app/jobs/scheduled/fix_user_usernames_and_groups_names_clash.rb index d176217d55f..471490be0f9 100644 --- a/app/jobs/scheduled/fix_user_usernames_and_groups_names_clash.rb +++ b/app/jobs/scheduled/fix_user_usernames_and_groups_names_clash.rb @@ -5,27 +5,24 @@ module Jobs every 1.week def execute(args) - User.joins("LEFT JOIN groups ON lower(groups.name) = users.username_lower") + User + .joins("LEFT JOIN groups ON lower(groups.name) = users.username_lower") .where("groups.id IS NOT NULL") .find_each do |user| + suffix = 1 + old_username = user.username - suffix = 1 - old_username = user.username + loop do + user.username = "#{old_username}#{suffix}" + suffix += 1 + break if user.valid? + end - loop do - user.username = "#{old_username}#{suffix}" - suffix += 1 - break if user.valid? + new_username = user.username + user.username = old_username + + UsernameChanger.new(user, new_username).change(asynchronous: false) end - - new_username = user.username - user.username = old_username - - UsernameChanger.new( - user, - new_username - ).change(asynchronous: false) - end end end end diff --git a/app/jobs/scheduled/grant_anniversary_badges.rb b/app/jobs/scheduled/grant_anniversary_badges.rb index a5246c3243f..c244a2e92ee 100644 --- a/app/jobs/scheduled/grant_anniversary_badges.rb +++ b/app/jobs/scheduled/grant_anniversary_badges.rb @@ -35,10 +35,9 @@ module Jobs HAVING COUNT(p.id) > 0 AND COUNT(ub.id) = 0 SQL - User.where(id: user_ids).find_each do |user| - BadgeGranter.grant(badge, user, created_at: end_date) - end + User + .where(id: user_ids) + .find_each { |user| BadgeGranter.grant(badge, user, created_at: end_date) } end - end end diff --git a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb index 3359b934174..ce169794bb0 100644 --- a/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb +++ b/app/jobs/scheduled/grant_new_user_of_the_month_badges.rb @@ -13,9 +13,14 @@ module Jobs previous_month_beginning = 1.month.ago.beginning_of_month previous_month_end = 1.month.ago.end_of_month - return if UserBadge.where("badge_id = ? AND granted_at BETWEEN ? AND ?", - badge.id, previous_month_beginning, Time.zone.now - ).exists? + if UserBadge.where( + "badge_id = ? AND granted_at BETWEEN ? AND ?", + badge.id, + previous_month_beginning, + Time.zone.now, + ).exists? + return + end scores(previous_month_beginning, previous_month_end).each do |user_id, score| # Don't bother awarding to users who haven't received any likes @@ -24,9 +29,10 @@ module Jobs if user.badges.where(id: Badge::NewUserOfTheMonth).blank? BadgeGranter.grant(badge, user, created_at: previous_month_end) - SystemMessage.new(user).create('new_user_of_the_month', + SystemMessage.new(user).create( + "new_user_of_the_month", month_year: I18n.l(previous_month_beginning, format: :no_day), - url: "#{Discourse.base_url}/badges" + url: "#{Discourse.base_url}/badges", ) end end @@ -65,7 +71,7 @@ module Jobs LEFT OUTER JOIN topics AS t ON t.id = p.topic_id WHERE u.active AND u.id > 0 - AND u.id NOT IN (#{current_owners.join(',')}) + AND u.id NOT IN (#{current_owners.join(",")}) AND NOT u.staged AND NOT u.admin AND NOT u.moderator @@ -87,10 +93,9 @@ module Jobs *DB.query_single( sql, min_user_created_at: min_user_created_at, - max_user_created_at: max_user_created_at + max_user_created_at: max_user_created_at, ) ] end - end end diff --git a/app/jobs/scheduled/heartbeat.rb b/app/jobs/scheduled/heartbeat.rb index 16ab8b77bbb..04b807ea8e2 100644 --- a/app/jobs/scheduled/heartbeat.rb +++ b/app/jobs/scheduled/heartbeat.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - # used to ensure at least 1 sidekiq is running correctly class Heartbeat < ::Jobs::Scheduled every 3.minute diff --git a/app/jobs/scheduled/ignored_users_summary.rb b/app/jobs/scheduled/ignored_users_summary.rb index 59483c2b529..962176a69d3 100644 --- a/app/jobs/scheduled/ignored_users_summary.rb +++ b/app/jobs/scheduled/ignored_users_summary.rb @@ -24,7 +24,11 @@ module Jobs private def notify_user(user) - params = SystemMessage.new(user).defaults.merge(ignores_threshold: SiteSetting.ignored_users_count_message_threshold) + params = + SystemMessage + .new(user) + .defaults + .merge(ignores_threshold: SiteSetting.ignored_users_count_message_threshold) title = I18n.t("system_messages.ignored_users_summary.subject_template") raw = I18n.t("system_messages.ignored_users_summary.text_body_template", params) @@ -35,7 +39,8 @@ module Jobs subtype: TopicSubtype.system_message, title: title, raw: raw, - skip_validations: true) + skip_validations: true, + ) IgnoredUser.where(ignored_user_id: user.id).update_all(summarized_at: Time.zone.now) end end diff --git a/app/jobs/scheduled/invalidate_inactive_admins.rb b/app/jobs/scheduled/invalidate_inactive_admins.rb index bc3e2500a15..3e78dae5a3a 100644 --- a/app/jobs/scheduled/invalidate_inactive_admins.rb +++ b/app/jobs/scheduled/invalidate_inactive_admins.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class InvalidateInactiveAdmins < ::Jobs::Scheduled every 1.day @@ -10,29 +9,38 @@ module Jobs timestamp = SiteSetting.invalidate_inactive_admin_email_after_days.days.ago - User.human_users + User + .human_users .where(admin: true) .where(active: true) - .where('last_seen_at < ?', timestamp) - .where("NOT EXISTS ( SELECT 1 from api_keys WHERE api_keys.user_id = users.id AND COALESCE(last_used_at, updated_at) > ? )", timestamp) - .where("NOT EXISTS ( SELECT 1 from posts WHERE posts.user_id = users.id AND created_at > ?)", timestamp) + .where("last_seen_at < ?", timestamp) + .where( + "NOT EXISTS ( SELECT 1 from api_keys WHERE api_keys.user_id = users.id AND COALESCE(last_used_at, updated_at) > ? )", + timestamp, + ) + .where( + "NOT EXISTS ( SELECT 1 from posts WHERE posts.user_id = users.id AND created_at > ?)", + timestamp, + ) .each do |user| + User.transaction do + user.deactivate(Discourse.system_user) + user.email_tokens.update_all(confirmed: false, expired: true) - User.transaction do - user.deactivate(Discourse.system_user) - user.email_tokens.update_all(confirmed: false, expired: true) + reason = + I18n.t( + "user.deactivated_by_inactivity", + count: SiteSetting.invalidate_inactive_admin_email_after_days, + ) + StaffActionLogger.new(Discourse.system_user).log_user_deactivate(user, reason) - reason = I18n.t("user.deactivated_by_inactivity", count: SiteSetting.invalidate_inactive_admin_email_after_days) - StaffActionLogger.new(Discourse.system_user).log_user_deactivate(user, reason) - - Discourse.authenticators.each do |authenticator| - if authenticator.can_revoke? && authenticator.description_for_user(user).present? - authenticator.revoke(user) + Discourse.authenticators.each do |authenticator| + if authenticator.can_revoke? && authenticator.description_for_user(user).present? + authenticator.revoke(user) + end end end end - end end end - end diff --git a/app/jobs/scheduled/migrate_upload_scheme.rb b/app/jobs/scheduled/migrate_upload_scheme.rb index 601ad3afd87..bccae4d7b8f 100644 --- a/app/jobs/scheduled/migrate_upload_scheme.rb +++ b/app/jobs/scheduled/migrate_upload_scheme.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class MigrateUploadScheme < ::Jobs::Scheduled every 10.minutes sidekiq_options retry: false @@ -10,51 +9,44 @@ module Jobs return unless SiteSetting.migrate_to_new_scheme # clean up failed uploads - Upload.where("created_at < ?", 1.hour.ago) + Upload + .where("created_at < ?", 1.hour.ago) .where("LENGTH(COALESCE(url, '')) = 0") - .find_each do |upload| - - upload.destroy! - end + .find_each { |upload| upload.destroy! } # migrate uploads to new scheme problems = Upload.migrate_to_new_scheme(limit: 50) problems.each do |hash| upload_id = hash[:upload].id - Discourse.handle_job_exception(hash[:ex], error_context(args, "Migrating upload id #{upload_id}", upload_id: upload_id)) + Discourse.handle_job_exception( + hash[:ex], + error_context(args, "Migrating upload id #{upload_id}", upload_id: upload_id), + ) end # clean up failed optimized images OptimizedImage .where("LENGTH(COALESCE(url, '')) = 0") - .find_each do |optimized_image| - - optimized_image.destroy! - end + .find_each { |optimized_image| optimized_image.destroy! } # Clean up orphan optimized images OptimizedImage .joins("LEFT JOIN uploads ON optimized_images.upload_id = uploads.id") .where("uploads.id IS NULL") - .find_each do |optimized_image| - - optimized_image.destroy! - end + .find_each { |optimized_image| optimized_image.destroy! } # Clean up optimized images that needs to be regenerated - OptimizedImage.joins(:upload) + OptimizedImage + .joins(:upload) .where("optimized_images.url NOT LIKE '%/optimized/_X/%'") .where("uploads.url LIKE '%/original/_X/%'") .limit(50) .find_each do |optimized_image| - - upload = optimized_image.upload - optimized_image.destroy! - upload.rebake_posts_on_old_scheme - end + upload = optimized_image.upload + optimized_image.destroy! + upload.rebake_posts_on_old_scheme + end end - end - end diff --git a/app/jobs/scheduled/old_keys_reminder.rb b/app/jobs/scheduled/old_keys_reminder.rb index de824fa5d0e..cec2108b542 100644 --- a/app/jobs/scheduled/old_keys_reminder.rb +++ b/app/jobs/scheduled/old_keys_reminder.rb @@ -17,26 +17,34 @@ module Jobs raw: body, archetype: Archetype.private_message, target_usernames: admins.map(&:username), - validate: false + validate: false, ) end private def old_site_settings_keys - @old_site_settings_keys ||= SiteSetting.secret_settings.each_with_object([]) do |secret_name, old_keys| - site_setting = SiteSetting.find_by(name: secret_name) - next if site_setting&.value.blank? - next if site_setting.updated_at + OLD_CREDENTIALS_PERIOD > Time.zone.now - old_keys << site_setting - end.sort_by { |key| key.updated_at } + @old_site_settings_keys ||= + SiteSetting + .secret_settings + .each_with_object([]) do |secret_name, old_keys| + site_setting = SiteSetting.find_by(name: secret_name) + next if site_setting&.value.blank? + next if site_setting.updated_at + OLD_CREDENTIALS_PERIOD > Time.zone.now + old_keys << site_setting + end + .sort_by { |key| key.updated_at } end def old_api_keys - @old_api_keys ||= ApiKey.all.order(created_at: :asc).each_with_object([]) do |api_key, old_keys| - next if api_key.created_at + OLD_CREDENTIALS_PERIOD > Time.zone.now - old_keys << api_key - end + @old_api_keys ||= + ApiKey + .all + .order(created_at: :asc) + .each_with_object([]) do |api_key, old_keys| + next if api_key.created_at + OLD_CREDENTIALS_PERIOD > Time.zone.now + old_keys << api_key + end end def admins @@ -45,20 +53,24 @@ module Jobs def message_exists? message = Topic.private_messages.with_deleted.find_by(title: title) - message && message.created_at + SiteSetting.send_old_credential_reminder_days.to_i.days > Time.zone.now + message && + message.created_at + SiteSetting.send_old_credential_reminder_days.to_i.days > Time.zone.now end def title - I18n.t('old_keys_reminder.title') + I18n.t("old_keys_reminder.title") end def body - I18n.t('old_keys_reminder.body', keys: keys_list) + I18n.t("old_keys_reminder.body", keys: keys_list) end def keys_list - messages = old_site_settings_keys.map { |key| "#{key.name} - #{key.updated_at.to_date.to_fs(:db)}" } - old_api_keys.each_with_object(messages) { |key, array| array << "#{[key.description, key.user&.username, key.created_at.to_date.to_fs(:db)].compact.join(" - ")}" } + messages = + old_site_settings_keys.map { |key| "#{key.name} - #{key.updated_at.to_date.to_fs(:db)}" } + old_api_keys.each_with_object(messages) do |key, array| + array << "#{[key.description, key.user&.username, key.created_at.to_date.to_fs(:db)].compact.join(" - ")}" + end messages.join("\n") end end diff --git a/app/jobs/scheduled/pending_queued_posts_reminder.rb b/app/jobs/scheduled/pending_queued_posts_reminder.rb index 22b84fc701b..78f5220adbd 100644 --- a/app/jobs/scheduled/pending_queued_posts_reminder.rb +++ b/app/jobs/scheduled/pending_queued_posts_reminder.rb @@ -2,7 +2,6 @@ module Jobs class PendingQueuedPostsReminder < ::Jobs::Scheduled - every 15.minutes def execute(args) @@ -16,8 +15,16 @@ module Jobs target_group_names: Group[:moderators].name, archetype: Archetype.private_message, subtype: TopicSubtype.system_message, - title: I18n.t('system_messages.queued_posts_reminder.subject_template', count: queued_post_ids.size), - raw: I18n.t('system_messages.queued_posts_reminder.text_body_template', base_url: Discourse.base_url) + title: + I18n.t( + "system_messages.queued_posts_reminder.subject_template", + count: queued_post_ids.size, + ), + raw: + I18n.t( + "system_messages.queued_posts_reminder.text_body_template", + base_url: Discourse.base_url, + ), ) self.last_notified_id = queued_post_ids.max @@ -27,9 +34,10 @@ module Jobs end def should_notify_ids - ReviewableQueuedPost.pending.where( - 'created_at < ?', SiteSetting.notify_about_queued_posts_after.to_f.hours.ago - ).pluck(:id) + ReviewableQueuedPost + .pending + .where("created_at < ?", SiteSetting.notify_about_queued_posts_after.to_f.hours.ago) + .pluck(:id) end def last_notified_id diff --git a/app/jobs/scheduled/pending_reviewables_reminder.rb b/app/jobs/scheduled/pending_reviewables_reminder.rb index 1a94e70b836..deb784934ef 100644 --- a/app/jobs/scheduled/pending_reviewables_reminder.rb +++ b/app/jobs/scheduled/pending_reviewables_reminder.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class PendingReviewablesReminder < ::Jobs::Scheduled every 15.minutes @@ -11,25 +10,30 @@ module Jobs @sent_reminder = false if SiteSetting.notify_about_flags_after > 0 - reviewable_ids = Reviewable - .pending - .default_visible - .where('latest_score < ?', SiteSetting.notify_about_flags_after.to_f.hours.ago) - .order('id DESC') - .pluck(:id) + reviewable_ids = + Reviewable + .pending + .default_visible + .where("latest_score < ?", SiteSetting.notify_about_flags_after.to_f.hours.ago) + .order("id DESC") + .pluck(:id) if reviewable_ids.size > 0 && self.class.last_notified_id < reviewable_ids[0] usernames = active_moderator_usernames - mentions = usernames.size > 0 ? "@#{usernames.join(', @')} " : "" + mentions = usernames.size > 0 ? "@#{usernames.join(", @")} " : "" - message = GroupMessage.new( - Group[:moderators].name, - 'reviewables_reminder', - { - limit_once_per: false, - message_params: { mentions: mentions, count: SiteSetting.notify_about_flags_after } - } - ) + message = + GroupMessage.new( + Group[:moderators].name, + "reviewables_reminder", + { + limit_once_per: false, + message_params: { + mentions: mentions, + count: SiteSetting.notify_about_flags_after, + }, + }, + ) Topic.transaction do message.delete_previous!(match_raw: false) @@ -58,13 +62,7 @@ module Jobs end def active_moderator_usernames - User.where(moderator: true) - .human_users - .order('last_seen_at DESC') - .limit(3) - .pluck(:username) + User.where(moderator: true).human_users.order("last_seen_at DESC").limit(3).pluck(:username) end - end - end diff --git a/app/jobs/scheduled/pending_users_reminder.rb b/app/jobs/scheduled/pending_users_reminder.rb index 0d8a2d393f9..0ed3fbdc57b 100644 --- a/app/jobs/scheduled/pending_users_reminder.rb +++ b/app/jobs/scheduled/pending_users_reminder.rb @@ -1,15 +1,18 @@ # frozen_string_literal: true module Jobs - class PendingUsersReminder < ::Jobs::Scheduled every 5.minutes def execute(args) if SiteSetting.must_approve_users && SiteSetting.pending_users_reminder_delay_minutes >= 0 - query = AdminUserIndexQuery.new(query: 'pending', stats: false).find_users_query # default order is: users.created_at DESC + query = AdminUserIndexQuery.new(query: "pending", stats: false).find_users_query # default order is: users.created_at DESC if SiteSetting.pending_users_reminder_delay_minutes > 0 - query = query.where('users.created_at < ?', SiteSetting.pending_users_reminder_delay_minutes.minutes.ago) + query = + query.where( + "users.created_at < ?", + SiteSetting.pending_users_reminder_delay_minutes.minutes.ago, + ) end newest_username = query.limit(1).select(:username).first&.username @@ -19,17 +22,24 @@ module Jobs count = query.count if count > 0 - target_usernames = Group[:moderators].users.map do |user| - next if user.bot? + target_usernames = + Group[:moderators] + .users + .map do |user| + next if user.bot? - unseen_count = user.notifications.joins(:topic) - .where("notifications.id > ?", user.seen_notification_id) - .where("notifications.read = false") - .where("topics.subtype = ?", TopicSubtype.pending_users_reminder) - .count + unseen_count = + user + .notifications + .joins(:topic) + .where("notifications.id > ?", user.seen_notification_id) + .where("notifications.read = false") + .where("topics.subtype = ?", TopicSubtype.pending_users_reminder) + .count - unseen_count == 0 ? user.username : nil - end.compact + unseen_count == 0 ? user.username : nil + end + .compact unless target_usernames.empty? PostCreator.create( @@ -37,8 +47,14 @@ module Jobs target_usernames: target_usernames, archetype: Archetype.private_message, subtype: TopicSubtype.pending_users_reminder, - title: I18n.t("system_messages.pending_users_reminder.subject_template", count: count), - raw: I18n.t("system_messages.pending_users_reminder.text_body_template", count: count, base_url: Discourse.base_url) + title: + I18n.t("system_messages.pending_users_reminder.subject_template", count: count), + raw: + I18n.t( + "system_messages.pending_users_reminder.text_body_template", + count: count, + base_url: Discourse.base_url, + ), ) self.previous_newest_username = newest_username @@ -60,7 +76,5 @@ module Jobs def previous_newest_username_cache_key "pending-users-reminder:newest-username" end - end - end diff --git a/app/jobs/scheduled/periodical_updates.rb b/app/jobs/scheduled/periodical_updates.rb index 5c5da8bac6b..f74adc180c7 100644 --- a/app/jobs/scheduled/periodical_updates.rb +++ b/app/jobs/scheduled/periodical_updates.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - # This job will run on a regular basis to update statistics and denormalized data. # If it does not run, the site will not function properly. class PeriodicalUpdates < ::Jobs::Scheduled @@ -25,11 +24,15 @@ module Jobs ScoreCalculator.new.calculate(args) # Forces rebake of old posts where needed, as long as no system avatars need updating - if !SiteSetting.automatically_download_gravatars || !UserAvatar.where("last_gravatar_download_attempt IS NULL").limit(1).first + if !SiteSetting.automatically_download_gravatars || + !UserAvatar.where("last_gravatar_download_attempt IS NULL").limit(1).first problems = Post.rebake_old(SiteSetting.rebake_old_posts_count, priority: :ultra_low) problems.each do |hash| post_id = hash[:post].id - Discourse.handle_job_exception(hash[:ex], error_context(args, "Rebaking post id #{post_id}", post_id: post_id)) + Discourse.handle_job_exception( + hash[:ex], + error_context(args, "Rebaking post id #{post_id}", post_id: post_id), + ) end end @@ -37,20 +40,19 @@ module Jobs problems = UserProfile.rebake_old(250) problems.each do |hash| user_id = hash[:profile].user_id - Discourse.handle_job_exception(hash[:ex], error_context(args, "Rebaking user id #{user_id}", user_id: user_id)) + Discourse.handle_job_exception( + hash[:ex], + error_context(args, "Rebaking user id #{user_id}", user_id: user_id), + ) end offset = (SiteSetting.max_new_topics).to_i - last_new_topic = Topic.order('created_at desc').offset(offset).select(:created_at).first - if last_new_topic - SiteSetting.min_new_topics_time = last_new_topic.created_at.to_i - end + last_new_topic = Topic.order("created_at desc").offset(offset).select(:created_at).first + SiteSetting.min_new_topics_time = last_new_topic.created_at.to_i if last_new_topic Category.auto_bump_topic! nil end - end - end diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb index 19e5a09d654..f4535bc0779 100644 --- a/app/jobs/scheduled/poll_mailbox.rb +++ b/app/jobs/scheduled/poll_mailbox.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'net/pop' +require "net/pop" module Jobs class PollMailbox < ::Jobs::Scheduled @@ -43,26 +43,37 @@ module Jobs process_popmail(mail_string) p.delete if SiteSetting.pop3_polling_delete_from_server? rescue => e - Discourse.handle_job_exception(e, error_context(@args, "Failed to process incoming email.")) + Discourse.handle_job_exception( + e, + error_context(@args, "Failed to process incoming email."), + ) end end rescue Net::OpenTimeout => e count = Discourse.redis.incr(POLL_MAILBOX_TIMEOUT_ERROR_KEY).to_i - Discourse.redis.expire( - POLL_MAILBOX_TIMEOUT_ERROR_KEY, - SiteSetting.pop3_polling_period_mins.minutes * 3 - ) if count == 1 + if count == 1 + Discourse.redis.expire( + POLL_MAILBOX_TIMEOUT_ERROR_KEY, + SiteSetting.pop3_polling_period_mins.minutes * 3, + ) + end if count > 3 Discourse.redis.del(POLL_MAILBOX_TIMEOUT_ERROR_KEY) mark_as_errored! - add_admin_dashboard_problem_message('dashboard.poll_pop3_timeout') - Discourse.handle_job_exception(e, error_context(@args, "Connecting to '#{SiteSetting.pop3_polling_host}' for polling emails.")) + add_admin_dashboard_problem_message("dashboard.poll_pop3_timeout") + Discourse.handle_job_exception( + e, + error_context( + @args, + "Connecting to '#{SiteSetting.pop3_polling_host}' for polling emails.", + ), + ) end rescue Net::POPAuthenticationError => e mark_as_errored! - add_admin_dashboard_problem_message('dashboard.poll_pop3_auth_error') + add_admin_dashboard_problem_message("dashboard.poll_pop3_auth_error") Discourse.handle_job_exception(e, error_context(@args, "Signing in to poll incoming emails.")) end @@ -75,7 +86,7 @@ module Jobs def mail_too_old?(mail_string) mail = Mail.new(mail_string) - date_header = mail.header['Date'] + date_header = mail.header["Date"] return false if date_header.blank? date = Time.parse(date_header.to_s) @@ -90,9 +101,8 @@ module Jobs def add_admin_dashboard_problem_message(i18n_key) AdminDashboardData.add_problem_message( i18n_key, - SiteSetting.pop3_polling_period_mins.minutes + 5.minutes + SiteSetting.pop3_polling_period_mins.minutes + 5.minutes, ) end - end end diff --git a/app/jobs/scheduled/process_user_notification_schedules.rb b/app/jobs/scheduled/process_user_notification_schedules.rb index 1e793e01f06..1f8bbe4f839 100644 --- a/app/jobs/scheduled/process_user_notification_schedules.rb +++ b/app/jobs/scheduled/process_user_notification_schedules.rb @@ -5,13 +5,19 @@ module Jobs every 1.day def execute(args) - UserNotificationSchedule.enabled.includes(:user).each do |schedule| - begin - schedule.create_do_not_disturb_timings - rescue => e - Discourse.warn_exception(e, message: "Failed to process user_notification_schedule with ID #{schedule.id}") + UserNotificationSchedule + .enabled + .includes(:user) + .each do |schedule| + begin + schedule.create_do_not_disturb_timings + rescue => e + Discourse.warn_exception( + e, + message: "Failed to process user_notification_schedule with ID #{schedule.id}", + ) + end end - end end end end diff --git a/app/jobs/scheduled/purge_deleted_uploads.rb b/app/jobs/scheduled/purge_deleted_uploads.rb index 73c3de50544..ca5376e04f3 100644 --- a/app/jobs/scheduled/purge_deleted_uploads.rb +++ b/app/jobs/scheduled/purge_deleted_uploads.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class PurgeDeletedUploads < ::Jobs::Scheduled every 1.day @@ -9,7 +8,5 @@ module Jobs grace_period = SiteSetting.purge_deleted_uploads_grace_period_days Discourse.store.purge_tombstone(grace_period) end - end - end diff --git a/app/jobs/scheduled/reindex_search.rb b/app/jobs/scheduled/reindex_search.rb index 26215cdc7ae..db54093bad5 100644 --- a/app/jobs/scheduled/reindex_search.rb +++ b/app/jobs/scheduled/reindex_search.rb @@ -113,7 +113,11 @@ module Jobs def load_problem_category_ids(limit) Category .joins("LEFT JOIN category_search_data ON category_id = categories.id") - .where("category_search_data.locale IS NULL OR category_search_data.locale != ? OR category_search_data.version != ?", SiteSetting.default_locale, SearchIndexer::CATEGORY_INDEX_VERSION) + .where( + "category_search_data.locale IS NULL OR category_search_data.locale != ? OR category_search_data.version != ?", + SiteSetting.default_locale, + SearchIndexer::CATEGORY_INDEX_VERSION, + ) .order("categories.id ASC") .limit(limit) .pluck(:id) @@ -122,7 +126,11 @@ module Jobs def load_problem_tag_ids(limit) Tag .joins("LEFT JOIN tag_search_data ON tag_id = tags.id") - .where("tag_search_data.locale IS NULL OR tag_search_data.locale != ? OR tag_search_data.version != ?", SiteSetting.default_locale, SearchIndexer::TAG_INDEX_VERSION) + .where( + "tag_search_data.locale IS NULL OR tag_search_data.locale != ? OR tag_search_data.version != ?", + SiteSetting.default_locale, + SearchIndexer::TAG_INDEX_VERSION, + ) .order("tags.id ASC") .limit(limit) .pluck(:id) @@ -131,7 +139,11 @@ module Jobs def load_problem_topic_ids(limit) Topic .joins("LEFT JOIN topic_search_data ON topic_id = topics.id") - .where("topic_search_data.locale IS NULL OR topic_search_data.locale != ? OR topic_search_data.version != ?", SiteSetting.default_locale, SearchIndexer::TOPIC_INDEX_VERSION) + .where( + "topic_search_data.locale IS NULL OR topic_search_data.locale != ? OR topic_search_data.version != ?", + SiteSetting.default_locale, + SearchIndexer::TOPIC_INDEX_VERSION, + ) .order("topics.id DESC") .limit(limit) .pluck(:id) @@ -143,7 +155,11 @@ module Jobs .joins("LEFT JOIN post_search_data ON post_id = posts.id") .where("posts.raw != ''") .where("topics.deleted_at IS NULL") - .where("post_search_data.locale IS NULL OR post_search_data.locale != ? OR post_search_data.version != ?", SiteSetting.default_locale, SearchIndexer::POST_INDEX_VERSION) + .where( + "post_search_data.locale IS NULL OR post_search_data.locale != ? OR post_search_data.version != ?", + SiteSetting.default_locale, + SearchIndexer::POST_INDEX_VERSION, + ) .order("posts.id DESC") .limit(limit) .pluck(:id) @@ -152,11 +168,14 @@ module Jobs def load_problem_user_ids(limit) User .joins("LEFT JOIN user_search_data ON user_id = users.id") - .where("user_search_data.locale IS NULL OR user_search_data.locale != ? OR user_search_data.version != ?", SiteSetting.default_locale, SearchIndexer::USER_INDEX_VERSION) + .where( + "user_search_data.locale IS NULL OR user_search_data.locale != ? OR user_search_data.version != ?", + SiteSetting.default_locale, + SearchIndexer::USER_INDEX_VERSION, + ) .order("users.id ASC") .limit(limit) .pluck(:id) end - end end diff --git a/app/jobs/scheduled/reviewable_priorities.rb b/app/jobs/scheduled/reviewable_priorities.rb index b9c5e4c8b55..55c54ba78c8 100644 --- a/app/jobs/scheduled/reviewable_priorities.rb +++ b/app/jobs/scheduled/reviewable_priorities.rb @@ -18,7 +18,9 @@ class Jobs::ReviewablePriorities < ::Jobs::Scheduled reviewable_count = Reviewable.approved.where("score > ?", min_priority_threshold).count return if reviewable_count < self.class.min_reviewables - res = DB.query_single(<<~SQL, target_count: self.class.target_count, min_priority: min_priority_threshold) + res = + DB.query_single( + <<~SQL, SELECT COALESCE(PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY score), 0.0) AS medium, COALESCE(PERCENTILE_DISC(0.85) WITHIN GROUP (ORDER BY score), 0.0) AS high FROM ( @@ -30,15 +32,14 @@ class Jobs::ReviewablePriorities < ::Jobs::Scheduled HAVING COUNT(*) >= :target_count ) AS x SQL + target_count: self.class.target_count, + min_priority: min_priority_threshold, + ) return unless res && res.size == 2 medium, high = res - Reviewable.set_priorities( - low: min_priority_threshold, - medium: medium, - high: high - ) + Reviewable.set_priorities(low: min_priority_threshold, medium: medium, high: high) end end diff --git a/app/jobs/scheduled/schedule_backup.rb b/app/jobs/scheduled/schedule_backup.rb index 703a4515c47..a4e0bcb8939 100644 --- a/app/jobs/scheduled/schedule_backup.rb +++ b/app/jobs/scheduled/schedule_backup.rb @@ -30,7 +30,7 @@ module Jobs Discourse.system_user, :backup_failed, target_group_names: Group[:admins].name, - logs: "#{ex}\n" + ex.backtrace.join("\n") + logs: "#{ex}\n" + ex.backtrace.join("\n"), ) end end diff --git a/app/jobs/scheduled/tl3_promotions.rb b/app/jobs/scheduled/tl3_promotions.rb index af1d6350ff4..9de8f711d43 100644 --- a/app/jobs/scheduled/tl3_promotions.rb +++ b/app/jobs/scheduled/tl3_promotions.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - class Tl3Promotions < ::Jobs::Scheduled daily at: 4.hours @@ -9,40 +8,40 @@ module Jobs if SiteSetting.default_trust_level < 3 # Demotions demoted_user_ids = [] - User.real - .joins("LEFT JOIN (SELECT gu.user_id, MAX(g.grant_trust_level) AS group_granted_trust_level FROM groups g, group_users gu WHERE g.id = gu.group_id GROUP BY gu.user_id) tl ON users.id = tl.user_id") - .where( - trust_level: TrustLevel[3], - manual_locked_trust_level: nil + User + .real + .joins( + "LEFT JOIN (SELECT gu.user_id, MAX(g.grant_trust_level) AS group_granted_trust_level FROM groups g, group_users gu WHERE g.id = gu.group_id GROUP BY gu.user_id) tl ON users.id = tl.user_id", + ) + .where(trust_level: TrustLevel[3], manual_locked_trust_level: nil) + .where( + "group_granted_trust_level IS NULL OR group_granted_trust_level < ?", + TrustLevel[3], ) - .where("group_granted_trust_level IS NULL OR group_granted_trust_level < ?", TrustLevel[3]) .find_each do |u| - # Don't demote too soon after being promoted - next if u.on_tl3_grace_period? + # Don't demote too soon after being promoted + next if u.on_tl3_grace_period? - if Promotion.tl3_lost?(u) - demoted_user_ids << u.id - Promotion.new(u).change_trust_level!(TrustLevel[2]) + if Promotion.tl3_lost?(u) + demoted_user_ids << u.id + Promotion.new(u).change_trust_level!(TrustLevel[2]) + end end - end end # Promotions - User.real.not_suspended.where( - trust_level: TrustLevel[2], - manual_locked_trust_level: nil - ).where.not(id: demoted_user_ids) + User + .real + .not_suspended + .where(trust_level: TrustLevel[2], manual_locked_trust_level: nil) + .where.not(id: demoted_user_ids) .joins(:user_stat) .where("user_stats.days_visited >= ?", SiteSetting.tl3_requires_days_visited) .where("user_stats.topics_entered >= ?", SiteSetting.tl3_requires_topics_viewed_all_time) .where("user_stats.posts_read_count >= ?", SiteSetting.tl3_requires_posts_read_all_time) .where("user_stats.likes_given >= ?", SiteSetting.tl3_requires_likes_given) .where("user_stats.likes_received >= ?", SiteSetting.tl3_requires_likes_received) - .find_each do |u| - Promotion.new(u).review_tl2 - end - + .find_each { |u| Promotion.new(u).review_tl2 } end end - end diff --git a/app/jobs/scheduled/topic_timer_enqueuer.rb b/app/jobs/scheduled/topic_timer_enqueuer.rb index 67204c4200c..13322184e66 100644 --- a/app/jobs/scheduled/topic_timer_enqueuer.rb +++ b/app/jobs/scheduled/topic_timer_enqueuer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - # Runs periodically to look through topic timers that are ready to execute, # and enqueues their related jobs. # @@ -13,13 +12,15 @@ module Jobs def execute(_args = nil) TopicTimer.pending_timers.find_each do |timer| - # the typed job may not enqueue if it has already # been scheduled with enqueue_at begin timer.enqueue_typed_job rescue => err - Discourse.warn_exception(err, message: "Error when attempting to enqueue topic timer job for timer #{timer.id}") + Discourse.warn_exception( + err, + message: "Error when attempting to enqueue topic timer job for timer #{timer.id}", + ) end end end diff --git a/app/jobs/scheduled/unsilence_users.rb b/app/jobs/scheduled/unsilence_users.rb index f6f5f5e0ff4..dbf0a907951 100644 --- a/app/jobs/scheduled/unsilence_users.rb +++ b/app/jobs/scheduled/unsilence_users.rb @@ -5,9 +5,9 @@ module Jobs every 15.minutes def execute(args) - User.where("silenced_till IS NOT NULL AND silenced_till < now()").find_each do |user| - UserSilencer.unsilence(user, Discourse.system_user) - end + User + .where("silenced_till IS NOT NULL AND silenced_till < now()") + .find_each { |user| UserSilencer.unsilence(user, Discourse.system_user) } end end end diff --git a/app/jobs/scheduled/update_animated_uploads.rb b/app/jobs/scheduled/update_animated_uploads.rb index 500e21d7b16..51ea6204a4f 100644 --- a/app/jobs/scheduled/update_animated_uploads.rb +++ b/app/jobs/scheduled/update_animated_uploads.rb @@ -12,11 +12,11 @@ module Jobs .where(animated: nil) .limit(MAX_PROCESSED_GIF_IMAGES) .each do |upload| - uri = Discourse.store.path_for(upload) || upload.url - upload.animated = FastImage.animated?(uri) - upload.save(validate: false) - upload.optimized_images.destroy_all if upload.animated - end + uri = Discourse.store.path_for(upload) || upload.url + upload.animated = FastImage.animated?(uri) + upload.save(validate: false) + upload.optimized_images.destroy_all if upload.animated + end nil end diff --git a/app/jobs/scheduled/version_check.rb b/app/jobs/scheduled/version_check.rb index 37eebf01f9e..6df0e103f82 100644 --- a/app/jobs/scheduled/version_check.rb +++ b/app/jobs/scheduled/version_check.rb @@ -5,25 +5,23 @@ module Jobs every 1.day def execute(args) - if SiteSetting.version_checks? && (DiscourseUpdates.updated_at.nil? || DiscourseUpdates.updated_at < (1.minute.ago)) + if SiteSetting.version_checks? && + (DiscourseUpdates.updated_at.nil? || DiscourseUpdates.updated_at < (1.minute.ago)) begin prev_missing_versions_count = DiscourseUpdates.missing_versions_count || 0 json = DiscourseHub.discourse_version_check DiscourseUpdates.last_installed_version = Discourse::VERSION::STRING - DiscourseUpdates.latest_version = json['latestVersion'] - DiscourseUpdates.critical_updates_available = json['criticalUpdates'] - DiscourseUpdates.missing_versions_count = json['missingVersionsCount'] + DiscourseUpdates.latest_version = json["latestVersion"] + DiscourseUpdates.critical_updates_available = json["criticalUpdates"] + DiscourseUpdates.missing_versions_count = json["missingVersionsCount"] DiscourseUpdates.updated_at = Time.zone.now - DiscourseUpdates.missing_versions = json['versions'] - - if SiteSetting.new_version_emails && - json['missingVersionsCount'] > (0) && - prev_missing_versions_count < (json['missingVersionsCount'].to_i) + DiscourseUpdates.missing_versions = json["versions"] + if SiteSetting.new_version_emails && json["missingVersionsCount"] > (0) && + prev_missing_versions_count < (json["missingVersionsCount"].to_i) message = VersionMailer.send_notice Email::Sender.new(message, :new_version).send - end rescue => e raise e unless Rails.env.development? # Fail version check silently in development mode @@ -31,6 +29,5 @@ module Jobs end true end - end end diff --git a/app/jobs/scheduled/weekly.rb b/app/jobs/scheduled/weekly.rb index a020fa11f1d..ae023bdc343 100644 --- a/app/jobs/scheduled/weekly.rb +++ b/app/jobs/scheduled/weekly.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module Jobs - # This job will run on a regular basis to update statistics and denormalized data. # If it does not run, the site will not function properly. class Weekly < ::Jobs::Scheduled diff --git a/app/mailers/admin_confirmation_mailer.rb b/app/mailers/admin_confirmation_mailer.rb index 9b6ffa2409c..2cc20258db2 100644 --- a/app/mailers/admin_confirmation_mailer.rb +++ b/app/mailers/admin_confirmation_mailer.rb @@ -6,10 +6,10 @@ class AdminConfirmationMailer < ActionMailer::Base def send_email(to_address, target_email, target_username, token) build_email( to_address, - template: 'admin_confirmation_mailer', + template: "admin_confirmation_mailer", target_email: target_email, target_username: target_username, - admin_confirm_url: confirm_admin_url(token: token, host: Discourse.base_url) + admin_confirm_url: confirm_admin_url(token: token, host: Discourse.base_url), ) end end diff --git a/app/mailers/download_backup_mailer.rb b/app/mailers/download_backup_mailer.rb index 62d5e583684..be0b26ffede 100644 --- a/app/mailers/download_backup_mailer.rb +++ b/app/mailers/download_backup_mailer.rb @@ -4,6 +4,6 @@ class DownloadBackupMailer < ActionMailer::Base include Email::BuildEmailHelper def send_email(to_address, backup_file_path) - build_email(to_address, template: 'download_backup_mailer', backup_file_path: backup_file_path) + build_email(to_address, template: "download_backup_mailer", backup_file_path: backup_file_path) end end diff --git a/app/mailers/group_smtp_mailer.rb b/app/mailers/group_smtp_mailer.rb index c394439026a..5d0fe11f7ad 100644 --- a/app/mailers/group_smtp_mailer.rb +++ b/app/mailers/group_smtp_mailer.rb @@ -4,7 +4,7 @@ class GroupSmtpMailer < ActionMailer::Base include Email::BuildEmailHelper def send_mail(from_group, to_address, post, cc_addresses: nil, bcc_addresses: nil) - raise 'SMTP is disabled' if !SiteSetting.enable_smtp + raise "SMTP is disabled" if !SiteSetting.enable_smtp op_incoming_email = post.topic.first_post.incoming_email recipient_user = User.find_by_email(to_address, primary: true) @@ -17,7 +17,7 @@ class GroupSmtpMailer < ActionMailer::Base password: from_group.email_password, authentication: GlobalSetting.smtp_authentication, enable_starttls_auto: from_group.smtp_ssl, - return_response: true + return_response: true, } group_name = from_group.name_full_preferred @@ -37,17 +37,17 @@ class GroupSmtpMailer < ActionMailer::Base private_reply: post.topic.private_message?, participants: UserNotifications.participants(post, recipient_user, reveal_staged_email: true), include_respond_instructions: true, - template: 'user_notifications.user_posted_pm', + template: "user_notifications.user_posted_pm", use_topic_title_subject: true, topic_title: op_incoming_email&.subject || post.topic.title, add_re_to_subject: true, locale: SiteSetting.default_locale, delivery_method_options: delivery_options, from: from_group.smtp_from_address, - from_alias: I18n.t('email_from_without_site', group_name: group_name), + from_alias: I18n.t("email_from_without_site", group_name: group_name), html_override: html_override(post), cc: cc_addresses, - bcc: bcc_addresses + bcc: bcc_addresses, ) end @@ -55,7 +55,7 @@ class GroupSmtpMailer < ActionMailer::Base def html_override(post) UserNotificationRenderer.render( - template: 'email/notification', + template: "email/notification", format: :html, locals: { context_posts: nil, @@ -63,9 +63,9 @@ class GroupSmtpMailer < ActionMailer::Base post: post, in_reply_to_post: nil, classes: Rtl.new(nil).css_class, - first_footer_classes: '', - reply_above_line: true - } + first_footer_classes: "", + reply_above_line: true, + }, ) end end diff --git a/app/mailers/invite_mailer.rb b/app/mailers/invite_mailer.rb index 6f7fee63468..f11883761ae 100644 --- a/app/mailers/invite_mailer.rb +++ b/app/mailers/invite_mailer.rb @@ -3,7 +3,7 @@ class InviteMailer < ActionMailer::Base include Email::BuildEmailHelper - layout 'email_template' + layout "email_template" def send_invite(invite, invite_to_topic: false) # Find the first topic they were invited to @@ -15,16 +15,20 @@ class InviteMailer < ActionMailer::Base inviter_name = "#{invite.invited_by.name} (#{invite.invited_by.username})" end - sanitized_message = invite.custom_message.present? ? - ActionView::Base.full_sanitizer.sanitize(invite.custom_message.gsub(/\n+/, " ").strip) : nil + sanitized_message = + ( + if invite.custom_message.present? + ActionView::Base.full_sanitizer.sanitize(invite.custom_message.gsub(/\n+/, " ").strip) + else + nil + end + ) # If they were invited to a topic if invite_to_topic && first_topic.present? # get topic excerpt topic_excerpt = "" - if first_topic.excerpt - topic_excerpt = first_topic.excerpt.tr("\n", " ") - end + topic_excerpt = first_topic.excerpt.tr("\n", " ") if first_topic.excerpt topic_title = first_topic.try(:title) if SiteSetting.private_email? @@ -32,35 +36,41 @@ class InviteMailer < ActionMailer::Base topic_excerpt = "" end - build_email(invite.email, - template: sanitized_message ? 'custom_invite_mailer' : 'invite_mailer', - inviter_name: inviter_name, - site_domain_name: Discourse.current_hostname, - invite_link: invite.link(with_email_token: true), - topic_title: topic_title, - topic_excerpt: topic_excerpt, - site_description: SiteSetting.site_description, - site_title: SiteSetting.title, - user_custom_message: sanitized_message) + build_email( + invite.email, + template: sanitized_message ? "custom_invite_mailer" : "invite_mailer", + inviter_name: inviter_name, + site_domain_name: Discourse.current_hostname, + invite_link: invite.link(with_email_token: true), + topic_title: topic_title, + topic_excerpt: topic_excerpt, + site_description: SiteSetting.site_description, + site_title: SiteSetting.title, + user_custom_message: sanitized_message, + ) else - build_email(invite.email, - template: sanitized_message ? 'custom_invite_forum_mailer' : 'invite_forum_mailer', - inviter_name: inviter_name, - site_domain_name: Discourse.current_hostname, - invite_link: invite.link(with_email_token: true), - site_description: SiteSetting.site_description, - site_title: SiteSetting.title, - user_custom_message: sanitized_message) + build_email( + invite.email, + template: sanitized_message ? "custom_invite_forum_mailer" : "invite_forum_mailer", + inviter_name: inviter_name, + site_domain_name: Discourse.current_hostname, + invite_link: invite.link(with_email_token: true), + site_description: SiteSetting.site_description, + site_title: SiteSetting.title, + user_custom_message: sanitized_message, + ) end end def send_password_instructions(user) if user.present? - email_token = user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset]) - build_email(user.email, - template: 'invite_password_instructions', - email_token: email_token.token) + email_token = + user.email_tokens.create!(email: user.email, scope: EmailToken.scopes[:password_reset]) + build_email( + user.email, + template: "invite_password_instructions", + email_token: email_token.token, + ) end end - end diff --git a/app/mailers/rejection_mailer.rb b/app/mailers/rejection_mailer.rb index ecbf56fbf46..0178a97b2b7 100644 --- a/app/mailers/rejection_mailer.rb +++ b/app/mailers/rejection_mailer.rb @@ -1,27 +1,29 @@ # frozen_string_literal: true -require 'email/message_builder' +require "email/message_builder" class RejectionMailer < ActionMailer::Base include Email::BuildEmailHelper - DISALLOWED_TEMPLATE_ARGS = [:to, - :from, - :base_url, - :user_preferences_url, - :include_respond_instructions, - :html_override, - :add_unsubscribe_link, - :respond_instructions, - :style, - :body, - :post_id, - :topic_id, - :subject, - :template, - :allow_reply_by_email, - :private_reply, - :from_alias] + DISALLOWED_TEMPLATE_ARGS = %i[ + to + from + base_url + user_preferences_url + include_respond_instructions + html_override + add_unsubscribe_link + respond_instructions + style + body + post_id + topic_id + subject + template + allow_reply_by_email + private_reply + from_alias + ] # Send an email rejection message. # @@ -32,10 +34,9 @@ class RejectionMailer < ActionMailer::Base # BuildEmailHelper. You can see the list in DISALLOWED_TEMPLATE_ARGS. def send_rejection(template, message_from, template_args) if template_args.keys.any? { |k| DISALLOWED_TEMPLATE_ARGS.include? k } - raise ArgumentError.new('Reserved key in template arguments') + raise ArgumentError.new("Reserved key in template arguments") end build_email(message_from, template_args.merge(template: "system_messages.#{template}")) end - end diff --git a/app/mailers/subscription_mailer.rb b/app/mailers/subscription_mailer.rb index c2c40752c84..57d4489ac58 100644 --- a/app/mailers/subscription_mailer.rb +++ b/app/mailers/subscription_mailer.rb @@ -9,6 +9,7 @@ class SubscriptionMailer < ActionMailer::Base template: "unsubscribe_mailer", site_title: SiteSetting.title, site_domain_name: Discourse.current_hostname, - confirm_unsubscribe_link: email_unsubscribe_url(unsubscribe_key, host: Discourse.base_url) + confirm_unsubscribe_link: + email_unsubscribe_url(unsubscribe_key, host: Discourse.base_url) end end diff --git a/app/mailers/test_mailer.rb b/app/mailers/test_mailer.rb index 4abd75263a7..4c561e648aa 100644 --- a/app/mailers/test_mailer.rb +++ b/app/mailers/test_mailer.rb @@ -4,6 +4,6 @@ class TestMailer < ActionMailer::Base include Email::BuildEmailHelper def send_test(to_address, opts = {}) - build_email(to_address, template: 'test_mailer', **opts) + build_email(to_address, template: "test_mailer", **opts) end end diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 3aca5a93346..d5580858274 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -4,37 +4,38 @@ class UserNotifications < ActionMailer::Base include UserNotificationsHelper include ApplicationHelper helper :application, :email - default charset: 'UTF-8' - layout 'email_template' + default charset: "UTF-8" + layout "email_template" include Email::BuildEmailHelper def signup(user, opts = {}) - build_user_email_token_by_template( - "user_notifications.signup", - user, - opts[:email_token] - ) + build_user_email_token_by_template("user_notifications.signup", user, opts[:email_token]) end def activation_reminder(user, opts = {}) build_user_email_token_by_template( "user_notifications.activation_reminder", user, - opts[:email_token] + opts[:email_token], ) end def signup_after_approval(user, opts = {}) locale = user_locale(user) - tips = I18n.t('system_messages.usage_tips.text_body_template', - base_url: Discourse.base_url, - locale: locale) + tips = + I18n.t( + "system_messages.usage_tips.text_body_template", + base_url: Discourse.base_url, + locale: locale, + ) - build_email(user.email, - template: 'user_notifications.signup_after_approval', - locale: locale, - new_user_tips: tips) + build_email( + user.email, + template: "user_notifications.signup_after_approval", + locale: locale, + new_user_tips: tips, + ) end def post_approved(user, opts = {}) @@ -43,20 +44,23 @@ class UserNotifications < ActionMailer::Base return if post_url.nil? locale = user_locale(user) - build_email(user.email, - template: 'user_notifications.post_approved', + build_email( + user.email, + template: "user_notifications.post_approved", locale: locale, base_url: Discourse.base_url, - post_url: post_url + post_url: post_url, ) end def signup_after_reject(user, opts = {}) locale = user_locale(user) - build_email(user.email, - template: 'user_notifications.signup_after_reject', - locale: locale, - reject_reason: opts[:reject_reason]) + build_email( + user.email, + template: "user_notifications.signup_after_reject", + locale: locale, + reject_reason: opts[:reject_reason], + ) end def suspicious_login(user, opts = {}) @@ -71,32 +75,36 @@ class UserNotifications < ActionMailer::Base template: "user_notifications.suspicious_login", locale: user_locale(user), client_ip: opts[:client_ip], - location: location.present? ? location : I18n.t('staff_action_logs.unknown'), + location: location.present? ? location : I18n.t("staff_action_logs.unknown"), browser: I18n.t("user_auth_tokens.browser.#{browser}"), device: I18n.t("user_auth_tokens.device.#{device}"), - os: I18n.t("user_auth_tokens.os.#{os}") + os: I18n.t("user_auth_tokens.os.#{os}"), ) end def notify_old_email(user, opts = {}) - build_email(user.email, - template: "user_notifications.notify_old_email", - locale: user_locale(user), - new_email: opts[:new_email]) + build_email( + user.email, + template: "user_notifications.notify_old_email", + locale: user_locale(user), + new_email: opts[:new_email], + ) end def notify_old_email_add(user, opts = {}) - build_email(user.email, - template: "user_notifications.notify_old_email_add", - locale: user_locale(user), - new_email: opts[:new_email]) + build_email( + user.email, + template: "user_notifications.notify_old_email_add", + locale: user_locale(user), + new_email: opts[:new_email], + ) end def confirm_old_email(user, opts = {}) build_user_email_token_by_template( "user_notifications.confirm_old_email", user, - opts[:email_token] + opts[:email_token], ) end @@ -104,15 +112,21 @@ class UserNotifications < ActionMailer::Base build_user_email_token_by_template( "user_notifications.confirm_old_email_add", user, - opts[:email_token] + opts[:email_token], ) end def confirm_new_email(user, opts = {}) build_user_email_token_by_template( - opts[:requested_by_admin] ? "user_notifications.confirm_new_email_via_admin" : "user_notifications.confirm_new_email", + ( + if opts[:requested_by_admin] + "user_notifications.confirm_new_email_via_admin" + else + "user_notifications.confirm_new_email" + end + ), user, - opts[:email_token] + opts[:email_token], ) end @@ -120,31 +134,23 @@ class UserNotifications < ActionMailer::Base build_user_email_token_by_template( user.has_password? ? "user_notifications.forgot_password" : "user_notifications.set_password", user, - opts[:email_token] + opts[:email_token], ) end def email_login(user, opts = {}) - build_user_email_token_by_template( - "user_notifications.email_login", - user, - opts[:email_token] - ) + build_user_email_token_by_template("user_notifications.email_login", user, opts[:email_token]) end def admin_login(user, opts = {}) - build_user_email_token_by_template( - "user_notifications.admin_login", - user, - opts[:email_token] - ) + build_user_email_token_by_template("user_notifications.admin_login", user, opts[:email_token]) end def account_created(user, opts = {}) build_user_email_token_by_template( "user_notifications.account_created", user, - opts[:email_token] + opts[:email_token], ) end @@ -158,7 +164,7 @@ class UserNotifications < ActionMailer::Base user.email, template: "user_notifications.account_silenced_forever", locale: user_locale(user), - reason: user_history.details + reason: user_history.details, ) else silenced_till = user.silenced_till.in_time_zone(user.user_option.timezone) @@ -167,7 +173,7 @@ class UserNotifications < ActionMailer::Base template: "user_notifications.account_silenced", locale: user_locale(user), reason: user_history.details, - silenced_till: I18n.l(silenced_till, format: :long) + silenced_till: I18n.l(silenced_till, format: :long), ) end end @@ -182,7 +188,7 @@ class UserNotifications < ActionMailer::Base user.email, template: "user_notifications.account_suspended_forever", locale: user_locale(user), - reason: user_history.details + reason: user_history.details, ) else suspended_till = user.suspended_till.in_time_zone(user.user_option.timezone) @@ -191,7 +197,7 @@ class UserNotifications < ActionMailer::Base template: "user_notifications.account_suspended", locale: user_locale(user), reason: user_history.details, - suspended_till: I18n.l(suspended_till, format: :long) + suspended_till: I18n.l(suspended_till, format: :long), ) end end @@ -199,18 +205,18 @@ class UserNotifications < ActionMailer::Base def account_exists(user, opts = {}) build_email( user.email, - template: 'user_notifications.account_exists', + template: "user_notifications.account_exists", locale: user_locale(user), - email: user.email + email: user.email, ) end def account_second_factor_disabled(user, opts = {}) build_email( user.email, - template: 'user_notifications.account_second_factor_disabled', + template: "user_notifications.account_second_factor_disabled", locale: user_locale(user), - email: user.email + email: user.email, ) end @@ -221,34 +227,59 @@ class UserNotifications < ActionMailer::Base min_date = opts[:since] || user.last_emailed_at || user.last_seen_at || 1.month.ago # Fetch some topics and posts to show - digest_opts = { limit: SiteSetting.digest_topics + SiteSetting.digest_other_topics, top_order: true } + digest_opts = { + limit: SiteSetting.digest_topics + SiteSetting.digest_other_topics, + top_order: true, + } topics_for_digest = Topic.for_digest(user, min_date, digest_opts).to_a if topics_for_digest.empty? && !user.user_option.try(:include_tl0_in_digests) # Find some topics from new users that are at least 24 hours old - topics_for_digest = Topic.for_digest(user, min_date, digest_opts.merge(include_tl0: true)).where('topics.created_at < ?', 24.hours.ago).to_a + topics_for_digest = + Topic + .for_digest(user, min_date, digest_opts.merge(include_tl0: true)) + .where("topics.created_at < ?", 24.hours.ago) + .to_a end @popular_topics = topics_for_digest[0, SiteSetting.digest_topics] if @popular_topics.present? - @other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : [] + @other_new_for_you = + ( + if topics_for_digest.size > SiteSetting.digest_topics + topics_for_digest[SiteSetting.digest_topics..-1] + else + [] + end + ) - @popular_posts = if SiteSetting.digest_posts > 0 - Post.order("posts.score DESC") - .for_mailing_list(user, min_date) - .where('posts.post_type = ?', Post.types[:regular]) - .where('posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false') - .where("posts.post_number > ? AND posts.score > ?", 1, ScoreCalculator.default_score_weights[:like_score] * 5.0) - .where('posts.created_at < ?', (SiteSetting.editing_grace_period || 0).seconds.ago) - .limit(SiteSetting.digest_posts) - else - [] - end + @popular_posts = + if SiteSetting.digest_posts > 0 + Post + .order("posts.score DESC") + .for_mailing_list(user, min_date) + .where("posts.post_type = ?", Post.types[:regular]) + .where( + "posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false", + ) + .where( + "posts.post_number > ? AND posts.score > ?", + 1, + ScoreCalculator.default_score_weights[:like_score] * 5.0, + ) + .where("posts.created_at < ?", (SiteSetting.editing_grace_period || 0).seconds.ago) + .limit(SiteSetting.digest_posts) + else + [] + end @excerpts = {} @popular_topics.map do |t| - @excerpts[t.first_post.id] = email_excerpt(t.first_post.cooked, t.first_post) if t.first_post.present? + @excerpts[t.first_post.id] = email_excerpt( + t.first_post.cooked, + t.first_post, + ) if t.first_post.present? end # Try to find 3 interesting stats for the top of the digest @@ -258,33 +289,60 @@ class UserNotifications < ActionMailer::Base # We used topics from new users instead, so count should match new_topics_count = topics_for_digest.size end - @counts = [{ label_key: 'user_notifications.digest.new_topics', - value: new_topics_count, - href: "#{Discourse.base_url}/new" }] + @counts = [ + { + label_key: "user_notifications.digest.new_topics", + value: new_topics_count, + href: "#{Discourse.base_url}/new", + }, + ] # totalling unread notifications (which are low-priority only) and unread # PMs and bookmark reminder notifications, so the total is both unread low # and high priority PMs value = user.unread_notifications + user.unread_high_priority_notifications - @counts << { label_key: 'user_notifications.digest.unread_notifications', value: value, href: "#{Discourse.base_url}/my/notifications" } if value > 0 + if value > 0 + @counts << { + label_key: "user_notifications.digest.unread_notifications", + value: value, + href: "#{Discourse.base_url}/my/notifications", + } + end if @counts.size < 3 value = user.unread_notifications_of_type(Notification.types[:liked]) - @counts << { label_key: 'user_notifications.digest.liked_received', value: value, href: "#{Discourse.base_url}/my/notifications" } if value > 0 + if value > 0 + @counts << { + label_key: "user_notifications.digest.liked_received", + value: value, + href: "#{Discourse.base_url}/my/notifications", + } + end end if @counts.size < 3 && user.user_option.digest_after_minutes.to_i >= 1440 value = summary_new_users_count(min_date) - @counts << { label_key: 'user_notifications.digest.new_users', value: value, href: "#{Discourse.base_url}/about" } if value > 0 + if value > 0 + @counts << { + label_key: "user_notifications.digest.new_users", + value: value, + href: "#{Discourse.base_url}/about", + } + end end @last_seen_at = short_date(user.last_seen_at || user.created_at) - @preheader_text = I18n.t('user_notifications.digest.preheader', last_seen_at: @last_seen_at) + @preheader_text = I18n.t("user_notifications.digest.preheader", last_seen_at: @last_seen_at) opts = { - from_alias: I18n.t('user_notifications.digest.from', site_name: Email.site_title), - subject: I18n.t('user_notifications.digest.subject_template', email_prefix: @email_prefix, date: short_date(Time.now)), + from_alias: I18n.t("user_notifications.digest.from", site_name: Email.site_title), + subject: + I18n.t( + "user_notifications.digest.subject_template", + email_prefix: @email_prefix, + date: short_date(Time.now), + ), add_unsubscribe_link: true, unsubscribe_url: "#{Discourse.base_url}/email/unsubscribe/#{@unsubscribe_key}", } @@ -351,7 +409,7 @@ class UserNotifications < ActionMailer::Base opts[:show_group_in_subject] = true if SiteSetting.group_in_subject # We use the 'user_posted' event when you are emailed a post in a PM. - opts[:notification_type] = 'posted' + opts[:notification_type] = "posted" notification_email(user, opts) end @@ -400,29 +458,33 @@ class UserNotifications < ActionMailer::Base def email_post_markdown(post, add_posted_by = false) result = +"#{post.raw}\n\n" if add_posted_by - result << "#{I18n.t('user_notifications.posted_by', username: post.username, post_date: post.created_at.strftime("%m/%d/%Y"))}\n\n" + result << "#{I18n.t("user_notifications.posted_by", username: post.username, post_date: post.created_at.strftime("%m/%d/%Y"))}\n\n" end result end def self.get_context_posts(post, topic_user, user) if (user.user_option.email_previous_replies == UserOption.previous_replies_type[:never]) || - SiteSetting.private_email? + SiteSetting.private_email? return [] end allowed_post_types = [Post.types[:regular]] allowed_post_types << Post.types[:whisper] if topic_user.try(:user).try(:staff?) - context_posts = Post.where(topic_id: post.topic_id) - .where("post_number < ?", post.post_number) - .where(user_deleted: false) - .where(hidden: false) - .where(post_type: allowed_post_types) - .order('created_at desc') - .limit(SiteSetting.email_posts_context) + context_posts = + Post + .where(topic_id: post.topic_id) + .where("post_number < ?", post.post_number) + .where(user_deleted: false) + .where(hidden: false) + .where(post_type: allowed_post_types) + .order("created_at desc") + .limit(SiteSetting.email_posts_context) - if topic_user && topic_user.last_emailed_post_number && user.user_option.email_previous_replies == UserOption.previous_replies_type[:unless_emailed] + if topic_user && topic_user.last_emailed_post_number && + user.user_option.email_previous_replies == + UserOption.previous_replies_type[:unless_emailed] context_posts = context_posts.where("post_number > ?", topic_user.last_emailed_post_number) end @@ -435,9 +497,7 @@ class UserNotifications < ActionMailer::Base post = opts[:post] unless String === notification_type - if Numeric === notification_type - notification_type = Notification.types[notification_type] - end + notification_type = Notification.types[notification_type] if Numeric === notification_type notification_type = notification_type.to_s end @@ -449,13 +509,16 @@ class UserNotifications < ActionMailer::Base end allow_reply_by_email = opts[:allow_reply_by_email] unless user.suspended? - original_username = notification_data[:original_username] || notification_data[:display_username] + original_username = + notification_data[:original_username] || notification_data[:display_username] if user.staged && post - original_subject = IncomingEmail.joins(:post) - .where("posts.topic_id = ? AND posts.post_number = 1", post.topic_id) - .pluck(:subject) - .first + original_subject = + IncomingEmail + .joins(:post) + .where("posts.topic_id = ? AND posts.post_number = 1", post.topic_id) + .pluck(:subject) + .first end if original_subject @@ -472,7 +535,7 @@ class UserNotifications < ActionMailer::Base title: topic_title, post: post, username: original_username, - from_alias: I18n.t('email_from', user_name: user_name, site_name: Email.site_title), + from_alias: I18n.t("email_from", user_name: user_name, site_name: Email.site_title), allow_reply_by_email: allow_reply_by_email, use_site_subject: opts[:use_site_subject], add_re_to_subject: opts[:add_re_to_subject], @@ -482,7 +545,7 @@ class UserNotifications < ActionMailer::Base notification_type: notification_type, use_invite_template: opts[:use_invite_template], use_topic_title_subject: use_topic_title_subject, - user: user + user: user, } if group_id = notification_data[:group_id] @@ -525,7 +588,8 @@ class UserNotifications < ActionMailer::Base # subcategory case if !category.parent_category_id.nil? - show_category_in_subject = "#{Category.where(id: category.parent_category_id).pluck_first(:name)}/#{show_category_in_subject}" + show_category_in_subject = + "#{Category.where(id: category.parent_category_id).pluck_first(:name)}/#{show_category_in_subject}" end else show_category_in_subject = nil @@ -555,7 +619,7 @@ class UserNotifications < ActionMailer::Base "[#{group.name}] " end else - I18n.t('subject_pm') + I18n.t("subject_pm") end participants = self.class.participants(post, user) @@ -573,27 +637,25 @@ class UserNotifications < ActionMailer::Base context_posts = context_posts.to_a if context_posts.present? - context << +"-- \n*#{I18n.t('user_notifications.previous_discussion')}*\n" - context_posts.each do |cp| - context << email_post_markdown(cp, true) - end + context << +"-- \n*#{I18n.t("user_notifications.previous_discussion")}*\n" + context_posts.each { |cp| context << email_post_markdown(cp, true) } end - translation_override_exists = TranslationOverride.where( - locale: SiteSetting.default_locale, - translation_key: "#{template}.text_body_template" - ).exists? + translation_override_exists = + TranslationOverride.where( + locale: SiteSetting.default_locale, + translation_key: "#{template}.text_body_template", + ).exists? if opts[:use_invite_template] invite_template = +"user_notifications.invited" invite_template << "_group" if group_name - invite_template << - if post.topic.private_message? - "_to_private_message_body" - else - "_to_topic_body" - end + invite_template << if post.topic.private_message? + "_to_private_message_body" + else + "_to_topic_body" + end topic_excerpt = post.excerpt.tr("\n", " ") if post.is_first_post? && post.excerpt topic_url = post.topic&.url @@ -603,28 +665,38 @@ class UserNotifications < ActionMailer::Base topic_url = "" end - message = I18n.t(invite_template, - username: username, - group_name: group_name, - topic_title: gsub_emoji_to_unicode(title), - topic_excerpt: topic_excerpt, - site_title: SiteSetting.title, - site_description: SiteSetting.site_description, - topic_url: topic_url - ) + message = + I18n.t( + invite_template, + username: username, + group_name: group_name, + topic_title: gsub_emoji_to_unicode(title), + topic_excerpt: topic_excerpt, + site_title: SiteSetting.title, + site_description: SiteSetting.site_description, + topic_url: topic_url, + ) html = PrettyText.cook(message, sanitize: false).html_safe else reached_limit = SiteSetting.max_emails_per_day_per_user > 0 - reached_limit &&= (EmailLog.where(user_id: user.id) - .where('created_at > ?', 1.day.ago) - .count) >= (SiteSetting.max_emails_per_day_per_user - 1) + reached_limit &&= + (EmailLog.where(user_id: user.id).where("created_at > ?", 1.day.ago).count) >= + (SiteSetting.max_emails_per_day_per_user - 1) in_reply_to_post = post.reply_to_post if user.user_option.email_in_reply_to if SiteSetting.private_email? - message = I18n.t('system_messages.contents_hidden') + message = I18n.t("system_messages.contents_hidden") else - message = email_post_markdown(post) + (reached_limit ? "\n\n#{I18n.t "user_notifications.reached_limit", count: SiteSetting.max_emails_per_day_per_user}" : "") + message = + email_post_markdown(post) + + ( + if reached_limit + "\n\n#{I18n.t "user_notifications.reached_limit", count: SiteSetting.max_emails_per_day_per_user}" + else + "" + end + ) end first_footer_classes = "highlight" @@ -633,18 +705,20 @@ class UserNotifications < ActionMailer::Base end unless translation_override_exists - html = UserNotificationRenderer.render( - template: 'email/notification', - format: :html, - locals: { context_posts: context_posts, - reached_limit: reached_limit, - post: post, - in_reply_to_post: in_reply_to_post, - classes: Rtl.new(user).css_class, - first_footer_classes: first_footer_classes, - reply_above_line: false - } - ) + html = + UserNotificationRenderer.render( + template: "email/notification", + format: :html, + locals: { + context_posts: context_posts, + reached_limit: reached_limit, + post: post, + in_reply_to_post: in_reply_to_post, + classes: Rtl.new(user).css_class, + first_footer_classes: first_footer_classes, + reply_above_line: false, + }, + ) end end @@ -676,12 +750,10 @@ class UserNotifications < ActionMailer::Base site_description: SiteSetting.site_description, site_title: SiteSetting.title, site_title_url_encoded: UrlHelper.encode_component(SiteSetting.title), - locale: locale + locale: locale, } - unless translation_override_exists - email_opts[:html_override] = html - end + email_opts[:html_override] = html unless translation_override_exists # If we have a display name, change the from address email_opts[:from_alias] = from_alias if from_alias.present? @@ -701,20 +773,26 @@ class UserNotifications < ActionMailer::Base break if list.size >= SiteSetting.max_participant_names end - recent_posts_query = post.topic.posts - .select("user_id, MAX(post_number) AS post_number") - .where(post_type: Post.types[:regular], post_number: ..post.post_number) - .where.not(user_id: recipient_user.id) - .group(:user_id) - .order("post_number DESC") - .limit(SiteSetting.max_participant_names) - .to_sql + recent_posts_query = + post + .topic + .posts + .select("user_id, MAX(post_number) AS post_number") + .where(post_type: Post.types[:regular], post_number: ..post.post_number) + .where.not(user_id: recipient_user.id) + .group(:user_id) + .order("post_number DESC") + .limit(SiteSetting.max_participant_names) + .to_sql - allowed_users = post.topic.allowed_users - .joins("LEFT JOIN (#{recent_posts_query}) pu ON topic_allowed_users.user_id = pu.user_id") - .order("post_number DESC NULLS LAST", :id) - .where.not(id: recipient_user.id) - .human_users + allowed_users = + post + .topic + .allowed_users + .joins("LEFT JOIN (#{recent_posts_query}) pu ON topic_allowed_users.user_id = pu.user_id") + .order("post_number DESC NULLS LAST", :id) + .where.not(id: recipient_user.id) + .human_users allowed_users.each do |u| break if list.size >= SiteSetting.max_participant_names @@ -730,7 +808,11 @@ class UserNotifications < ActionMailer::Base others_count = allowed_groups.size + allowed_users.size - list.size if others_count > 0 - I18n.t("user_notifications.more_pm_participants", participants: participants, count: others_count) + I18n.t( + "user_notifications.more_pm_participants", + participants: participants, + count: others_count, + ) else participants end @@ -739,23 +821,18 @@ class UserNotifications < ActionMailer::Base private def build_user_email_token_by_template(template, user, email_token) - build_email( - user.email, - template: template, - locale: user_locale(user), - email_token: email_token - ) + build_email(user.email, template: template, locale: user_locale(user), email_token: email_token) end def build_summary_for(user) - @site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n - @user = user - @date = short_date(Time.now) - @base_url = Discourse.base_url - @email_prefix = SiteSetting.email_prefix.presence || SiteSetting.title - @header_color = ColorScheme.hex_for_name('header_primary') - @header_bgcolor = ColorScheme.hex_for_name('header_background') - @anchor_color = ColorScheme.hex_for_name('tertiary') + @site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n + @user = user + @date = short_date(Time.now) + @base_url = Discourse.base_url + @email_prefix = SiteSetting.email_prefix.presence || SiteSetting.title + @header_color = ColorScheme.hex_for_name("header_primary") + @header_bgcolor = ColorScheme.hex_for_name("header_background") + @anchor_color = ColorScheme.hex_for_name("tertiary") @markdown_linker = MarkdownLinker.new(@base_url) @disable_email_custom_styles = !SiteSetting.apply_custom_styles_to_digest end @@ -765,12 +842,19 @@ class UserNotifications < ActionMailer::Base end def summary_new_users_count(min_date) - min_date_str = min_date.is_a?(String) ? min_date : min_date.strftime('%Y-%m-%d') + min_date_str = min_date.is_a?(String) ? min_date : min_date.strftime("%Y-%m-%d") key = self.class.summary_new_users_count_key(min_date_str) - ((count = Discourse.redis.get(key)) && count.to_i) || begin - count = User.real.where(active: true, staged: false).not_suspended.where("created_at > ?", min_date_str).count - Discourse.redis.setex(key, 1.day, count) - count - end + ((count = Discourse.redis.get(key)) && count.to_i) || + begin + count = + User + .real + .where(active: true, staged: false) + .not_suspended + .where("created_at > ?", min_date_str) + .count + Discourse.redis.setex(key, 1.day, count) + count + end end end diff --git a/app/mailers/version_mailer.rb b/app/mailers/version_mailer.rb index 0b9445f452b..c38cd29a90b 100644 --- a/app/mailers/version_mailer.rb +++ b/app/mailers/version_mailer.rb @@ -6,17 +6,21 @@ class VersionMailer < ActionMailer::Base def send_notice if SiteSetting.contact_email.present? missing_versions = DiscourseUpdates.missing_versions - if missing_versions.present? && missing_versions.first['notes'].present? - build_email(SiteSetting.contact_email, - template: 'new_version_mailer_with_notes', - notes: missing_versions.first['notes'], - new_version: DiscourseUpdates.latest_version, - installed_version: Discourse::VERSION::STRING) + if missing_versions.present? && missing_versions.first["notes"].present? + build_email( + SiteSetting.contact_email, + template: "new_version_mailer_with_notes", + notes: missing_versions.first["notes"], + new_version: DiscourseUpdates.latest_version, + installed_version: Discourse::VERSION::STRING, + ) else - build_email(SiteSetting.contact_email, - template: 'new_version_mailer', - new_version: DiscourseUpdates.latest_version, - installed_version: Discourse::VERSION::STRING) + build_email( + SiteSetting.contact_email, + template: "new_version_mailer", + new_version: DiscourseUpdates.latest_version, + installed_version: Discourse::VERSION::STRING, + ) end end end diff --git a/app/models/about.rb b/app/models/about.rb index 44220472ccc..1901fccc663 100644 --- a/app/models/about.rb +++ b/app/models/about.rb @@ -32,11 +32,10 @@ class About include ActiveModel::Serialization include StatsCacheable - attr_accessor :moderators, - :admins + attr_accessor :moderators, :admins def self.stats_cache_key - 'about-stats' + "about-stats" end def self.fetch_stats @@ -68,38 +67,37 @@ class About end def moderators - @moderators ||= User.where(moderator: true, admin: false) - .human_users - .order("last_seen_at DESC") + @moderators ||= User.where(moderator: true, admin: false).human_users.order("last_seen_at DESC") end def admins - @admins ||= User.where(admin: true) - .human_users - .order("last_seen_at DESC") + @admins ||= User.where(admin: true).human_users.order("last_seen_at DESC") end def stats @stats ||= { topic_count: Topic.listable_topics.count, - topics_last_day: Topic.listable_topics.where('created_at > ?', 1.days.ago).count, - topics_7_days: Topic.listable_topics.where('created_at > ?', 7.days.ago).count, - topics_30_days: Topic.listable_topics.where('created_at > ?', 30.days.ago).count, + topics_last_day: Topic.listable_topics.where("created_at > ?", 1.days.ago).count, + topics_7_days: Topic.listable_topics.where("created_at > ?", 7.days.ago).count, + topics_30_days: Topic.listable_topics.where("created_at > ?", 30.days.ago).count, post_count: Post.count, - posts_last_day: Post.where('created_at > ?', 1.days.ago).count, - posts_7_days: Post.where('created_at > ?', 7.days.ago).count, - posts_30_days: Post.where('created_at > ?', 30.days.ago).count, + posts_last_day: Post.where("created_at > ?", 1.days.ago).count, + posts_7_days: Post.where("created_at > ?", 7.days.ago).count, + posts_30_days: Post.where("created_at > ?", 30.days.ago).count, user_count: User.real.count, - users_last_day: User.real.where('created_at > ?', 1.days.ago).count, - users_7_days: User.real.where('created_at > ?', 7.days.ago).count, - users_30_days: User.real.where('created_at > ?', 30.days.ago).count, - active_users_last_day: User.where('last_seen_at > ?', 1.days.ago).count, - active_users_7_days: User.where('last_seen_at > ?', 7.days.ago).count, - active_users_30_days: User.where('last_seen_at > ?', 30.days.ago).count, + users_last_day: User.real.where("created_at > ?", 1.days.ago).count, + users_7_days: User.real.where("created_at > ?", 7.days.ago).count, + users_30_days: User.real.where("created_at > ?", 30.days.ago).count, + active_users_last_day: User.where("last_seen_at > ?", 1.days.ago).count, + active_users_7_days: User.where("last_seen_at > ?", 7.days.ago).count, + active_users_30_days: User.where("last_seen_at > ?", 30.days.ago).count, like_count: UserAction.where(action_type: UserAction::LIKE).count, - likes_last_day: UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 1.days.ago).count, - likes_7_days: UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 7.days.ago).count, - likes_30_days: UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 30.days.ago).count + likes_last_day: + UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 1.days.ago).count, + likes_7_days: + UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 7.days.ago).count, + likes_30_days: + UserAction.where(action_type: UserAction::LIKE).where("created_at > ?", 30.days.ago).count, }.merge(plugin_stats) end @@ -109,17 +107,21 @@ class About begin stats = stat_group.call rescue StandardError => err - Discourse.warn_exception(err, message: "Unexpected error when collecting #{plugin_stat_group_name} About stats.") + Discourse.warn_exception( + err, + message: "Unexpected error when collecting #{plugin_stat_group_name} About stats.", + ) next end - if !stats.key?(:last_day) || !stats.key?("7_days") || !stats.key?("30_days") || !stats.key?(:count) - Rails.logger.warn("Plugin stat group #{plugin_stat_group_name} for About stats does not have all required keys, skipping.") + if !stats.key?(:last_day) || !stats.key?("7_days") || !stats.key?("30_days") || + !stats.key?(:count) + Rails.logger.warn( + "Plugin stat group #{plugin_stat_group_name} for About stats does not have all required keys, skipping.", + ) else final_plugin_stats.merge!( - stats.transform_keys do |key| - "#{plugin_stat_group_name}_#{key}".to_sym - end + stats.transform_keys { |key| "#{plugin_stat_group_name}_#{key}".to_sym }, ) end end @@ -151,9 +153,7 @@ class About mods = User.where(id: results.map(&:user_ids).flatten.uniq).index_by(&:id) - results.map do |row| - CategoryMods.new(row.category_id, mods.values_at(*row.user_ids)) - end + results.map { |row| CategoryMods.new(row.category_id, mods.values_at(*row.user_ids)) } end def category_mods_limit diff --git a/app/models/admin_dashboard_data.rb b/app/models/admin_dashboard_data.rb index ae6dfa2535e..8c3245a803b 100644 --- a/app/models/admin_dashboard_data.rb +++ b/app/models/admin_dashboard_data.rb @@ -3,13 +3,10 @@ class AdminDashboardData include StatsCacheable - cattr_reader :problem_syms, - :problem_blocks, - :problem_messages, - :problem_scheduled_check_blocks + cattr_reader :problem_syms, :problem_blocks, :problem_messages, :problem_scheduled_check_blocks class Problem - VALID_PRIORITIES = ["low", "high"].freeze + VALID_PRIORITIES = %w[low high].freeze attr_reader :message, :priority, :identifier @@ -114,11 +111,12 @@ class AdminDashboardData found_problems_json = Discourse.redis.get(SCHEDULED_PROBLEM_STORAGE_KEY) return [] if found_problems_json.blank? begin - JSON.parse(found_problems_json).map do |problem| - Problem.from_h(problem) - end + JSON.parse(found_problems_json).map { |problem| Problem.from_h(problem) } rescue JSON::ParserError => err - Discourse.warn_exception(err, message: "Error parsing found problem JSON in admin dashboard: #{found_problems_json}") + Discourse.warn_exception( + err, + message: "Error parsing found problem JSON in admin dashboard: #{found_problems_json}", + ) [] end end @@ -127,16 +125,21 @@ class AdminDashboardData add_scheduled_problem_check(:group_smtp_credentials) do problems = GroupEmailCredentialsCheck.run problems.map do |p| - problem_message = I18n.t( - "dashboard.group_email_credentials_warning", - { - base_path: Discourse.base_path, - group_name: p[:group_name], - group_full_name: p[:group_full_name], - error: p[:message] - } + problem_message = + I18n.t( + "dashboard.group_email_credentials_warning", + { + base_path: Discourse.base_path, + group_name: p[:group_name], + group_full_name: p[:group_full_name], + error: p[:message], + }, + ) + Problem.new( + problem_message, + priority: "high", + identifier: "group_#{p[:group_id]}_email_credentials", ) - Problem.new(problem_message, priority: "high", identifier: "group_#{p[:group_id]}_email_credentials") end end end @@ -149,7 +152,10 @@ class AdminDashboardData begin problems = instance_exec(&blk) rescue StandardError => err - Discourse.warn_exception(err, message: "A scheduled admin dashboard problem check (#{check_identifier}) errored.") + Discourse.warn_exception( + err, + message: "A scheduled admin dashboard problem check (#{check_identifier}) errored.", + ) # we don't want to hold up other checks because this one errored next end @@ -177,26 +183,33 @@ class AdminDashboardData @@problem_blocks = [] @@problem_scheduled_check_blocks = {} - @@problem_messages = [ - 'dashboard.bad_favicon_url', - 'dashboard.poll_pop3_timeout', - 'dashboard.poll_pop3_auth_error', + @@problem_messages = %w[ + dashboard.bad_favicon_url + dashboard.poll_pop3_timeout + dashboard.poll_pop3_auth_error ] - add_problem_check :rails_env_check, :host_names_check, :force_https_check, - :ram_check, :google_oauth2_config_check, - :facebook_config_check, :twitter_config_check, - :github_config_check, :s3_config_check, :s3_cdn_check, - :image_magick_check, :failing_emails_check, + add_problem_check :rails_env_check, + :host_names_check, + :force_https_check, + :ram_check, + :google_oauth2_config_check, + :facebook_config_check, + :twitter_config_check, + :github_config_check, + :s3_config_check, + :s3_cdn_check, + :image_magick_check, + :failing_emails_check, :subfolder_ends_in_slash_check, :email_polling_errored_recently, - :out_of_date_themes, :unreachable_themes, :watched_words_check + :out_of_date_themes, + :unreachable_themes, + :watched_words_check register_default_scheduled_problem_checks - add_problem_check do - sidekiq_check || queue_size_check - end + add_problem_check { sidekiq_check || queue_size_check } end reset_problem_checks @@ -213,7 +226,7 @@ class AdminDashboardData end def self.problems_started_key - 'dash-problems-started-at' + "dash-problems-started-at" end def self.set_problems_started @@ -235,7 +248,11 @@ class AdminDashboardData end def self.problem_message_check(i18n_key) - Discourse.redis.get(problem_message_key(i18n_key)) ? I18n.t(i18n_key, base_path: Discourse.base_path) : nil + if Discourse.redis.get(problem_message_key(i18n_key)) + I18n.t(i18n_key, base_path: Discourse.base_path) + else + nil + end end ## @@ -264,97 +281,128 @@ class AdminDashboardData end def host_names_check - I18n.t("dashboard.host_names_warning") if ['localhost', 'production.localhost'].include?(Discourse.current_hostname) + if %w[localhost production.localhost].include?(Discourse.current_hostname) + I18n.t("dashboard.host_names_warning") + end end def sidekiq_check last_job_performed_at = Jobs.last_job_performed_at - I18n.t('dashboard.sidekiq_warning') if Jobs.queued > 0 && (last_job_performed_at.nil? || last_job_performed_at < 2.minutes.ago) + if Jobs.queued > 0 && (last_job_performed_at.nil? || last_job_performed_at < 2.minutes.ago) + I18n.t("dashboard.sidekiq_warning") + end end def queue_size_check queue_size = Jobs.queued - I18n.t('dashboard.queue_size_warning', queue_size: queue_size) unless queue_size < 100_000 + I18n.t("dashboard.queue_size_warning", queue_size: queue_size) unless queue_size < 100_000 end def ram_check - I18n.t('dashboard.memory_warning') if MemInfo.new.mem_total && MemInfo.new.mem_total < 950_000 + I18n.t("dashboard.memory_warning") if MemInfo.new.mem_total && MemInfo.new.mem_total < 950_000 end def google_oauth2_config_check - if SiteSetting.enable_google_oauth2_logins && (SiteSetting.google_oauth2_client_id.blank? || SiteSetting.google_oauth2_client_secret.blank?) - I18n.t('dashboard.google_oauth2_config_warning', base_path: Discourse.base_path) + if SiteSetting.enable_google_oauth2_logins && + ( + SiteSetting.google_oauth2_client_id.blank? || + SiteSetting.google_oauth2_client_secret.blank? + ) + I18n.t("dashboard.google_oauth2_config_warning", base_path: Discourse.base_path) end end def facebook_config_check - if SiteSetting.enable_facebook_logins && (SiteSetting.facebook_app_id.blank? || SiteSetting.facebook_app_secret.blank?) - I18n.t('dashboard.facebook_config_warning', base_path: Discourse.base_path) + if SiteSetting.enable_facebook_logins && + (SiteSetting.facebook_app_id.blank? || SiteSetting.facebook_app_secret.blank?) + I18n.t("dashboard.facebook_config_warning", base_path: Discourse.base_path) end end def twitter_config_check - if SiteSetting.enable_twitter_logins && (SiteSetting.twitter_consumer_key.blank? || SiteSetting.twitter_consumer_secret.blank?) - I18n.t('dashboard.twitter_config_warning', base_path: Discourse.base_path) + if SiteSetting.enable_twitter_logins && + (SiteSetting.twitter_consumer_key.blank? || SiteSetting.twitter_consumer_secret.blank?) + I18n.t("dashboard.twitter_config_warning", base_path: Discourse.base_path) end end def github_config_check - if SiteSetting.enable_github_logins && (SiteSetting.github_client_id.blank? || SiteSetting.github_client_secret.blank?) - I18n.t('dashboard.github_config_warning', base_path: Discourse.base_path) + if SiteSetting.enable_github_logins && + (SiteSetting.github_client_id.blank? || SiteSetting.github_client_secret.blank?) + I18n.t("dashboard.github_config_warning", base_path: Discourse.base_path) end end def s3_config_check # if set via global setting it is validated during the `use_s3?` call if !GlobalSetting.use_s3? - bad_keys = (SiteSetting.s3_access_key_id.blank? || SiteSetting.s3_secret_access_key.blank?) && !SiteSetting.s3_use_iam_profile + bad_keys = + (SiteSetting.s3_access_key_id.blank? || SiteSetting.s3_secret_access_key.blank?) && + !SiteSetting.s3_use_iam_profile if SiteSetting.enable_s3_uploads && (bad_keys || SiteSetting.s3_upload_bucket.blank?) - return I18n.t('dashboard.s3_config_warning', base_path: Discourse.base_path) + return I18n.t("dashboard.s3_config_warning", base_path: Discourse.base_path) end - if SiteSetting.backup_location == BackupLocationSiteSetting::S3 && (bad_keys || SiteSetting.s3_backup_bucket.blank?) - return I18n.t('dashboard.s3_backup_config_warning', base_path: Discourse.base_path) + if SiteSetting.backup_location == BackupLocationSiteSetting::S3 && + (bad_keys || SiteSetting.s3_backup_bucket.blank?) + return I18n.t("dashboard.s3_backup_config_warning", base_path: Discourse.base_path) end end nil end def s3_cdn_check - if (GlobalSetting.use_s3? || SiteSetting.enable_s3_uploads) && SiteSetting.Upload.s3_cdn_url.blank? - I18n.t('dashboard.s3_cdn_warning') + if (GlobalSetting.use_s3? || SiteSetting.enable_s3_uploads) && + SiteSetting.Upload.s3_cdn_url.blank? + I18n.t("dashboard.s3_cdn_warning") end end def image_magick_check - I18n.t('dashboard.image_magick_warning') if SiteSetting.create_thumbnails && !system("command -v convert >/dev/null;") + if SiteSetting.create_thumbnails && !system("command -v convert >/dev/null;") + I18n.t("dashboard.image_magick_warning") + end end def failing_emails_check num_failed_jobs = Jobs.num_email_retry_jobs - I18n.t('dashboard.failing_emails_warning', num_failed_jobs: num_failed_jobs, base_path: Discourse.base_path) if num_failed_jobs > 0 + if num_failed_jobs > 0 + I18n.t( + "dashboard.failing_emails_warning", + num_failed_jobs: num_failed_jobs, + base_path: Discourse.base_path, + ) + end end def subfolder_ends_in_slash_check - I18n.t('dashboard.subfolder_ends_in_slash') if Discourse.base_path =~ /\/$/ + I18n.t("dashboard.subfolder_ends_in_slash") if Discourse.base_path =~ %r{/$} end def email_polling_errored_recently errors = Jobs::PollMailbox.errors_in_past_24_hours - I18n.t('dashboard.email_polling_errored_recently', count: errors, base_path: Discourse.base_path) if errors > 0 + if errors > 0 + I18n.t( + "dashboard.email_polling_errored_recently", + count: errors, + base_path: Discourse.base_path, + ) + end end def missing_mailgun_api_key return unless SiteSetting.reply_by_email_enabled - return unless ActionMailer::Base.smtp_settings[:address]['smtp.mailgun.org'] + return unless ActionMailer::Base.smtp_settings[:address]["smtp.mailgun.org"] return unless SiteSetting.mailgun_api_key.blank? - I18n.t('dashboard.missing_mailgun_api_key') + I18n.t("dashboard.missing_mailgun_api_key") end def force_https_check return unless @opts[:check_force_https] - I18n.t('dashboard.force_https_warning', base_path: Discourse.base_path) unless SiteSetting.force_https + unless SiteSetting.force_https + I18n.t("dashboard.force_https_warning", base_path: Discourse.base_path) + end end def watched_words_check @@ -363,7 +411,11 @@ class AdminDashboardData WordWatcher.word_matcher_regexp_list(action, raise_errors: true) rescue RegexpError => e translated_action = I18n.t("admin_js.admin.watched_words.actions.#{action}") - I18n.t('dashboard.watched_word_regexp_error', base_path: Discourse.base_path, action: translated_action) + I18n.t( + "dashboard.watched_word_regexp_error", + base_path: Discourse.base_path, + action: translated_action, + ) end end nil @@ -373,22 +425,25 @@ class AdminDashboardData old_themes = RemoteTheme.out_of_date_themes return unless old_themes.present? - themes_html_format(old_themes, 'dashboard.out_of_date_themes') + themes_html_format(old_themes, "dashboard.out_of_date_themes") end def unreachable_themes themes = RemoteTheme.unreachable_themes return unless themes.present? - themes_html_format(themes, 'dashboard.unreachable_themes') + themes_html_format(themes, "dashboard.unreachable_themes") end private def themes_html_format(themes, i18n_key) - html = themes.map do |name, id| - "
  • #{CGI.escapeHTML(name)}
  • " - end.join("\n") + html = + themes + .map do |name, id| + "
  • #{CGI.escapeHTML(name)}
  • " + end + .join("\n") "#{I18n.t(i18n_key)}" end diff --git a/app/models/admin_dashboard_general_data.rb b/app/models/admin_dashboard_general_data.rb index 06962ac2901..eff6304c545 100644 --- a/app/models/admin_dashboard_general_data.rb +++ b/app/models/admin_dashboard_general_data.rb @@ -2,11 +2,13 @@ class AdminDashboardGeneralData < AdminDashboardData def get_json - days_since_update = Discourse.last_commit_date ? ((DateTime.now - Discourse.last_commit_date) / 1.day).to_i : nil + days_since_update = + Discourse.last_commit_date ? ((DateTime.now - Discourse.last_commit_date) / 1.day).to_i : nil { updated_at: Time.zone.now.as_json, discourse_updated_at: Discourse.last_commit_date, - release_notes_link: "https://meta.discourse.org/c/announcements/67?tags=release-notes&before=#{days_since_update}" + release_notes_link: + "https://meta.discourse.org/c/announcements/67?tags=release-notes&before=#{days_since_update}", } end diff --git a/app/models/admin_dashboard_index_data.rb b/app/models/admin_dashboard_index_data.rb index 92da9f0087d..b8f92d2dae0 100644 --- a/app/models/admin_dashboard_index_data.rb +++ b/app/models/admin_dashboard_index_data.rb @@ -2,9 +2,7 @@ class AdminDashboardIndexData < AdminDashboardData def get_json - { - updated_at: Time.zone.now.as_json - } + { updated_at: Time.zone.now.as_json } end def self.stats_cache_key diff --git a/app/models/anonymous_user.rb b/app/models/anonymous_user.rb index 57147625083..b6b42e2eee4 100644 --- a/app/models/anonymous_user.rb +++ b/app/models/anonymous_user.rb @@ -2,7 +2,7 @@ class AnonymousUser < ActiveRecord::Base belongs_to :user - belongs_to :master_user, class_name: 'User' + belongs_to :master_user, class_name: "User" end # == Schema Information diff --git a/app/models/api_key.rb b/app/models/api_key.rb index 1849f2cd398..dc66d2fb40e 100644 --- a/app/models/api_key.rb +++ b/app/models/api_key.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true class ApiKey < ActiveRecord::Base - class KeyAccessError < StandardError; end + class KeyAccessError < StandardError + end has_many :api_key_scopes belongs_to :user - belongs_to :created_by, class_name: 'User' + belongs_to :created_by, class_name: "User" scope :active, -> { where("revoked_at IS NULL") } scope :revoked, -> { where("revoked_at IS NOT NULL") } - scope :with_key, ->(key) { - hashed = self.hash_key(key) - where(key_hash: hashed) - } + scope :with_key, + ->(key) { + hashed = self.hash_key(key) + where(key_hash: hashed) + } after_initialize :generate_key @@ -26,7 +28,9 @@ class ApiKey < ActiveRecord::Base end def key - raise KeyAccessError.new "API key is only accessible immediately after creation" unless key_available? + unless key_available? + raise KeyAccessError.new "API key is only accessible immediately after creation" + end @key end @@ -40,10 +44,12 @@ class ApiKey < ActiveRecord::Base def self.revoke_unused_keys! return if SiteSetting.revoke_api_keys_days == 0 # Never expire keys - to_revoke = active.where("GREATEST(last_used_at, created_at, updated_at, :epoch) < :threshold", - epoch: last_used_epoch, - threshold: SiteSetting.revoke_api_keys_days.days.ago - ) + to_revoke = + active.where( + "GREATEST(last_used_at, created_at, updated_at, :epoch) < :threshold", + epoch: last_used_epoch, + threshold: SiteSetting.revoke_api_keys_days.days.ago, + ) to_revoke.find_each do |api_key| ApiKey.transaction do @@ -53,7 +59,12 @@ class ApiKey < ActiveRecord::Base api_key, UserHistory.actions[:api_key_update], changes: api_key.saved_changes, - context: I18n.t("staff_action_logs.api_key.automatic_revoked", count: SiteSetting.revoke_api_keys_days)) + context: + I18n.t( + "staff_action_logs.api_key.automatic_revoked", + count: SiteSetting.revoke_api_keys_days, + ), + ) end end end @@ -63,7 +74,9 @@ class ApiKey < ActiveRecord::Base end def request_allowed?(env) - return false if allowed_ips.present? && allowed_ips.none? { |ip| ip.include?(Rack::Request.new(env).ip) } + if allowed_ips.present? && allowed_ips.none? { |ip| ip.include?(Rack::Request.new(env).ip) } + return false + end return true if RouteMatcher.new(methods: :get, actions: "session#scopes").match?(env: env) api_key_scopes.blank? || api_key_scopes.any? { |s| s.permits?(env) } diff --git a/app/models/api_key_scope.rb b/app/models/api_key_scope.rb index 18db82addd7..c0d072d0bca 100644 --- a/app/models/api_key_scope.rb +++ b/app/models/api_key_scope.rb @@ -18,26 +18,48 @@ class ApiKeyScope < ActiveRecord::Base mappings = { global: { - read: { methods: %i[get] } + read: { + methods: %i[get], + }, }, topics: { - write: { actions: %w[posts#create], params: %i[topic_id] }, - update: { actions: %w[topics#update], params: %i[topic_id] }, + write: { + actions: %w[posts#create], + params: %i[topic_id], + }, + update: { + actions: %w[topics#update], + params: %i[topic_id], + }, read: { actions: %w[topics#show topics#feed topics#posts], - params: %i[topic_id], aliases: { topic_id: :id } + params: %i[topic_id], + aliases: { + topic_id: :id, + }, }, read_lists: { - actions: list_actions, params: %i[category_id], - aliases: { category_id: :category_slug_path_with_id } - } + actions: list_actions, + params: %i[category_id], + aliases: { + category_id: :category_slug_path_with_id, + }, + }, }, posts: { - edit: { actions: %w[posts#update], params: %i[id] } + edit: { + actions: %w[posts#update], + params: %i[id], + }, }, categories: { - list: { actions: %w[categories#index] }, - show: { actions: %w[categories#show], params: %i[id] } + list: { + actions: %w[categories#index], + }, + show: { + actions: %w[categories#show], + params: %i[id], + }, }, uploads: { create: { @@ -49,42 +71,95 @@ class ApiKeyScope < ActiveRecord::Base uploads#batch_presign_multipart_parts uploads#abort_multipart uploads#complete_multipart - ] - } + ], + }, }, users: { - bookmarks: { actions: %w[users#bookmarks], params: %i[username] }, - sync_sso: { actions: %w[admin/users#sync_sso], params: %i[sso sig] }, - show: { actions: %w[users#show], params: %i[username external_id external_provider] }, - check_emails: { actions: %w[users#check_emails], params: %i[username] }, - update: { actions: %w[users#update], params: %i[username] }, - log_out: { actions: %w[admin/users#log_out] }, - anonymize: { actions: %w[admin/users#anonymize] }, - delete: { actions: %w[admin/users#destroy] }, - list: { actions: %w[admin/users#index] }, + bookmarks: { + actions: %w[users#bookmarks], + params: %i[username], + }, + sync_sso: { + actions: %w[admin/users#sync_sso], + params: %i[sso sig], + }, + show: { + actions: %w[users#show], + params: %i[username external_id external_provider], + }, + check_emails: { + actions: %w[users#check_emails], + params: %i[username], + }, + update: { + actions: %w[users#update], + params: %i[username], + }, + log_out: { + actions: %w[admin/users#log_out], + }, + anonymize: { + actions: %w[admin/users#anonymize], + }, + delete: { + actions: %w[admin/users#destroy], + }, + list: { + actions: %w[admin/users#index], + }, }, user_status: { - read: { actions: %w[user_status#get] }, - update: { actions: %w[user_status#set user_status#clear] }, + read: { + actions: %w[user_status#get], + }, + update: { + actions: %w[user_status#set user_status#clear], + }, }, email: { - receive_emails: { actions: %w[admin/email#handle_mail admin/email#smtp_should_reject] } + receive_emails: { + actions: %w[admin/email#handle_mail admin/email#smtp_should_reject], + }, }, badges: { - create: { actions: %w[admin/badges#create] }, - show: { actions: %w[badges#show] }, - update: { actions: %w[admin/badges#update] }, - delete: { actions: %w[admin/badges#destroy] }, - list_user_badges: { actions: %w[user_badges#username], params: %i[username] }, - assign_badge_to_user: { actions: %w[user_badges#create], params: %i[username] }, - revoke_badge_from_user: { actions: %w[user_badges#destroy] }, + create: { + actions: %w[admin/badges#create], + }, + show: { + actions: %w[badges#show], + }, + update: { + actions: %w[admin/badges#update], + }, + delete: { + actions: %w[admin/badges#destroy], + }, + list_user_badges: { + actions: %w[user_badges#username], + params: %i[username], + }, + assign_badge_to_user: { + actions: %w[user_badges#create], + params: %i[username], + }, + revoke_badge_from_user: { + actions: %w[user_badges#destroy], + }, }, wordpress: { - publishing: { actions: %w[site#site posts#create topics#update topics#status topics#show] }, - commenting: { actions: %w[topics#wordpress] }, - discourse_connect: { actions: %w[admin/users#sync_sso admin/users#log_out admin/users#index users#show] }, - utilities: { actions: %w[users#create groups#index] } - } + publishing: { + actions: %w[site#site posts#create topics#update topics#status topics#show], + }, + commenting: { + actions: %w[topics#wordpress], + }, + discourse_connect: { + actions: %w[admin/users#sync_sso admin/users#log_out admin/users#index users#show], + }, + utilities: { + actions: %w[users#create groups#index], + }, + }, } parse_resources!(mappings) @@ -106,7 +181,10 @@ class ApiKeyScope < ActiveRecord::Base def parse_resources!(mappings) mappings.each_value do |resource_actions| resource_actions.each_value do |action_data| - action_data[:urls] = find_urls(actions: action_data[:actions], methods: action_data[:methods]) + action_data[:urls] = find_urls( + actions: action_data[:actions], + methods: action_data[:methods], + ) end end end @@ -128,12 +206,12 @@ class ApiKeyScope < ActiveRecord::Base set.routes.each do |route| defaults = route.defaults action = "#{defaults[:controller].to_s}##{defaults[:action]}" - path = route.path.spec.to_s.gsub(/\(\.:format\)/, '') - api_supported_path = ( - path.end_with?('.rss') || - !route.path.requirements[:format] || - route.path.requirements[:format].match?('json') - ) + path = route.path.spec.to_s.gsub(/\(\.:format\)/, "") + api_supported_path = + ( + path.end_with?(".rss") || !route.path.requirements[:format] || + route.path.requirements[:format].match?("json") + ) excluded_paths = %w[/new-topic /new-message /exception] if actions.include?(action) && api_supported_path && !excluded_paths.include?(path) @@ -143,18 +221,16 @@ class ApiKeyScope < ActiveRecord::Base end end - if methods.present? - methods.each do |method| - urls << "* (#{method})" - end - end + methods.each { |method| urls << "* (#{method})" } if methods.present? urls.to_a end end def permits?(env) - RouteMatcher.new(**mapping.except(:urls), allowed_param_values: allowed_parameters).match?(env: env) + RouteMatcher.new(**mapping.except(:urls), allowed_param_values: allowed_parameters).match?( + env: env, + ) end private diff --git a/app/models/application_request.rb b/app/models/application_request.rb index 81125566e4a..d422a08c960 100644 --- a/app/models/application_request.rb +++ b/app/models/application_request.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true class ApplicationRequest < ActiveRecord::Base - - enum req_type: %i(http_total - http_2xx - http_background - http_3xx - http_4xx - http_5xx - page_view_crawler - page_view_logged_in - page_view_anon - page_view_logged_in_mobile - page_view_anon_mobile - api - user_api) + enum req_type: %i[ + http_total + http_2xx + http_background + http_3xx + http_4xx + http_5xx + page_view_crawler + page_view_logged_in + page_view_anon + page_view_logged_in_mobile + page_view_anon_mobile + api + user_api + ] include CachedCounting @@ -36,14 +37,12 @@ class ApplicationRequest < ActiveRecord::Base end def self.req_id(date, req_type, retries = 0) - req_type_id = req_types[req_type] # a poor man's upsert id = where(date: date, req_type: req_type_id).pluck_first(:id) id ||= create!(date: date, req_type: req_type_id, count: 0).id - - rescue # primary key violation + rescue StandardError # primary key violation if retries == 0 req_id(date, req_type, 1) else @@ -56,9 +55,9 @@ class ApplicationRequest < ActiveRecord::Base self.req_types.each do |key, i| query = self.where(req_type: i) - s["#{key}_total"] = query.sum(:count) + s["#{key}_total"] = query.sum(:count) s["#{key}_30_days"] = query.where("date > ?", 30.days.ago).sum(:count) - s["#{key}_7_days"] = query.where("date > ?", 7.days.ago).sum(:count) + s["#{key}_7_days"] = query.where("date > ?", 7.days.ago).sum(:count) end s diff --git a/app/models/associated_group.rb b/app/models/associated_group.rb index 9d6e9365dcd..3c8c635094b 100644 --- a/app/models/associated_group.rb +++ b/app/models/associated_group.rb @@ -14,9 +14,11 @@ class AssociatedGroup < ActiveRecord::Base end def self.cleanup! - AssociatedGroup.left_joins(:group_associated_groups, :user_associated_groups) + AssociatedGroup + .left_joins(:group_associated_groups, :user_associated_groups) .where("group_associated_groups.id IS NULL AND user_associated_groups.id IS NULL") - .where("last_used < ?", 1.week.ago).delete_all + .where("last_used < ?", 1.week.ago) + .delete_all end end diff --git a/app/models/auto_track_duration_site_setting.rb b/app/models/auto_track_duration_site_setting.rb index 9764d0d5256..0aad12e6e41 100644 --- a/app/models/auto_track_duration_site_setting.rb +++ b/app/models/auto_track_duration_site_setting.rb @@ -1,28 +1,25 @@ # frozen_string_literal: true class AutoTrackDurationSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.auto_track_options.never', value: -1 }, - { name: 'user.auto_track_options.immediately', value: 0 }, - { name: 'user.auto_track_options.after_30_seconds', value: 1000 * 30 }, - { name: 'user.auto_track_options.after_1_minute', value: 1000 * 60 }, - { name: 'user.auto_track_options.after_2_minutes', value: 1000 * 60 * 2 }, - { name: 'user.auto_track_options.after_3_minutes', value: 1000 * 60 * 3 }, - { name: 'user.auto_track_options.after_4_minutes', value: 1000 * 60 * 4 }, - { name: 'user.auto_track_options.after_5_minutes', value: 1000 * 60 * 5 }, - { name: 'user.auto_track_options.after_10_minutes', value: 1000 * 60 * 10 }, + { name: "user.auto_track_options.never", value: -1 }, + { name: "user.auto_track_options.immediately", value: 0 }, + { name: "user.auto_track_options.after_30_seconds", value: 1000 * 30 }, + { name: "user.auto_track_options.after_1_minute", value: 1000 * 60 }, + { name: "user.auto_track_options.after_2_minutes", value: 1000 * 60 * 2 }, + { name: "user.auto_track_options.after_3_minutes", value: 1000 * 60 * 3 }, + { name: "user.auto_track_options.after_4_minutes", value: 1000 * 60 * 4 }, + { name: "user.auto_track_options.after_5_minutes", value: 1000 * 60 * 5 }, + { name: "user.auto_track_options.after_10_minutes", value: 1000 * 60 * 10 }, ] end def self.translate_names? true end - end diff --git a/app/models/backup_file.rb b/app/models/backup_file.rb index a8a09731a11..46377691047 100644 --- a/app/models/backup_file.rb +++ b/app/models/backup_file.rb @@ -3,10 +3,7 @@ class BackupFile include ActiveModel::SerializerSupport - attr_reader :filename, - :size, - :last_modified, - :source + attr_reader :filename, :size, :last_modified, :source def initialize(filename:, size:, last_modified:, source: nil) @filename = filename diff --git a/app/models/backup_location_site_setting.rb b/app/models/backup_location_site_setting.rb index 4236ca1de7f..1bdbd2403d6 100644 --- a/app/models/backup_location_site_setting.rb +++ b/app/models/backup_location_site_setting.rb @@ -11,7 +11,7 @@ class BackupLocationSiteSetting < EnumSiteSetting def self.values @values ||= [ { name: "admin.backups.location.local", value: LOCAL }, - { name: "admin.backups.location.s3", value: S3 } + { name: "admin.backups.location.s3", value: S3 }, ] end diff --git a/app/models/badge.rb b/app/models/badge.rb index 2a84344cad8..d8b1ce794da 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -2,7 +2,7 @@ class Badge < ActiveRecord::Base # TODO: Drop in July 2021 - self.ignored_columns = %w{image} + self.ignored_columns = %w[image] include GlobalPath include HasSanitizableFields @@ -76,10 +76,15 @@ class Badge < ActiveRecord::Base attr_accessor :has_badge def self.trigger_hash - @trigger_hash ||= Badge::Trigger.constants.map do |k| - name = k.to_s.underscore - [name, Badge::Trigger.const_get(k)] unless name =~ /deprecated/ - end.compact.to_h + @trigger_hash ||= + Badge::Trigger + .constants + .map do |k| + name = k.to_s.underscore + [name, Badge::Trigger.const_get(k)] unless name =~ /deprecated/ + end + .compact + .to_h end module Trigger @@ -105,7 +110,7 @@ class Badge < ActiveRecord::Base belongs_to :badge_type belongs_to :badge_grouping - belongs_to :image_upload, class_name: 'Upload' + belongs_to :image_upload, class_name: "Upload" has_many :user_badges, dependent: :destroy has_many :upload_references, as: :target, dependent: :destroy @@ -134,11 +139,7 @@ class Badge < ActiveRecord::Base # fields that can not be edited on system badges def self.protected_system_fields - [ - :name, :badge_type_id, :multiple_grant, - :target_posts, :show_posts, :query, - :trigger, :auto_revoke, :listable - ] + %i[name badge_type_id multiple_grant target_posts show_posts query trigger auto_revoke listable] end def self.trust_level_badge_ids @@ -152,7 +153,7 @@ class Badge < ActiveRecord::Base GreatPost => 50, NiceTopic => 10, GoodTopic => 25, - GreatTopic => 50 + GreatTopic => 50, } end @@ -219,7 +220,7 @@ class Badge < ActiveRecord::Base end def self.i18n_name(name) - name.downcase.tr(' ', '_') + name.downcase.tr(" ", "_") end def self.display_name(name) @@ -231,8 +232,8 @@ class Badge < ActiveRecord::Base end def self.find_system_badge_id_from_translation_key(translation_key) - return unless translation_key.starts_with?('badges.') - badge_name_klass = translation_key.split('.').second.camelize + return unless translation_key.starts_with?("badges.") + badge_name_klass = translation_key.split(".").second.camelize Badge.const_defined?(badge_name_klass) ? "Badge::#{badge_name_klass}".constantize : nil end @@ -283,7 +284,12 @@ class Badge < ActiveRecord::Base def long_description key = "badges.#{i18n_name}.long_description" - I18n.t(key, default: self[:long_description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day) + I18n.t( + key, + default: self[:long_description] || "", + base_uri: Discourse.base_path, + max_likes_per_day: SiteSetting.max_likes_per_day, + ) end def long_description=(val) @@ -293,7 +299,12 @@ class Badge < ActiveRecord::Base def description key = "badges.#{i18n_name}.description" - I18n.t(key, default: self[:description] || '', base_uri: Discourse.base_path, max_likes_per_day: SiteSetting.max_likes_per_day) + I18n.t( + key, + default: self[:description] || "", + base_uri: Discourse.base_path, + max_likes_per_day: SiteSetting.max_likes_per_day, + ) end def description=(val) @@ -302,7 +313,7 @@ class Badge < ActiveRecord::Base end def slug - Slug.for(self.display_name, '-') + Slug.for(self.display_name, "-") end def manually_grantable? @@ -314,9 +325,7 @@ class Badge < ActiveRecord::Base end def image_url - if image_upload_id.present? - upload_cdn_path(image_upload.url) - end + upload_cdn_path(image_upload.url) if image_upload_id.present? end def for_beginners? @@ -330,9 +339,7 @@ class Badge < ActiveRecord::Base end def sanitize_description - if description_changed? - self.description = sanitize_field(self.description) - end + self.description = sanitize_field(self.description) if description_changed? end end diff --git a/app/models/badge_grouping.rb b/app/models/badge_grouping.rb index b95c78f1c4e..c0afcbdbb10 100644 --- a/app/models/badge_grouping.rb +++ b/app/models/badge_grouping.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class BadgeGrouping < ActiveRecord::Base - GettingStarted = 1 Community = 2 Posting = 3 diff --git a/app/models/base_font_setting.rb b/app/models/base_font_setting.rb index 36c0321876b..d4e3971f0df 100644 --- a/app/models/base_font_setting.rb +++ b/app/models/base_font_setting.rb @@ -8,8 +8,6 @@ class BaseFontSetting < EnumSiteSetting end def self.values - @values ||= DiscourseFonts.fonts.map do |font| - { name: font[:name], value: font[:key] } - end + @values ||= DiscourseFonts.fonts.map { |font| { name: font[:name], value: font[:key] } } end end diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb index c6947cf0787..3e1a6dab387 100644 --- a/app/models/bookmark.rb +++ b/app/models/bookmark.rb @@ -3,7 +3,7 @@ class Bookmark < ActiveRecord::Base self.ignored_columns = [ "post_id", # TODO (martin) (2022-08-01) remove - "for_topic" # TODO (martin) (2022-08-01) remove + "for_topic", # TODO (martin) (2022-08-01) remove ] cattr_accessor :registered_bookmarkables @@ -42,26 +42,24 @@ class Bookmark < ActiveRecord::Base belongs_to :bookmarkable, polymorphic: true def self.auto_delete_preferences - @auto_delete_preferences ||= Enum.new( - never: 0, - when_reminder_sent: 1, - on_owner_reply: 2, - clear_reminder: 3, - ) + @auto_delete_preferences ||= + Enum.new(never: 0, when_reminder_sent: 1, on_owner_reply: 2, clear_reminder: 3) end def self.select_type(bookmarks_relation, type) bookmarks_relation.select { |bm| bm.bookmarkable_type == type } end - validate :polymorphic_columns_present, on: [:create, :update] - validate :valid_bookmarkable_type, on: [:create, :update] + validate :polymorphic_columns_present, on: %i[create update] + validate :valid_bookmarkable_type, on: %i[create update] validate :unique_per_bookmarkable, - on: [:create, :update], - if: Proc.new { |b| - b.will_save_change_to_bookmarkable_id? || b.will_save_change_to_bookmarkable_type? || b.will_save_change_to_user_id? - } + on: %i[create update], + if: + Proc.new { |b| + b.will_save_change_to_bookmarkable_id? || b.will_save_change_to_bookmarkable_type? || + b.will_save_change_to_user_id? + } validate :ensure_sane_reminder_at_time, if: :will_save_change_to_reminder_at? validate :bookmark_limit_not_reached @@ -78,7 +76,13 @@ class Bookmark < ActiveRecord::Base end def unique_per_bookmarkable - return if !Bookmark.exists?(user_id: user_id, bookmarkable_id: bookmarkable_id, bookmarkable_type: bookmarkable_type) + if !Bookmark.exists?( + user_id: user_id, + bookmarkable_id: bookmarkable_id, + bookmarkable_type: bookmarkable_type, + ) + return + end self.errors.add(:base, I18n.t("bookmarks.errors.already_bookmarked", type: bookmarkable_type)) end @@ -102,15 +106,18 @@ class Bookmark < ActiveRecord::Base I18n.t( "bookmarks.errors.too_many", user_bookmarks_url: "#{Discourse.base_url}/my/activity/bookmarks", - limit: SiteSetting.max_bookmarks_per_user - ) + limit: SiteSetting.max_bookmarks_per_user, + ), ) end def valid_bookmarkable_type return if Bookmark.valid_bookmarkable_types.include?(self.bookmarkable_type) - self.errors.add(:base, I18n.t("bookmarks.errors.invalid_bookmarkable", type: self.bookmarkable_type)) + self.errors.add( + :base, + I18n.t("bookmarks.errors.invalid_bookmarkable", type: self.bookmarkable_type), + ) end def auto_delete_when_reminder_sent? @@ -126,54 +133,57 @@ class Bookmark < ActiveRecord::Base end def clear_reminder! - update!( - reminder_last_sent_at: Time.zone.now, - reminder_set_at: nil, - ) + update!(reminder_last_sent_at: Time.zone.now, reminder_set_at: nil) end - scope :with_reminders, -> do - where("reminder_at IS NOT NULL") - end + scope :with_reminders, -> { where("reminder_at IS NOT NULL") } - scope :pending_reminders, ->(before_time = Time.now.utc) do - with_reminders.where("reminder_at <= ?", before_time).where(reminder_last_sent_at: nil) - end + scope :pending_reminders, + ->(before_time = Time.now.utc) { + with_reminders.where("reminder_at <= ?", before_time).where(reminder_last_sent_at: nil) + } - scope :pending_reminders_for_user, ->(user) do - pending_reminders.where(user: user) - end + scope :pending_reminders_for_user, ->(user) { pending_reminders.where(user: user) } - scope :for_user_in_topic, ->(user_id, topic_id) { - joins("LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'") - .joins("LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR - (topics.id = posts.topic_id)") - .where( - "bookmarks.user_id = :user_id AND (topics.id = :topic_id OR posts.topic_id = :topic_id) + scope :for_user_in_topic, + ->(user_id, topic_id) { + joins( + "LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'", + ).joins( + "LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR + (topics.id = posts.topic_id)", + ).where( + "bookmarks.user_id = :user_id AND (topics.id = :topic_id OR posts.topic_id = :topic_id) AND posts.deleted_at IS NULL AND topics.deleted_at IS NULL", - user_id: user_id, topic_id: topic_id - ) - } + user_id: user_id, + topic_id: topic_id, + ) + } def self.count_per_day(opts = nil) opts ||= {} - result = where('bookmarks.created_at >= ?', opts[:start_date] || (opts[:since_days_ago] || 30).days.ago) + result = + where( + "bookmarks.created_at >= ?", + opts[:start_date] || (opts[:since_days_ago] || 30).days.ago, + ) - if opts[:end_date] - result = result.where('bookmarks.created_at <= ?', opts[:end_date]) - end + result = result.where("bookmarks.created_at <= ?", opts[:end_date]) if opts[:end_date] if opts[:category_id] - result = result - .joins("LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'") - .joins("LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR (topics.id = posts.topic_id)") - .where("topics.deleted_at IS NULL AND posts.deleted_at IS NULL") - .merge(Topic.in_category_and_subcategories(opts[:category_id])) + result = + result + .joins( + "LEFT JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'", + ) + .joins( + "LEFT JOIN topics ON (topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic') OR (topics.id = posts.topic_id)", + ) + .where("topics.deleted_at IS NULL AND posts.deleted_at IS NULL") + .merge(Topic.in_category_and_subcategories(opts[:category_id])) end - result.group('date(bookmarks.created_at)') - .order('date(bookmarks.created_at)') - .count + result.group("date(bookmarks.created_at)").order("date(bookmarks.created_at)").count end ## diff --git a/app/models/category.rb b/app/models/category.rb index c68ad7f1c9e..fc721f09405 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class Category < ActiveRecord::Base - RESERVED_SLUGS = [ - 'none' - ] + RESERVED_SLUGS = ["none"] self.ignored_columns = [ :suppress_from_latest, # TODO(2020-11-18): remove @@ -18,9 +16,9 @@ class Category < ActiveRecord::Base include AnonCacheInvalidator include HasDestroyedWebHook - REQUIRE_TOPIC_APPROVAL = 'require_topic_approval' - REQUIRE_REPLY_APPROVAL = 'require_reply_approval' - NUM_AUTO_BUMP_DAILY = 'num_auto_bump_daily' + REQUIRE_TOPIC_APPROVAL = "require_topic_approval" + REQUIRE_REPLY_APPROVAL = "require_reply_approval" + NUM_AUTO_BUMP_DAILY = "num_auto_bump_daily" register_custom_field_type(REQUIRE_TOPIC_APPROVAL, :boolean) register_custom_field_type(REQUIRE_REPLY_APPROVAL, :boolean) @@ -28,9 +26,9 @@ class Category < ActiveRecord::Base belongs_to :topic belongs_to :topic_only_relative_url, - -> { select "id, title, slug" }, - class_name: "Topic", - foreign_key: "topic_id" + -> { select "id, title, slug" }, + class_name: "Topic", + foreign_key: "topic_id" belongs_to :user belongs_to :latest_post, class_name: "Post" @@ -52,10 +50,20 @@ class Category < ActiveRecord::Base validates :user_id, presence: true - validates :name, if: Proc.new { |c| c.new_record? || c.will_save_change_to_name? || c.will_save_change_to_parent_category_id? }, - presence: true, - uniqueness: { scope: :parent_category_id, case_sensitive: false }, - length: { in: 1..50 } + validates :name, + if: + Proc.new { |c| + c.new_record? || c.will_save_change_to_name? || + c.will_save_change_to_parent_category_id? + }, + presence: true, + uniqueness: { + scope: :parent_category_id, + case_sensitive: false, + }, + length: { + in: 1..50, + } validates :num_featured_topics, numericality: { only_integer: true, greater_than: 0 } validates :search_priority, inclusion: { in: Searchable::PRIORITIES.values } @@ -65,7 +73,12 @@ class Category < ActiveRecord::Base validate :ensure_slug validate :permissions_compatibility_validator - validates :auto_close_hours, numericality: { greater_than: 0, less_than_or_equal_to: 87600 }, allow_nil: true + validates :auto_close_hours, + numericality: { + greater_than: 0, + less_than_or_equal_to: 87_600, + }, + allow_nil: true validates :slug, exclusion: { in: RESERVED_SLUGS } after_create :create_category_definition @@ -84,7 +97,8 @@ class Category < ActiveRecord::Base after_save :update_reviewables after_save do - if saved_change_to_uploaded_logo_id? || saved_change_to_uploaded_logo_dark_id? || saved_change_to_uploaded_background_id? + if saved_change_to_uploaded_logo_id? || saved_change_to_uploaded_logo_dark_id? || + saved_change_to_uploaded_background_id? upload_ids = [self.uploaded_logo_id, self.uploaded_logo_dark_id, self.uploaded_background_id] UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) end @@ -106,8 +120,8 @@ class Category < ActiveRecord::Base after_save_commit :index_search - belongs_to :parent_category, class_name: 'Category' - has_many :subcategories, class_name: 'Category', foreign_key: 'parent_category_id' + belongs_to :parent_category, class_name: "Category" + has_many :subcategories, class_name: "Category", foreign_key: "parent_category_id" has_many :category_tags, dependent: :destroy has_many :tags, through: :category_tags @@ -117,65 +131,74 @@ class Category < ActiveRecord::Base has_many :category_required_tag_groups, -> { order(order: :asc) }, dependent: :destroy has_many :sidebar_section_links, as: :linkable, dependent: :delete_all - belongs_to :reviewable_by_group, class_name: 'Group' + belongs_to :reviewable_by_group, class_name: "Group" - scope :latest, -> { order('topic_count DESC') } + scope :latest, -> { order("topic_count DESC") } - scope :secured, -> (guardian = nil) { - ids = guardian.secure_category_ids if guardian + scope :secured, + ->(guardian = nil) { + ids = guardian.secure_category_ids if guardian - if ids.present? - where("NOT categories.read_restricted OR categories.id IN (:cats)", cats: ids).references(:categories) - else - where("NOT categories.read_restricted").references(:categories) - end - } + if ids.present? + where( + "NOT categories.read_restricted OR categories.id IN (:cats)", + cats: ids, + ).references(:categories) + else + where("NOT categories.read_restricted").references(:categories) + end + } TOPIC_CREATION_PERMISSIONS ||= [:full] - POST_CREATION_PERMISSIONS ||= [:create_post, :full] + POST_CREATION_PERMISSIONS ||= %i[create_post full] - scope :topic_create_allowed, -> (guardian) do + scope :topic_create_allowed, + ->(guardian) { + scoped = scoped_to_permissions(guardian, TOPIC_CREATION_PERMISSIONS) - scoped = scoped_to_permissions(guardian, TOPIC_CREATION_PERMISSIONS) + if !SiteSetting.allow_uncategorized_topics && !guardian.is_staff? + scoped = scoped.where.not(id: SiteSetting.uncategorized_category_id) + end - if !SiteSetting.allow_uncategorized_topics && !guardian.is_staff? - scoped = scoped.where.not(id: SiteSetting.uncategorized_category_id) - end + scoped + } - scoped - end + scope :post_create_allowed, + ->(guardian) { scoped_to_permissions(guardian, POST_CREATION_PERMISSIONS) } - scope :post_create_allowed, -> (guardian) { scoped_to_permissions(guardian, POST_CREATION_PERMISSIONS) } - - delegate :post_template, to: 'self.class' + delegate :post_template, to: "self.class" # permission is just used by serialization # we may consider wrapping this in another spot - attr_accessor :displayable_topics, :permission, :subcategory_ids, :subcategory_list, :notification_level, :has_children + attr_accessor :displayable_topics, + :permission, + :subcategory_ids, + :subcategory_list, + :notification_level, + :has_children # Allows us to skip creating the category definition topic in tests. attr_accessor :skip_category_definition - @topic_id_cache = DistributedCache.new('category_topic_ids') + @topic_id_cache = DistributedCache.new("category_topic_ids") def self.topic_ids - @topic_id_cache['ids'] || reset_topic_ids_cache + @topic_id_cache["ids"] || reset_topic_ids_cache end def self.reset_topic_ids_cache - @topic_id_cache['ids'] = Set.new(Category.pluck(:topic_id).compact) + @topic_id_cache["ids"] = Set.new(Category.pluck(:topic_id).compact) end def reset_topic_ids_cache Category.reset_topic_ids_cache end - @@subcategory_ids = DistributedCache.new('subcategory_ids') + @@subcategory_ids = DistributedCache.new("subcategory_ids") def self.subcategory_ids(category_id) - @@subcategory_ids[category_id] ||= - begin - sql = <<~SQL + @@subcategory_ids[category_id] ||= begin + sql = <<~SQL WITH RECURSIVE subcategories AS ( SELECT :category_id id, 1 depth UNION @@ -186,12 +209,12 @@ class Category < ActiveRecord::Base ) SELECT id FROM subcategories SQL - DB.query_single( - sql, - category_id: category_id, - max_category_nesting: SiteSetting.max_category_nesting - ) - end + DB.query_single( + sql, + category_id: category_id, + max_category_nesting: SiteSetting.max_category_nesting, + ) + end end def self.clear_subcategory_ids @@ -217,7 +240,8 @@ class Category < ActiveRecord::Base end else permissions = permission_types.map { |p| CategoryGroup.permission_types[p] } - where("(:staged AND LENGTH(COALESCE(email_in, '')) > 0 AND email_in_allow_strangers) + where( + "(:staged AND LENGTH(COALESCE(email_in, '')) > 0 AND email_in_allow_strangers) OR categories.id NOT IN (SELECT category_id FROM category_groups) OR categories.id IN ( SELECT category_id @@ -228,16 +252,21 @@ class Category < ActiveRecord::Base staged: guardian.is_staged?, permissions: permissions, user_id: guardian.user.id, - everyone: Group::AUTO_GROUPS[:everyone]) + everyone: Group::AUTO_GROUPS[:everyone], + ) end end def self.update_stats - topics_with_post_count = Topic - .select("topics.category_id, COUNT(*) topic_count, SUM(topics.posts_count) post_count") - .where("topics.id NOT IN (select cc.topic_id from categories cc WHERE topic_id IS NOT NULL)") - .group("topics.category_id") - .visible.to_sql + topics_with_post_count = + Topic + .select("topics.category_id, COUNT(*) topic_count, SUM(topics.posts_count) post_count") + .where( + "topics.id NOT IN (select cc.topic_id from categories cc WHERE topic_id IS NOT NULL)", + ) + .group("topics.category_id") + .visible + .to_sql DB.exec <<~SQL UPDATE categories c @@ -265,29 +294,31 @@ class Category < ActiveRecord::Base Category.all.each do |c| topics = c.topics.visible - topics = topics.where(['topics.id <> ?', c.topic_id]) if c.topic_id - c.topics_year = topics.created_since(1.year.ago).count + topics = topics.where(["topics.id <> ?", c.topic_id]) if c.topic_id + c.topics_year = topics.created_since(1.year.ago).count c.topics_month = topics.created_since(1.month.ago).count - c.topics_week = topics.created_since(1.week.ago).count - c.topics_day = topics.created_since(1.day.ago).count + c.topics_week = topics.created_since(1.week.ago).count + c.topics_day = topics.created_since(1.day.ago).count posts = c.visible_posts - c.posts_year = posts.created_since(1.year.ago).count + c.posts_year = posts.created_since(1.year.ago).count c.posts_month = posts.created_since(1.month.ago).count - c.posts_week = posts.created_since(1.week.ago).count - c.posts_day = posts.created_since(1.day.ago).count + c.posts_week = posts.created_since(1.week.ago).count + c.posts_day = posts.created_since(1.day.ago).count c.save if c.changed? end end def visible_posts - query = Post.joins(:topic) - .where(['topics.category_id = ?', self.id]) - .where('topics.visible = true') - .where('posts.deleted_at IS NULL') - .where('posts.user_deleted = false') - self.topic_id ? query.where(['topics.id <> ?', self.topic_id]) : query + query = + Post + .joins(:topic) + .where(["topics.category_id = ?", self.id]) + .where("topics.visible = true") + .where("posts.deleted_at IS NULL") + .where("posts.user_deleted = false") + self.topic_id ? query.where(["topics.id <> ?", self.topic_id]) : query end # Internal: Generate the text of post prompting to enter category description. @@ -299,7 +330,13 @@ class Category < ActiveRecord::Base return if skip_category_definition Topic.transaction do - t = Topic.new(title: I18n.t("category.topic_prefix", category: name), user: user, pinned_at: Time.now, category_id: id) + t = + Topic.new( + title: I18n.t("category.topic_prefix", category: name), + user: user, + pinned_at: Time.now, + category_id: id, + ) t.skip_callbacks = true t.ignore_category_auto_close = true t.delete_topic_timer(TopicTimer.types[:close]) @@ -318,9 +355,7 @@ class Category < ActiveRecord::Base end def clear_related_site_settings - if self.id == SiteSetting.general_category_id - SiteSetting.general_category_id = -1 - end + SiteSetting.general_category_id = -1 if self.id == SiteSetting.general_category_id end def topic_url @@ -345,9 +380,7 @@ class Category < ActiveRecord::Base return nil unless self.description @@cache_excerpt ||= LruRedux::ThreadSafeCache.new(1000) - @@cache_excerpt.getset(self.description) do - PrettyText.excerpt(description, 300) - end + @@cache_excerpt.getset(self.description) { PrettyText.excerpt(description, 300) } end def access_category_via_group @@ -370,20 +403,20 @@ class Category < ActiveRecord::Base if slug.present? # if we don't unescape it first we strip the % from the encoded version - slug = SiteSetting.slug_generation_method == 'encoded' ? CGI.unescape(self.slug) : self.slug - self.slug = Slug.for(slug, '', method: :encoded) + slug = SiteSetting.slug_generation_method == "encoded" ? CGI.unescape(self.slug) : self.slug + self.slug = Slug.for(slug, "", method: :encoded) if self.slug.blank? errors.add(:slug, :invalid) - elsif SiteSetting.slug_generation_method == 'ascii' && !CGI.unescape(self.slug).ascii_only? + elsif SiteSetting.slug_generation_method == "ascii" && !CGI.unescape(self.slug).ascii_only? errors.add(:slug, I18n.t("category.errors.slug_contains_non_ascii_chars")) elsif duplicate_slug? errors.add(:slug, I18n.t("category.errors.is_already_in_use")) end else # auto slug - self.slug = Slug.for(name, '') - self.slug = '' if duplicate_slug? + self.slug = Slug.for(name, "") + self.slug = "" if duplicate_slug? end # only allow to use category itself id. @@ -403,30 +436,27 @@ class Category < ActiveRecord::Base if group_ids.present? MessageBus.publish( - '/categories', + "/categories", { categories: ActiveModel::ArraySerializer.new([self]).as_json }, - group_ids: group_ids + group_ids: group_ids, ) end else MessageBus.publish( - '/categories', - { categories: ActiveModel::ArraySerializer.new([self]).as_json } + "/categories", + { categories: ActiveModel::ArraySerializer.new([self]).as_json }, ) end end def remove_site_settings SiteSetting.all_settings.each do |s| - if s[:type] == 'category' && s[:value].to_i == self.id - SiteSetting.set(s[:setting], '') - end + SiteSetting.set(s[:setting], "") if s[:type] == "category" && s[:value].to_i == self.id end - end def publish_category_deletion - MessageBus.publish('/categories', deleted_categories: [self.id]) + MessageBus.publish("/categories", deleted_categories: [self.id]) end # This is used in a validation so has to produce accurate results before the @@ -492,7 +522,9 @@ class Category < ActiveRecord::Base errors.add(:base, I18n.t("category.errors.self_parent")) if parent_category_id == id total_depth = height_of_ancestors + 1 + depth_of_descendants - errors.add(:base, I18n.t("category.errors.depth")) if total_depth > SiteSetting.max_category_nesting + if total_depth > SiteSetting.max_category_nesting + errors.add(:base, I18n.t("category.errors.depth")) + end end end @@ -500,9 +532,7 @@ class Category < ActiveRecord::Base # this line bothers me, destroying in AR can not seem to be queued, thinking of extending it category_groups.destroy_all unless new_record? ids = Group.where(name: names.split(",")).pluck(:id) - ids.each do |id| - category_groups.build(group_id: id) - end + ids.each { |id| category_groups.build(group_id: id) } end # will reset permission on a topic to a particular @@ -527,11 +557,13 @@ class Category < ActiveRecord::Base def permissions_params hash = {} - category_groups.includes(:group).each do |category_group| - if category_group.group.present? - hash[category_group.group_name] = category_group.permission_type + category_groups + .includes(:group) + .each do |category_group| + if category_group.group.present? + hash[category_group.group_name] = category_group.permission_type + end end - end hash end @@ -551,17 +583,16 @@ class Category < ActiveRecord::Base everyone = Group::AUTO_GROUPS[:everyone] full = CategoryGroup.permission_types[:full] - mapped = permissions.map do |group, permission| - group_id = Group.group_id_from_param(group) - permission = CategoryGroup.permission_types[permission] unless permission.is_a?(Integer) + mapped = + permissions.map do |group, permission| + group_id = Group.group_id_from_param(group) + permission = CategoryGroup.permission_types[permission] unless permission.is_a?(Integer) - [group_id, permission] - end + [group_id, permission] + end mapped.each do |group, permission| - if group == everyone && permission == full - return [false, []] - end + return false, [] if group == everyone && permission == full read_restricted = false if group == everyone end @@ -587,7 +618,7 @@ class Category < ActiveRecord::Base def auto_bump_limiter return nil if num_auto_bump_daily.to_i == 0 - RateLimiter.new(nil, "auto_bump_limit_#{self.id}", 1, 86400 / num_auto_bump_daily.to_i) + RateLimiter.new(nil, "auto_bump_limit_#{self.id}", 1, 86_400 / num_auto_bump_daily.to_i) end def clear_auto_bump_cache! @@ -597,10 +628,11 @@ class Category < ActiveRecord::Base def self.auto_bump_topic! bumped = false - auto_bumps = CategoryCustomField - .where(name: Category::NUM_AUTO_BUMP_DAILY) - .where('NULLIF(value, \'\')::int > 0') - .pluck(:category_id) + auto_bumps = + CategoryCustomField + .where(name: Category::NUM_AUTO_BUMP_DAILY) + .where('NULLIF(value, \'\')::int > 0') + .pluck(:category_id) if (auto_bumps.length > 0) auto_bumps.shuffle.each do |category_id| @@ -625,23 +657,20 @@ class Category < ActiveRecord::Base relation = Topic - if filters.length > 0 - filters.each do |filter| - relation = filter.call(relation) - end - end + filters.each { |filter| relation = filter.call(relation) } if filters.length > 0 - topic = relation - .visible - .listable_topics - .exclude_scheduled_bump_topics - .where(category_id: self.id) - .where('id <> ?', self.topic_id) - .where('bumped_at < ?', 1.day.ago) - .where('pinned_at IS NULL AND NOT closed AND NOT archived') - .order('bumped_at ASC') - .limit(1) - .first + topic = + relation + .visible + .listable_topics + .exclude_scheduled_bump_topics + .where(category_id: self.id) + .where("id <> ?", self.topic_id) + .where("bumped_at < ?", 1.day.ago) + .where("pinned_at IS NULL AND NOT closed AND NOT archived") + .order("bumped_at ASC") + .limit(1) + .first if topic topic.add_small_action(Discourse.system_user, "autobumped", nil, bump: true) @@ -650,7 +679,6 @@ class Category < ActiveRecord::Base else false end - end def allowed_tags=(tag_names_arg) @@ -662,13 +690,20 @@ class Category < ActiveRecord::Base end def required_tag_groups=(required_groups) - map = Array(required_groups).map.with_index { |rg, i| [rg["name"], { min_count: rg["min_count"].to_i, order: i }] }.to_h + map = + Array(required_groups) + .map + .with_index { |rg, i| [rg["name"], { min_count: rg["min_count"].to_i, order: i }] } + .to_h tag_groups = TagGroup.where(name: map.keys) - self.category_required_tag_groups = tag_groups.map do |tag_group| - attrs = map[tag_group.name] - CategoryRequiredTagGroup.new(tag_group: tag_group, **attrs) - end.sort_by(&:order) + self.category_required_tag_groups = + tag_groups + .map do |tag_group| + attrs = map[tag_group.name] + CategoryRequiredTagGroup.new(tag_group: tag_group, **attrs) + end + .sort_by(&:order) end def downcase_email @@ -677,17 +712,32 @@ class Category < ActiveRecord::Base def email_in_validator return if self.email_in.blank? - email_in.split("|").each do |email| - - escaped = Rack::Utils.escape_html(email) - if !Email.is_valid?(email) - self.errors.add(:base, I18n.t('category.errors.invalid_email_in', email: escaped)) - elsif group = Group.find_by_email(email) - self.errors.add(:base, I18n.t('category.errors.email_already_used_in_group', email: escaped, group_name: Rack::Utils.escape_html(group.name))) - elsif category = Category.where.not(id: self.id).find_by_email(email) - self.errors.add(:base, I18n.t('category.errors.email_already_used_in_category', email: escaped, category_name: Rack::Utils.escape_html(category.name))) + email_in + .split("|") + .each do |email| + escaped = Rack::Utils.escape_html(email) + if !Email.is_valid?(email) + self.errors.add(:base, I18n.t("category.errors.invalid_email_in", email: escaped)) + elsif group = Group.find_by_email(email) + self.errors.add( + :base, + I18n.t( + "category.errors.email_already_used_in_group", + email: escaped, + group_name: Rack::Utils.escape_html(group.name), + ), + ) + elsif category = Category.where.not(id: self.id).find_by_email(email) + self.errors.add( + :base, + I18n.t( + "category.errors.email_already_used_in_category", + email: escaped, + category_name: Rack::Utils.escape_html(category.name), + ), + ) + end end - end end def downcase_name @@ -699,42 +749,45 @@ class Category < ActiveRecord::Base end def secure_group_ids - if self.read_restricted? - groups.pluck("groups.id") - end + groups.pluck("groups.id") if self.read_restricted? end def update_latest - latest_post_id = Post - .order("posts.created_at desc") - .where("NOT hidden") - .joins("join topics on topics.id = topic_id") - .where("topics.category_id = :id", id: self.id) - .limit(1) - .pluck("posts.id") - .first + latest_post_id = + Post + .order("posts.created_at desc") + .where("NOT hidden") + .joins("join topics on topics.id = topic_id") + .where("topics.category_id = :id", id: self.id) + .limit(1) + .pluck("posts.id") + .first - latest_topic_id = Topic - .order("topics.created_at desc") - .where("visible") - .where("topics.category_id = :id", id: self.id) - .limit(1) - .pluck("topics.id") - .first + latest_topic_id = + Topic + .order("topics.created_at desc") + .where("visible") + .where("topics.category_id = :id", id: self.id) + .limit(1) + .pluck("topics.id") + .first self.update(latest_topic_id: latest_topic_id, latest_post_id: latest_post_id) end def self.query_parent_category(parent_slug) - encoded_parent_slug = CGI.escape(parent_slug) if SiteSetting.slug_generation_method == 'encoded' - self.where(slug: (encoded_parent_slug || parent_slug), parent_category_id: nil).pluck_first(:id) || - self.where(id: parent_slug.to_i).pluck_first(:id) + encoded_parent_slug = CGI.escape(parent_slug) if SiteSetting.slug_generation_method == "encoded" + self.where(slug: (encoded_parent_slug || parent_slug), parent_category_id: nil).pluck_first( + :id, + ) || self.where(id: parent_slug.to_i).pluck_first(:id) end def self.query_category(slug_or_id, parent_category_id) - encoded_slug_or_id = CGI.escape(slug_or_id) if SiteSetting.slug_generation_method == 'encoded' - self.where(slug: (encoded_slug_or_id || slug_or_id), parent_category_id: parent_category_id).first || - self.where(id: slug_or_id.to_i, parent_category_id: parent_category_id).first + encoded_slug_or_id = CGI.escape(slug_or_id) if SiteSetting.slug_generation_method == "encoded" + self.where( + slug: (encoded_slug_or_id || slug_or_id), + parent_category_id: parent_category_id, + ).first || self.where(id: slug_or_id.to_i, parent_category_id: parent_category_id).first end def self.find_by_email(email) @@ -772,12 +825,16 @@ class Category < ActiveRecord::Base def url @@url_cache.defer_get_set(self.id) do - "#{Discourse.base_path}/c/#{slug_path.join('/')}/#{self.id}" + "#{Discourse.base_path}/c/#{slug_path.join("/")}/#{self.id}" end end def url_with_id - Discourse.deprecate("Category#url_with_id is deprecated. Use `Category#url` instead.", output_in_test: true, drop_from: '2.9.0') + Discourse.deprecate( + "Category#url_with_id is deprecated. Use `Category#url` instead.", + output_in_test: true, + drop_from: "2.9.0", + ) url end @@ -795,7 +852,7 @@ class Category < ActiveRecord::Base old_slug = saved_changes.transform_values(&:first)["slug"] url = +"#{Discourse.base_path}/c" - url << "/#{parent_category.slug_path.join('/')}" if parent_category_id + url << "/#{parent_category.slug_path.join("/")}" if parent_category_id url << "/#{old_slug}/#{id}" url = Permalink.normalize_url(url) @@ -807,7 +864,7 @@ class Category < ActiveRecord::Base end def delete_category_permalink - permalink = Permalink.find_by_url("c/#{slug_path.join('/')}") + permalink = Permalink.find_by_url("c/#{slug_path.join("/")}") permalink.destroy if permalink end @@ -816,7 +873,8 @@ class Category < ActiveRecord::Base end def index_search - Jobs.enqueue(:index_category_for_search, + Jobs.enqueue( + :index_category_for_search, category_id: self.id, force: saved_change_to_attribute?(:name), ) @@ -832,9 +890,7 @@ class Category < ActiveRecord::Base return nil if slug_path.empty? return nil if slug_path.size > SiteSetting.max_category_nesting - slug_path.map! do |slug| - CGI.escape(slug.downcase) - end + slug_path.map! { |slug| CGI.escape(slug.downcase) } query = slug_path.inject(nil) do |parent_id, slug| @@ -871,11 +927,7 @@ class Category < ActiveRecord::Base subcategory_list_style.end_with?("with_featured_topics") end - %i{ - category_created - category_updated - category_destroyed - }.each do |event| + %i[category_created category_updated category_destroyed].each do |event| define_method("trigger_#{event}_event") do DiscourseEvent.trigger(event, self) true @@ -888,10 +940,17 @@ class Category < ActiveRecord::Base return if parent_category.category_groups.empty? parent_permissions = parent_category.category_groups.pluck(:group_id, :permission_type) - child_permissions = @permissions.empty? ? [[Group[:everyone].id, CategoryGroup.permission_types[:full]]] : @permissions + child_permissions = + ( + if @permissions.empty? + [[Group[:everyone].id, CategoryGroup.permission_types[:full]]] + else + @permissions + end + ) check_permissions_compatibility(parent_permissions, child_permissions) - # when saving parent category + # when saving parent category elsif @permissions && subcategories.present? return if @permissions.empty? @@ -903,7 +962,6 @@ class Category < ActiveRecord::Base end def self.ensure_consistency! - sql = <<~SQL SELECT t.id FROM topics t JOIN categories c ON c.topic_id = t.id @@ -911,9 +969,7 @@ class Category < ActiveRecord::Base WHERE p.id IS NULL SQL - DB.query_single(sql).each do |id| - Topic.with_deleted.find_by(id: id).destroy! - end + DB.query_single(sql).each { |id| Topic.with_deleted.find_by(id: id).destroy! } sql = <<~SQL UPDATE categories c @@ -928,12 +984,10 @@ class Category < ActiveRecord::Base DB.exec(sql) Category - .joins('LEFT JOIN topics ON categories.topic_id = topics.id AND topics.deleted_at IS NULL') - .where('categories.id <> ?', SiteSetting.uncategorized_category_id) + .joins("LEFT JOIN topics ON categories.topic_id = topics.id AND topics.deleted_at IS NULL") + .where("categories.id <> ?", SiteSetting.uncategorized_category_id) .where(topics: { id: nil }) - .find_each do |category| - category.create_category_definition - end + .find_each { |category| category.create_category_definition } end def slug_path @@ -947,16 +1001,20 @@ class Category < ActiveRecord::Base end def cannot_delete_reason - return I18n.t('category.cannot_delete.uncategorized') if self.uncategorized? - return I18n.t('category.cannot_delete.has_subcategories') if self.has_children? + return I18n.t("category.cannot_delete.uncategorized") if self.uncategorized? + return I18n.t("category.cannot_delete.has_subcategories") if self.has_children? if self.topic_count != 0 - oldest_topic = self.topics.where.not(id: self.topic_id).order('created_at ASC').limit(1).first + oldest_topic = self.topics.where.not(id: self.topic_id).order("created_at ASC").limit(1).first if oldest_topic - I18n.t('category.cannot_delete.topic_exists', count: self.topic_count, topic_link: "#{CGI.escapeHTML(oldest_topic.title)}") + I18n.t( + "category.cannot_delete.topic_exists", + count: self.topic_count, + topic_link: "#{CGI.escapeHTML(oldest_topic.title)}", + ) else # This is a weird case, probably indicating a bug. - I18n.t('category.cannot_delete.topic_exists_no_oldest', count: self.topic_count) + I18n.t("category.cannot_delete.topic_exists_no_oldest", count: self.topic_count) end end end @@ -989,8 +1047,7 @@ class Category < ActiveRecord::Base everyone = Group[:everyone].id full = CategoryGroup.permission_types[:full] - result = - DB.query(<<-SQL, id: id, everyone: everyone, full: full) + result = DB.query(<<-SQL, id: id, everyone: everyone, full: full) SELECT category_groups.group_id, category_groups.permission_type FROM categories, category_groups WHERE categories.parent_category_id = :id diff --git a/app/models/category_featured_topic.rb b/app/models/category_featured_topic.rb index 0e4a53ef551..1602dc7ad09 100644 --- a/app/models/category_featured_topic.rb +++ b/app/models/category_featured_topic.rb @@ -4,38 +4,40 @@ class CategoryFeaturedTopic < ActiveRecord::Base belongs_to :category belongs_to :topic - NEXT_CATEGORY_ID_KEY = 'category-featured-topic:next-category-id' + NEXT_CATEGORY_ID_KEY = "category-featured-topic:next-category-id" DEFAULT_BATCH_SIZE = 100 # Populates the category featured topics. def self.feature_topics(batched: false, batch_size: nil) current = {} - CategoryFeaturedTopic.select(:topic_id, :category_id).order(:rank).each do |f| - (current[f.category_id] ||= []) << f.topic_id - end + CategoryFeaturedTopic + .select(:topic_id, :category_id) + .order(:rank) + .each { |f| (current[f.category_id] ||= []) << f.topic_id } batch_size ||= DEFAULT_BATCH_SIZE next_category_id = batched ? Discourse.redis.get(NEXT_CATEGORY_ID_KEY).to_i : 0 - categories = Category.select(:id, :topic_id, :num_featured_topics) - .where('id >= ?', next_category_id) - .order('id ASC') - .limit(batch_size) - .to_a + categories = + Category + .select(:id, :topic_id, :num_featured_topics) + .where("id >= ?", next_category_id) + .order("id ASC") + .limit(batch_size) + .to_a if batched if categories.length == batch_size - next_id = Category.where('id > ?', categories.last.id).order('id asc').limit(1).pluck(:id)[0] + next_id = + Category.where("id > ?", categories.last.id).order("id asc").limit(1).pluck(:id)[0] next_id ? Discourse.redis.setex(NEXT_CATEGORY_ID_KEY, 1.day, next_id) : clear_batch! else clear_batch! end end - categories.each do |c| - CategoryFeaturedTopic.feature_topics_for(c, current[c.id] || []) - end + categories.each { |c| CategoryFeaturedTopic.feature_topics_for(c, current[c.id] || []) } end def self.clear_batch! @@ -49,7 +51,7 @@ class CategoryFeaturedTopic < ActiveRecord::Base per_page: c.num_featured_topics, except_topic_ids: [c.topic_id], visible: true, - no_definitions: true + no_definitions: true, } # It may seem a bit odd that we are running 2 queries here, when admin diff --git a/app/models/category_group.rb b/app/models/category_group.rb index a4f5b51ef9d..5f0b34cbdcb 100644 --- a/app/models/category_group.rb +++ b/app/models/category_group.rb @@ -9,7 +9,6 @@ class CategoryGroup < ActiveRecord::Base def self.permission_types @permission_types ||= Enum.new(full: 1, create_post: 2, readonly: 3) end - end # == Schema Information diff --git a/app/models/category_list.rb b/app/models/category_list.rb index 21ebb279c82..38a55042a78 100644 --- a/app/models/category_list.rb +++ b/app/models/category_list.rb @@ -6,8 +6,7 @@ class CategoryList cattr_accessor :preloaded_topic_custom_fields self.preloaded_topic_custom_fields = Set.new - attr_accessor :categories, - :uncategorized + attr_accessor :categories, :uncategorized def initialize(guardian = nil, options = {}) @guardian = guardian || Guardian.new @@ -28,13 +27,9 @@ class CategoryList displayable_topics.compact! if displayable_topics.present? - Topic.preload_custom_fields( - displayable_topics, - preloaded_topic_custom_fields - ) + Topic.preload_custom_fields(displayable_topics, preloaded_topic_custom_fields) end end - end def preload_key @@ -46,11 +41,12 @@ class CategoryList categories.order(:position, :id) else allowed_category_ids = categories.pluck(:id) << nil # `nil` is necessary to include categories without any associated topics - categories.left_outer_joins(:featured_topics) + categories + .left_outer_joins(:featured_topics) .where(topics: { category_id: allowed_category_ids }) - .group('categories.id') + .group("categories.id") .order("max(topics.bumped_at) DESC NULLS LAST") - .order('categories.id ASC') + .order("categories.id ASC") end end @@ -60,22 +56,27 @@ class CategoryList @topics_by_id = {} @topics_by_category_id = {} - category_featured_topics = CategoryFeaturedTopic.select([:category_id, :topic_id]).order(:rank) + category_featured_topics = CategoryFeaturedTopic.select(%i[category_id topic_id]).order(:rank) - @all_topics = Topic - .where(id: category_featured_topics.map(&:topic_id)) - .includes( + @all_topics = + Topic.where(id: category_featured_topics.map(&:topic_id)).includes( :shared_draft, :category, - { topic_thumbnails: [:optimized_image, :upload] } + { topic_thumbnails: %i[optimized_image upload] }, ) - @all_topics = @all_topics.joins(:tags).where(tags: { name: @options[:tag] }) if @options[:tag].present? + @all_topics = @all_topics.joins(:tags).where(tags: { name: @options[:tag] }) if @options[ + :tag + ].present? if @guardian.authenticated? - @all_topics = @all_topics - .joins("LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id = #{@guardian.user.id.to_i}") - .where('COALESCE(tu.notification_level,1) > :muted', muted: TopicUser.notification_levels[:muted]) + @all_topics = + @all_topics.joins( + "LEFT JOIN topic_users tu ON topics.id = tu.topic_id AND tu.user_id = #{@guardian.user.id.to_i}", + ).where( + "COALESCE(tu.notification_level,1) > :muted", + muted: TopicUser.notification_levels[:muted], + ) end @all_topics = TopicQuery.remove_muted_tags(@all_topics, @guardian.user).includes(:last_poster) @@ -94,7 +95,8 @@ class CategoryList def dismissed_topic?(topic) if @guardian.current_user - @dismissed_topic_users_lookup ||= DismissedTopicUser.lookup_for(@guardian.current_user, @all_topics) + @dismissed_topic_users_lookup ||= + DismissedTopicUser.lookup_for(@guardian.current_user, @all_topics) @dismissed_topic_users_lookup.include?(topic.id) else false @@ -102,15 +104,20 @@ class CategoryList end def find_categories - @categories = Category.includes( - :uploaded_background, - :uploaded_logo, - :uploaded_logo_dark, - :topic_only_relative_url, - subcategories: [:topic_only_relative_url] - ).secured(@guardian) + @categories = + Category.includes( + :uploaded_background, + :uploaded_logo, + :uploaded_logo_dark, + :topic_only_relative_url, + subcategories: [:topic_only_relative_url], + ).secured(@guardian) - @categories = @categories.where("categories.parent_category_id = ?", @options[:parent_category_id].to_i) if @options[:parent_category_id].present? + @categories = + @categories.where( + "categories.parent_category_id = ?", + @options[:parent_category_id].to_i, + ) if @options[:parent_category_id].present? @categories = self.class.order_categories(@categories) @@ -138,9 +145,7 @@ class CategoryList end @categories.each do |c| c.subcategory_ids = subcategory_ids[c.id] || [] - if include_subcategories - c.subcategory_list = subcategory_list[c.id] || [] - end + c.subcategory_list = subcategory_list[c.id] || [] if include_subcategories end @categories.delete_if { |c| to_delete.include?(c) } end @@ -149,7 +154,9 @@ class CategoryList categories_with_descendants.each do |category| category.notification_level = notification_levels[category.id] || default_notification_level - category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?(category.id) + category.permission = CategoryGroup.permission_types[:full] if allowed_topic_create.include?( + category.id, + ) category.has_children = category.subcategories.present? end @@ -193,9 +200,7 @@ class CategoryList c.displayable_topics.each do |t| unpinned << t if t.pinned_at && PinnedCheck.unpinned?(t, t.user_data) end - unless unpinned.empty? - c.displayable_topics = (c.displayable_topics - unpinned) + unpinned - end + c.displayable_topics = (c.displayable_topics - unpinned) + unpinned unless unpinned.empty? end end end @@ -217,9 +222,7 @@ class CategoryList return @categories_with_children if @categories_with_children && (categories == @categories) return nil if categories.nil? - result = categories.flat_map do |c| - [c, *categories_with_descendants(c.subcategory_list)] - end + result = categories.flat_map { |c| [c, *categories_with_descendants(c.subcategory_list)] } @categories_with_children = result if categories == @categories diff --git a/app/models/category_page_style.rb b/app/models/category_page_style.rb index cf6381d58e6..fe8c5d41978 100644 --- a/app/models/category_page_style.rb +++ b/app/models/category_page_style.rb @@ -3,26 +3,39 @@ require "enum_site_setting" class CategoryPageStyle < EnumSiteSetting - def self.valid_value?(val) values.any? { |v| v[:value].to_s == val.to_s } end def self.values @values ||= [ - { name: 'category_page_style.categories_only', value: 'categories_only' }, - { name: 'category_page_style.categories_with_featured_topics', value: 'categories_with_featured_topics' }, - { name: 'category_page_style.categories_and_latest_topics_created_date', value: 'categories_and_latest_topics_created_date' }, - { name: 'category_page_style.categories_and_latest_topics', value: 'categories_and_latest_topics' }, - { name: 'category_page_style.categories_and_top_topics', value: 'categories_and_top_topics' }, - { name: 'category_page_style.categories_boxes', value: 'categories_boxes' }, - { name: 'category_page_style.categories_boxes_with_topics', value: 'categories_boxes_with_topics' }, - { name: 'category_page_style.subcategories_with_featured_topics', value: 'subcategories_with_featured_topics' }, + { name: "category_page_style.categories_only", value: "categories_only" }, + { + name: "category_page_style.categories_with_featured_topics", + value: "categories_with_featured_topics", + }, + { + name: "category_page_style.categories_and_latest_topics_created_date", + value: "categories_and_latest_topics_created_date", + }, + { + name: "category_page_style.categories_and_latest_topics", + value: "categories_and_latest_topics", + }, + { name: "category_page_style.categories_and_top_topics", value: "categories_and_top_topics" }, + { name: "category_page_style.categories_boxes", value: "categories_boxes" }, + { + name: "category_page_style.categories_boxes_with_topics", + value: "categories_boxes_with_topics", + }, + { + name: "category_page_style.subcategories_with_featured_topics", + value: "subcategories_with_featured_topics", + }, ] end def self.translate_names? true end - end diff --git a/app/models/category_required_tag_group.rb b/app/models/category_required_tag_group.rb index 50d9ef6ab6a..695b838f81f 100644 --- a/app/models/category_required_tag_group.rb +++ b/app/models/category_required_tag_group.rb @@ -6,9 +6,7 @@ class CategoryRequiredTagGroup < ActiveRecord::Base validates :min_count, numericality: { only_integer: true, greater_than: 0 } - after_commit do - Site.clear_cache - end + after_commit { Site.clear_cache } end # == Schema Information diff --git a/app/models/category_tag.rb b/app/models/category_tag.rb index e9cba7c189e..dad98b922ba 100644 --- a/app/models/category_tag.rb +++ b/app/models/category_tag.rb @@ -4,9 +4,7 @@ class CategoryTag < ActiveRecord::Base belongs_to :category belongs_to :tag - after_commit do - Site.clear_cache - end + after_commit { Site.clear_cache } end # == Schema Information diff --git a/app/models/category_tag_group.rb b/app/models/category_tag_group.rb index ea27bc50c15..6122a00acf6 100644 --- a/app/models/category_tag_group.rb +++ b/app/models/category_tag_group.rb @@ -4,9 +4,7 @@ class CategoryTagGroup < ActiveRecord::Base belongs_to :category belongs_to :tag_group - after_commit do - Site.clear_cache - end + after_commit { Site.clear_cache } end # == Schema Information diff --git a/app/models/category_tag_stat.rb b/app/models/category_tag_stat.rb index 5f2b1410a1c..478aa069cc2 100644 --- a/app/models/category_tag_stat.rb +++ b/app/models/category_tag_stat.rb @@ -6,9 +6,10 @@ class CategoryTagStat < ActiveRecord::Base def self.topic_moved(topic, from_category_id, to_category_id) if from_category_id - self.where(tag_id: topic.tags.map(&:id), category_id: from_category_id) - .where('topic_count > 0') - .update_all('topic_count = topic_count - 1') + self + .where(tag_id: topic.tags.map(&:id), category_id: from_category_id) + .where("topic_count > 0") + .update_all("topic_count = topic_count - 1") end if to_category_id diff --git a/app/models/category_user.rb b/app/models/category_user.rb index b07ac73e713..4623567656f 100644 --- a/app/models/category_user.rb +++ b/app/models/category_user.rb @@ -23,27 +23,24 @@ class CategoryUser < ActiveRecord::Base changed = false # Update pre-existing category users - if category_ids.present? && CategoryUser - .where(user_id: user.id, category_id: category_ids) - .where.not(notification_level: level_num) - .update_all(notification_level: level_num) > 0 - + if category_ids.present? && + CategoryUser + .where(user_id: user.id, category_id: category_ids) + .where.not(notification_level: level_num) + .update_all(notification_level: level_num) > 0 changed = true end # Remove extraneous category users - if CategoryUser.where(user_id: user.id, notification_level: level_num) - .where.not(category_id: category_ids) - .delete_all > 0 - + if CategoryUser + .where(user_id: user.id, notification_level: level_num) + .where.not(category_id: category_ids) + .delete_all > 0 changed = true end if category_ids.present? - params = { - user_id: user.id, - level_num: level_num, - } + params = { user_id: user.id, level_num: level_num } sql = <<~SQL INSERT INTO category_users (user_id, category_id, notification_level) @@ -55,11 +52,8 @@ class CategoryUser < ActiveRecord::Base # into the query, plus it is a bit of a micro optimisation category_ids.each do |category_id| params[:category_id] = category_id - if DB.exec(sql, params) > 0 - changed = true - end + changed = true if DB.exec(sql, params) > 0 end - end if changed @@ -91,7 +85,6 @@ class CategoryUser < ActiveRecord::Base end def self.auto_track(opts = {}) - builder = DB.build <<~SQL UPDATE topic_users tu SET notification_level = :tracking, @@ -100,11 +93,13 @@ class CategoryUser < ActiveRecord::Base /*where*/ SQL - builder.where("tu.topic_id = t.id AND + builder.where( + "tu.topic_id = t.id AND cu.category_id = t.category_id AND cu.user_id = tu.user_id AND cu.notification_level = :tracking AND - tu.notification_level = :regular") + tu.notification_level = :regular", + ) if category_id = opts[:category_id] builder.where("t.category_id = :category_id", category_id: category_id) @@ -121,12 +116,11 @@ class CategoryUser < ActiveRecord::Base builder.exec( tracking: notification_levels[:tracking], regular: notification_levels[:regular], - auto_track_category: TopicUser.notification_reasons[:auto_track_category] + auto_track_category: TopicUser.notification_reasons[:auto_track_category], ) end def self.auto_watch(opts = {}) - builder = DB.build <<~SQL UPDATE topic_users tu SET notification_level = @@ -181,9 +175,8 @@ class CategoryUser < ActiveRecord::Base watching: notification_levels[:watching], tracking: notification_levels[:tracking], regular: notification_levels[:regular], - auto_watch_category: TopicUser.notification_reasons[:auto_watch_category] + auto_watch_category: TopicUser.notification_reasons[:auto_watch_category], ) - end def self.ensure_consistency! @@ -198,25 +191,30 @@ class CategoryUser < ActiveRecord::Base end def self.default_notification_level - SiteSetting.mute_all_categories_by_default ? notification_levels[:muted] : notification_levels[:regular] + if SiteSetting.mute_all_categories_by_default + notification_levels[:muted] + else + notification_levels[:regular] + end end def self.notification_levels_for(user) # Anonymous users have all default categories set to regular tracking, # except for default muted categories which stay muted. if user.blank? - notification_levels = [ - SiteSetting.default_categories_watching.split("|"), - SiteSetting.default_categories_tracking.split("|"), - SiteSetting.default_categories_watching_first_post.split("|"), - SiteSetting.default_categories_normal.split("|") - ].flatten.map do |id| - [id.to_i, self.notification_levels[:regular]] - end + notification_levels = + [ + SiteSetting.default_categories_watching.split("|"), + SiteSetting.default_categories_tracking.split("|"), + SiteSetting.default_categories_watching_first_post.split("|"), + SiteSetting.default_categories_normal.split("|"), + ].flatten.map { |id| [id.to_i, self.notification_levels[:regular]] } - notification_levels += SiteSetting.default_categories_muted.split("|").map do |id| - [id.to_i, self.notification_levels[:muted]] - end + notification_levels += + SiteSetting + .default_categories_muted + .split("|") + .map { |id| [id.to_i, self.notification_levels[:muted]] } else notification_levels = CategoryUser.where(user: user).pluck(:category_id, :notification_level) end @@ -238,22 +236,32 @@ class CategoryUser < ActiveRecord::Base def self.muted_category_ids_query(user, include_direct: false) query = Category query = query.where.not(parent_category_id: nil) if !include_direct - query = query - .joins("LEFT JOIN categories categories2 ON categories2.id = categories.parent_category_id") - .joins("LEFT JOIN category_users ON category_users.category_id = categories.id AND category_users.user_id = #{user.id}") - .joins("LEFT JOIN category_users category_users2 ON category_users2.category_id = categories2.id AND category_users2.user_id = #{user.id}") + query = + query + .joins("LEFT JOIN categories categories2 ON categories2.id = categories.parent_category_id") + .joins( + "LEFT JOIN category_users ON category_users.category_id = categories.id AND category_users.user_id = #{user.id}", + ) + .joins( + "LEFT JOIN category_users category_users2 ON category_users2.category_id = categories2.id AND category_users2.user_id = #{user.id}", + ) - direct_category_muted_sql = "COALESCE(category_users.notification_level, #{CategoryUser.default_notification_level}) = #{CategoryUser.notification_levels[:muted]}" + direct_category_muted_sql = + "COALESCE(category_users.notification_level, #{CategoryUser.default_notification_level}) = #{CategoryUser.notification_levels[:muted]}" parent_category_muted_sql = "(category_users.id IS NULL AND COALESCE(category_users2.notification_level, #{CategoryUser.default_notification_level}) = #{notification_levels[:muted]})" conditions = [parent_category_muted_sql] conditions.push(direct_category_muted_sql) if include_direct if SiteSetting.max_category_nesting === 3 - query = query - .joins("LEFT JOIN categories categories3 ON categories3.id = categories2.parent_category_id") - .joins("LEFT JOIN category_users category_users3 ON category_users3.category_id = categories3.id AND category_users3.user_id = #{user.id}") - grandparent_category_muted_sql = "(category_users.id IS NULL AND category_users2.id IS NULL AND COALESCE(category_users3.notification_level, #{CategoryUser.default_notification_level}) = #{notification_levels[:muted]})" + query = + query.joins( + "LEFT JOIN categories categories3 ON categories3.id = categories2.parent_category_id", + ).joins( + "LEFT JOIN category_users category_users3 ON category_users3.category_id = categories3.id AND category_users3.user_id = #{user.id}", + ) + grandparent_category_muted_sql = + "(category_users.id IS NULL AND category_users2.id IS NULL AND COALESCE(category_users3.notification_level, #{CategoryUser.default_notification_level}) = #{notification_levels[:muted]})" conditions.push(grandparent_category_muted_sql) end diff --git a/app/models/child_theme.rb b/app/models/child_theme.rb index 7ce4d0dc78b..2f29e6c7b71 100644 --- a/app/models/child_theme.rb +++ b/app/models/child_theme.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class ChildTheme < ActiveRecord::Base - belongs_to :parent_theme, class_name: 'Theme' - belongs_to :child_theme, class_name: 'Theme' + belongs_to :parent_theme, class_name: "Theme" + belongs_to :child_theme, class_name: "Theme" validate :child_validations @@ -11,7 +11,8 @@ class ChildTheme < ActiveRecord::Base def child_validations if Theme.where( "(component IS true AND id = :parent) OR (component IS false AND id = :child)", - parent: parent_theme_id, child: child_theme_id + parent: parent_theme_id, + child: child_theme_id, ).exists? errors.add(:base, I18n.t("themes.errors.no_multilevels_components")) end diff --git a/app/models/color_scheme.rb b/app/models/color_scheme.rb index bdbef6df9e4..99a1ee75fff 100644 --- a/app/models/color_scheme.rb +++ b/app/models/color_scheme.rb @@ -1,173 +1,172 @@ # frozen_string_literal: true class ColorScheme < ActiveRecord::Base - # rubocop:disable Layout/HashAlignment CUSTOM_SCHEMES = { - 'Dark': { - "primary" => 'dddddd', - "secondary" => '222222', - "tertiary" => '0f82af', - "quaternary" => 'c14924', - "header_background" => '111111', - "header_primary" => 'dddddd', - "highlight" => 'a87137', - "danger" => 'e45735', - "success" => '1ca551', - "love" => 'fa6c8d' + Dark: { + "primary" => "dddddd", + "secondary" => "222222", + "tertiary" => "0f82af", + "quaternary" => "c14924", + "header_background" => "111111", + "header_primary" => "dddddd", + "highlight" => "a87137", + "danger" => "e45735", + "success" => "1ca551", + "love" => "fa6c8d", }, # By @itsbhanusharma - 'Neutral': { - "primary" => '000000', - "secondary" => 'ffffff', - "tertiary" => '51839b', - "quaternary" => 'b85e48', - "header_background" => '333333', - "header_primary" => 'f3f3f3', - "highlight" => 'ecec70', - "danger" => 'b85e48', - "success" => '518751', - "love" => 'fa6c8d' + Neutral: { + "primary" => "000000", + "secondary" => "ffffff", + "tertiary" => "51839b", + "quaternary" => "b85e48", + "header_background" => "333333", + "header_primary" => "f3f3f3", + "highlight" => "ecec70", + "danger" => "b85e48", + "success" => "518751", + "love" => "fa6c8d", }, # By @Flower_Child - 'Grey Amber': { - "primary" => 'd9d9d9', - "secondary" => '3d4147', - "tertiary" => 'fdd459', - "quaternary" => 'fdd459', - "header_background" => '36393e', - "header_primary" => 'd9d9d9', - "highlight" => 'fdd459', - "danger" => 'e45735', - "success" => 'fdd459', - "love" => 'fdd459' + "Grey Amber": { + "primary" => "d9d9d9", + "secondary" => "3d4147", + "tertiary" => "fdd459", + "quaternary" => "fdd459", + "header_background" => "36393e", + "header_primary" => "d9d9d9", + "highlight" => "fdd459", + "danger" => "e45735", + "success" => "fdd459", + "love" => "fdd459", }, # By @rafafotes - 'Shades of Blue': { - "primary" => '203243', - "secondary" => 'eef4f7', - "tertiary" => '416376', - "quaternary" => '5e99b9', - "header_background" => '86bddb', - "header_primary" => '203243', - "highlight" => '86bddb', - "danger" => 'bf3c3c', - "success" => '70db82', - "love" => 'fc94cb' + "Shades of Blue": { + "primary" => "203243", + "secondary" => "eef4f7", + "tertiary" => "416376", + "quaternary" => "5e99b9", + "header_background" => "86bddb", + "header_primary" => "203243", + "highlight" => "86bddb", + "danger" => "bf3c3c", + "success" => "70db82", + "love" => "fc94cb", }, # By @mikechristopher - 'Latte': { - "primary" => 'f2e5d7', - "secondary" => '262322', - "tertiary" => 'f7f2ed', - "quaternary" => 'd7c9aa', - "header_background" => 'd7c9aa', - "header_primary" => '262322', - "highlight" => 'd7c9aa', - "danger" => 'db9584', - "success" => '78be78', - "love" => '8f6201' + Latte: { + "primary" => "f2e5d7", + "secondary" => "262322", + "tertiary" => "f7f2ed", + "quaternary" => "d7c9aa", + "header_background" => "d7c9aa", + "header_primary" => "262322", + "highlight" => "d7c9aa", + "danger" => "db9584", + "success" => "78be78", + "love" => "8f6201", }, # By @Flower_Child - 'Summer': { - "primary" => '874342', - "secondary" => 'fffff4', - "tertiary" => 'fe9896', - "quaternary" => 'fcc9d0', - "header_background" => '96ccbf', - "header_primary" => 'fff1e7', - "highlight" => 'f3c07f', - "danger" => 'cfebdc', - "success" => 'fcb4b5', - "love" => 'f3c07f' + Summer: { + "primary" => "874342", + "secondary" => "fffff4", + "tertiary" => "fe9896", + "quaternary" => "fcc9d0", + "header_background" => "96ccbf", + "header_primary" => "fff1e7", + "highlight" => "f3c07f", + "danger" => "cfebdc", + "success" => "fcb4b5", + "love" => "f3c07f", }, # By @Flower_Child - 'Dark Rose': { - "primary" => 'ca9cb2', - "secondary" => '3a2a37', - "tertiary" => 'fdd459', - "quaternary" => '7e566a', - "header_background" => 'a97189', - "header_primary" => 'd9b2bb', - "highlight" => '6c3e63', - "danger" => '6c3e63', - "success" => 'd9b2bb', - "love" => 'd9b2bb' + "Dark Rose": { + "primary" => "ca9cb2", + "secondary" => "3a2a37", + "tertiary" => "fdd459", + "quaternary" => "7e566a", + "header_background" => "a97189", + "header_primary" => "d9b2bb", + "highlight" => "6c3e63", + "danger" => "6c3e63", + "success" => "d9b2bb", + "love" => "d9b2bb", }, - "WCAG": { - "primary" => '000000', - "primary-medium" => '696969', - "primary-low-mid" => '909090', - "secondary" => 'ffffff', - "tertiary" => '3369FF', - "quaternary" => '3369FF', - "header_background" => 'ffffff', - "header_primary" => '000000', - "highlight" => '3369FF', - "highlight-high" => '0036E6', - "highlight-medium" => 'e0e9ff', - "highlight-low" => 'e0e9ff', - "danger" => 'BB1122', - "success" => '3d854d', - "love" => '9D256B' + WCAG: { + "primary" => "000000", + "primary-medium" => "696969", + "primary-low-mid" => "909090", + "secondary" => "ffffff", + "tertiary" => "3369FF", + "quaternary" => "3369FF", + "header_background" => "ffffff", + "header_primary" => "000000", + "highlight" => "3369FF", + "highlight-high" => "0036E6", + "highlight-medium" => "e0e9ff", + "highlight-low" => "e0e9ff", + "danger" => "BB1122", + "success" => "3d854d", + "love" => "9D256B", }, "WCAG Dark": { - "primary" => 'ffffff', - "primary-medium" => '999999', - "primary-low-mid" => '888888', - "secondary" => '0c0c0c', - "tertiary" => '759AFF', - "quaternary" => '759AFF', - "header_background" => '000000', - "header_primary" => 'ffffff', - "highlight" => '3369FF', - "danger" => 'BB1122', - "success" => '3d854d', - "love" => '9D256B' + "primary" => "ffffff", + "primary-medium" => "999999", + "primary-low-mid" => "888888", + "secondary" => "0c0c0c", + "tertiary" => "759AFF", + "quaternary" => "759AFF", + "header_background" => "000000", + "header_primary" => "ffffff", + "highlight" => "3369FF", + "danger" => "BB1122", + "success" => "3d854d", + "love" => "9D256B", }, # By @zenorocha - "Dracula": { + Dracula: { "primary_very_low" => "373A47", "primary_low" => "414350", "primary_low_mid" => "8C8D94", "primary_medium" => "A3A4AA", "primary_high" => "CCCCCF", - "primary" => 'f2f2f2', - "primary-50" => '3F414E', - "primary-100" => '535460', - "primary-200" => '666972', - "primary-300" => '7A7C84', - "primary-400" => '8D8F96', - "primary-500" => 'A2A3A9', - "primary-600" => 'B6B7BC', - "primary-700" => 'C7C7C7', - "primary-800" => 'DEDFE0', - "primary-900" => 'F5F5F5', + "primary" => "f2f2f2", + "primary-50" => "3F414E", + "primary-100" => "535460", + "primary-200" => "666972", + "primary-300" => "7A7C84", + "primary-400" => "8D8F96", + "primary-500" => "A2A3A9", + "primary-600" => "B6B7BC", + "primary-700" => "C7C7C7", + "primary-800" => "DEDFE0", + "primary-900" => "F5F5F5", "secondary_low" => "CCCCCF", "secondary_medium" => "91939A", "secondary_high" => "6A6C76", "secondary_very_high" => "3D404C", - "secondary" => '2d303e', + "secondary" => "2d303e", "tertiary_low" => "4A4463", "tertiary_medium" => "6E5D92", - "tertiary" => 'bd93f9', + "tertiary" => "bd93f9", "tertiary_high" => "9275C1", "quaternary_low" => "6AA8BA", - "quaternary" => '8be9fd', - "header_background" => '373A47', - "header_primary" => 'f2f2f2', + "quaternary" => "8be9fd", + "header_background" => "373A47", + "header_primary" => "f2f2f2", "highlight_low" => "686D55", "highlight_medium" => "52592B", - "highlight" => '52592B', + "highlight" => "52592B", "highlight_high" => "C0C879", "danger_low" => "957279", - "danger" => 'ff5555', + "danger" => "ff5555", "success_low" => "386D50", "success_medium" => "44B366", - "success" => '50fa7b', + "success" => "50fa7b", "love_low" => "6C4667", - "love" => 'ff79c6' + "love" => "ff79c6", }, # By @altercation "Solarized Light": { @@ -176,40 +175,40 @@ class ColorScheme < ActiveRecord::Base "primary_low_mid" => "A4AFA5", "primary_medium" => "7E918C", "primary_high" => "4C6869", - "primary" => '002B36', - "primary-50" => 'F0EBDA', - "primary-100" => 'DAD8CA', - "primary-200" => 'B2B9B3', - "primary-300" => '839496', - "primary-400" => '76898C', - "primary-500" => '697F83', - "primary-600" => '627A7E', - "primary-700" => '556F74', - "primary-800" => '415F66', - "primary-900" => '21454E', + "primary" => "002B36", + "primary-50" => "F0EBDA", + "primary-100" => "DAD8CA", + "primary-200" => "B2B9B3", + "primary-300" => "839496", + "primary-400" => "76898C", + "primary-500" => "697F83", + "primary-600" => "627A7E", + "primary-700" => "556F74", + "primary-800" => "415F66", + "primary-900" => "21454E", "secondary_low" => "325458", "secondary_medium" => "6C8280", "secondary_high" => "97A59D", "secondary_very_high" => "E8E6D3", - "secondary" => 'FCF6E1', + "secondary" => "FCF6E1", "tertiary_low" => "D6E6DE", "tertiary_medium" => "7EBFD7", - "tertiary" => '0088cc', + "tertiary" => "0088cc", "tertiary_high" => "329ED0", - "quaternary" => 'e45735', - "header_background" => 'FCF6E1', - "header_primary" => '002B36', + "quaternary" => "e45735", + "header_background" => "FCF6E1", + "header_primary" => "002B36", "highlight_low" => "FDF9AD", "highlight_medium" => "E3D0A3", - "highlight" => 'F2F481', + "highlight" => "F2F481", "highlight_high" => "BCAA7F", "danger_low" => "F8D9C2", - "danger" => 'e45735', + "danger" => "e45735", "success_low" => "CFE5B9", "success_medium" => "4CB544", - "success" => '009900', + "success" => "009900", "love_low" => "FCDDD2", - "love" => 'fa6c8d' + "love" => "fa6c8d", }, # By @altercation "Solarized Dark": { @@ -218,65 +217,59 @@ class ColorScheme < ActiveRecord::Base "primary_low_mid" => "798C88", "primary_medium" => "97A59D", "primary_high" => "B5BDB1", - "primary" => 'FCF6E1', - "primary-50" => '21454E', - "primary-100" => '415F66', - "primary-200" => '556F74', - "primary-300" => '627A7E', - "primary-400" => '697F83', - "primary-500" => '76898C', - "primary-600" => '839496', - "primary-700" => 'B2B9B3', - "primary-800" => 'DAD8CA', - "primary-900" => 'F0EBDA', + "primary" => "FCF6E1", + "primary-50" => "21454E", + "primary-100" => "415F66", + "primary-200" => "556F74", + "primary-300" => "627A7E", + "primary-400" => "697F83", + "primary-500" => "76898C", + "primary-600" => "839496", + "primary-700" => "B2B9B3", + "primary-800" => "DAD8CA", + "primary-900" => "F0EBDA", "secondary_low" => "B5BDB1", "secondary_medium" => "81938D", "secondary_high" => "4E6A6B", "secondary_very_high" => "143B44", - "secondary" => '002B36', + "secondary" => "002B36", "tertiary_low" => "003E54", "tertiary_medium" => "00557A", - "tertiary" => '0088cc', + "tertiary" => "0088cc", "tertiary_high" => "006C9F", "quaternary_low" => "944835", - "quaternary" => 'e45735', - "header_background" => '002B36', - "header_primary" => 'FCF6E1', + "quaternary" => "e45735", + "header_background" => "002B36", + "header_primary" => "FCF6E1", "highlight_low" => "4D6B3D", "highlight_medium" => "464C33", - "highlight" => 'F2F481', + "highlight" => "F2F481", "highlight_high" => "BFCA47", "danger_low" => "443836", "danger_medium" => "944835", - "danger" => 'e45735', + "danger" => "e45735", "success_low" => "004C26", "success_medium" => "007313", - "success" => '009900', + "success" => "009900", "love_low" => "4B3F50", - "love" => 'fa6c8d', - } + "love" => "fa6c8d", + }, } # rubocop:enable Layout/HashAlignment - LIGHT_THEME_ID = 'Light' + LIGHT_THEME_ID = "Light" def self.base_color_scheme_colors base_with_hash = [] - base_colors.each do |name, color| - base_with_hash << { name: name, hex: "#{color}" } - end + base_colors.each { |name, color| base_with_hash << { name: name, hex: "#{color}" } } - list = [ - { id: LIGHT_THEME_ID, colors: base_with_hash } - ] + list = [{ id: LIGHT_THEME_ID, colors: base_with_hash }] CUSTOM_SCHEMES.each do |k, v| colors = [] - v.each do |name, color| - colors << { name: name, hex: "#{color}" } - end + v.each { |name, color| colors << { name: name, hex: "#{color}" } } list.push(id: k.to_s, colors: colors) end @@ -290,7 +283,7 @@ class ColorScheme < ActiveRecord::Base attr_accessor :is_base attr_accessor :skip_publish - has_many :color_scheme_colors, -> { order('id ASC') }, dependent: :destroy + has_many :color_scheme_colors, -> { order("id ASC") }, dependent: :destroy alias_method :colors, :color_scheme_colors @@ -303,7 +296,8 @@ class ColorScheme < ActiveRecord::Base validates_associated :color_scheme_colors BASE_COLORS_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/colors.scss" - COLOR_TRANSFORMATION_FILE = "#{Rails.root}/app/assets/stylesheets/common/foundation/color_transformations.scss" + COLOR_TRANSFORMATION_FILE = + "#{Rails.root}/app/assets/stylesheets/common/foundation/color_transformations.scss" @mutex = Mutex.new @@ -312,10 +306,12 @@ class ColorScheme < ActiveRecord::Base @mutex.synchronize do return @base_colors if @base_colors base_colors = {} - File.readlines(BASE_COLORS_FILE).each do |line| - matches = /\$([\w]+):\s*#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})(?:[;]|\s)/.match(line.strip) - base_colors[matches[1]] = matches[2] if matches - end + File + .readlines(BASE_COLORS_FILE) + .each do |line| + matches = /\$([\w]+):\s*#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})(?:[;]|\s)/.match(line.strip) + base_colors[matches[1]] = matches[2] if matches + end @base_colors = base_colors end @base_colors @@ -326,10 +322,12 @@ class ColorScheme < ActiveRecord::Base @mutex.synchronize do return @transformation_variables if @transformation_variables transformation_variables = [] - File.readlines(COLOR_TRANSFORMATION_FILE).each do |line| - matches = /\$([\w\-_]+):.*/.match(line.strip) - transformation_variables.append(matches[1]) if matches - end + File + .readlines(COLOR_TRANSFORMATION_FILE) + .each do |line| + matches = /\$([\w\-_]+):.*/.match(line.strip) + transformation_variables.append(matches[1]) if matches + end @transformation_variables = transformation_variables end @transformation_variables @@ -337,7 +335,11 @@ class ColorScheme < ActiveRecord::Base def self.base_color_schemes base_color_scheme_colors.map do |hash| - scheme = new(name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(' ', '_')}"), base_scheme_id: hash[:id]) + scheme = + new( + name: I18n.t("color_schemes.#{hash[:id].downcase.gsub(" ", "_")}"), + base_scheme_id: hash[:id], + ) scheme.colors = hash[:colors].map { |k| { name: k[:name], hex: k[:hex] } } scheme.is_base = true scheme @@ -346,7 +348,7 @@ class ColorScheme < ActiveRecord::Base def self.base return @base_color_scheme if @base_color_scheme - @base_color_scheme = new(name: I18n.t('color_schemes.base_theme_name')) + @base_color_scheme = new(name: I18n.t("color_schemes.base_theme_name")) @base_color_scheme.colors = base_colors.map { |name, hex| { name: name, hex: hex } } @base_color_scheme.is_base = true @base_color_scheme @@ -363,9 +365,10 @@ class ColorScheme < ActiveRecord::Base new_color_scheme.base_scheme_id = params[:base_scheme_id] new_color_scheme.user_selectable = true - colors = CUSTOM_SCHEMES[params[:base_scheme_id].to_sym]&.map do |name, hex| - { name: name, hex: hex } - end if params[:base_scheme_id] + colors = + CUSTOM_SCHEMES[params[:base_scheme_id].to_sym]&.map do |name, hex| + { name: name, hex: hex } + end if params[:base_scheme_id] colors ||= base.colors_hashes # Override base values @@ -394,13 +397,17 @@ class ColorScheme < ActiveRecord::Base def colors=(arr) @colors_by_name = nil - arr.each do |c| - self.color_scheme_colors << ColorSchemeColor.new(name: c[:name], hex: c[:hex]) - end + arr.each { |c| self.color_scheme_colors << ColorSchemeColor.new(name: c[:name], hex: c[:hex]) } end def colors_by_name - @colors_by_name ||= self.colors.inject({}) { |sum, c| sum[c.name] = c; sum; } + @colors_by_name ||= + self + .colors + .inject({}) do |sum, c| + sum[c.name] = c + sum + end end def clear_colors_cache @@ -408,16 +415,12 @@ class ColorScheme < ActiveRecord::Base end def colors_hashes - color_scheme_colors.map do |c| - { name: c.name, hex: c.hex } - end + color_scheme_colors.map { |c| { name: c.name, hex: c.hex } } end def base_colors colors = nil - if base_scheme_id && base_scheme_id != "Light" - colors = CUSTOM_SCHEMES[base_scheme_id.to_sym] - end + colors = CUSTOM_SCHEMES[base_scheme_id.to_sym] if base_scheme_id && base_scheme_id != "Light" colors || ColorScheme.base_colors end @@ -425,21 +428,15 @@ class ColorScheme < ActiveRecord::Base resolved = ColorScheme.base_colors.dup if base_scheme_id && base_scheme_id != "Light" if scheme = CUSTOM_SCHEMES[base_scheme_id.to_sym] - scheme.each do |name, value| - resolved[name] = value - end + scheme.each { |name, value| resolved[name] = value } end end - colors.each do |c| - resolved[c.name] = c.hex - end + colors.each { |c| resolved[c.name] = c.hex } resolved end def publish_discourse_stylesheet - if self.id - self.class.publish_discourse_stylesheets!(self.id) - end + self.class.publish_discourse_stylesheets!(self.id) if self.id end def self.publish_discourse_stylesheets!(id = nil) @@ -458,7 +455,7 @@ class ColorScheme < ActiveRecord::Base theme_ids, with_scheme: true, clear_manager_cache: false, - all_themes: true + all_themes: true, ) end end @@ -469,9 +466,7 @@ class ColorScheme < ActiveRecord::Base end def bump_version - if self.id - self.version += 1 - end + self.version += 1 if self.id end def is_dark? @@ -484,7 +479,7 @@ class ColorScheme < ActiveRecord::Base end def is_wcag? - base_scheme_id&.start_with?('WCAG') + base_scheme_id&.start_with?("WCAG") end # Equivalent to dc-color-brightness() in variables.scss diff --git a/app/models/color_scheme_setting.rb b/app/models/color_scheme_setting.rb index 4003348b0e8..8dc5ce5cc68 100644 --- a/app/models/color_scheme_setting.rb +++ b/app/models/color_scheme_setting.rb @@ -1,17 +1,13 @@ # frozen_string_literal: true class ColorSchemeSetting < EnumSiteSetting - def self.valid_value?(val) val == -1 || ColorScheme.find_by_id(val) end def self.values values = [{ name: I18n.t("site_settings.dark_mode_none"), value: -1 }] - ColorScheme.all.map do |c| - values << { name: c.name, value: c.id } - end + ColorScheme.all.map { |c| values << { name: c.name, value: c.id } } values end - end diff --git a/app/models/concerns/anon_cache_invalidator.rb b/app/models/concerns/anon_cache_invalidator.rb index f87cada6c0e..cb468212fac 100644 --- a/app/models/concerns/anon_cache_invalidator.rb +++ b/app/models/concerns/anon_cache_invalidator.rb @@ -4,12 +4,8 @@ module AnonCacheInvalidator extend ActiveSupport::Concern included do - after_destroy do - Site.clear_anon_cache! - end + after_destroy { Site.clear_anon_cache! } - after_save do - Site.clear_anon_cache! - end + after_save { Site.clear_anon_cache! } end end diff --git a/app/models/concerns/cached_counting.rb b/app/models/concerns/cached_counting.rb index f8083cc0788..7598c7be22b 100644 --- a/app/models/concerns/cached_counting.rb +++ b/app/models/concerns/cached_counting.rb @@ -53,9 +53,7 @@ module CachedCounting @last_ensure_thread = now - if !@thread&.alive? - @thread = nil - end + @thread = nil if !@thread&.alive? @thread ||= Thread.new { thread_loop } end end @@ -74,26 +72,20 @@ module CachedCounting end iterations += 1 end - rescue => ex if Redis::CommandError === ex && ex.message =~ /READONLY/ # do not warn for Redis readonly mode elsif PG::ReadOnlySqlTransaction === ex # do not warn for PG readonly mode else - Discourse.warn_exception( - ex, - message: 'Unexpected error while processing cached counts' - ) + Discourse.warn_exception(ex, message: "Unexpected error while processing cached counts") end end def self.flush @flush = true @thread.wakeup - while @flush - sleep 0.001 - end + sleep 0.001 while @flush end COUNTER_REDIS_HASH = "CounterCacheHash" @@ -122,25 +114,23 @@ module CachedCounting redis = Discourse.redis.without_namespace DistributedMutex.synchronize("flush_counters_to_db", redis: redis, validity: 5.minutes) do if allowed_to_flush_to_db? - redis.hkeys(COUNTER_REDIS_HASH).each do |key| + redis + .hkeys(COUNTER_REDIS_HASH) + .each do |key| + val = LUA_HGET_DEL.eval(redis, [COUNTER_REDIS_HASH, key]).to_i - val = LUA_HGET_DEL.eval( - redis, - [COUNTER_REDIS_HASH, key] - ).to_i + # unlikely (protected by mutex), but protect just in case + # could be a race condition in test + if val > 0 + klass_name, db, date, local_key = key.split(",", 4) + date = Date.strptime(date, "%Y%m%d") + klass = Module.const_get(klass_name) - # unlikely (protected by mutex), but protect just in case - # could be a race condition in test - if val > 0 - klass_name, db, date, local_key = key.split(",", 4) - date = Date.strptime(date, "%Y%m%d") - klass = Module.const_get(klass_name) - - RailsMultisite::ConnectionManagement.with_connection(db) do - klass.write_cache!(local_key, val, date) + RailsMultisite::ConnectionManagement.with_connection(db) do + klass.write_cache!(local_key, val, date) + end end end - end end end end @@ -154,7 +144,12 @@ module CachedCounting end def self.allowed_to_flush_to_db? - Discourse.redis.without_namespace.set(DB_COOLDOWN_KEY, "1", ex: DB_FLUSH_COOLDOWN_SECONDS, nx: true) + Discourse.redis.without_namespace.set( + DB_COOLDOWN_KEY, + "1", + ex: DB_FLUSH_COOLDOWN_SECONDS, + nx: true, + ) end def self.queue(key, klass) @@ -176,6 +171,5 @@ module CachedCounting def write_cache!(key, count, date) raise NotImplementedError end - end end diff --git a/app/models/concerns/category_hashtag.rb b/app/models/concerns/category_hashtag.rb index db28efe789f..52d4d01b5b5 100644 --- a/app/models/concerns/category_hashtag.rb +++ b/app/models/concerns/category_hashtag.rb @@ -55,9 +55,7 @@ module CategoryHashtag end end else - categories.find do |cat| - cat.slug.downcase == parent_slug && cat.top_level? - end + categories.find { |cat| cat.slug.downcase == parent_slug && cat.top_level? } end end .compact diff --git a/app/models/concerns/has_custom_fields.rb b/app/models/concerns/has_custom_fields.rb index 07ebc880ce9..2b2f64f08a3 100644 --- a/app/models/concerns/has_custom_fields.rb +++ b/app/models/concerns/has_custom_fields.rb @@ -4,7 +4,6 @@ module HasCustomFields extend ActiveSupport::Concern module Helpers - def self.append_field(target, key, value, types) if target.has_key?(key) target[key] = [target[key]] if !target[key].is_a? Array @@ -14,18 +13,14 @@ module HasCustomFields end end - CUSTOM_FIELD_TRUE ||= ['1', 't', 'true', 'T', 'True', 'TRUE'].freeze + CUSTOM_FIELD_TRUE ||= %w[1 t true T True TRUE].freeze def self.get_custom_field_type(types, key) return unless types - sorted_types = types.keys.select { |k| k.end_with?("*") } - .sort_by(&:length) - .reverse + sorted_types = types.keys.select { |k| k.end_with?("*") }.sort_by(&:length).reverse - sorted_types.each do |t| - return types[t] if key =~ /^#{t}/i - end + sorted_types.each { |t| return types[t] if key =~ /^#{t}/i } types[key] end @@ -42,9 +37,12 @@ module HasCustomFields result = case type - when :boolean then !!CUSTOM_FIELD_TRUE.include?(value) - when :integer then value.to_i - when :json then parse_json_value(value, key) + when :boolean + !!CUSTOM_FIELD_TRUE.include?(value) + when :integer + value.to_i + when :json + parse_json_value(value, key) else value end @@ -55,7 +53,9 @@ module HasCustomFields def self.parse_json_value(value, key) ::JSON.parse(value) rescue JSON::ParserError - Rails.logger.warn("Value '#{value}' for custom field '#{key}' is not json, it is being ignored.") + Rails.logger.warn( + "Value '#{value}' for custom field '#{key}' is not json, it is being ignored.", + ) {} end end @@ -80,11 +80,13 @@ module HasCustomFields return result if allowed_fields.blank? - klass.where(foreign_key => ids, :name => allowed_fields) - .pluck(foreign_key, :name, :value).each do |cf| - result[cf[0]] ||= {} - append_custom_field(result[cf[0]], cf[1], cf[2]) - end + klass + .where(foreign_key => ids, :name => allowed_fields) + .pluck(foreign_key, :name, :value) + .each do |cf| + result[cf[0]] ||= {} + append_custom_field(result[cf[0]], cf[1], cf[2]) + end result end @@ -108,9 +110,7 @@ module HasCustomFields map = {} empty = {} - fields.each do |field| - empty[field] = nil - end + fields.each { |field| empty[field] = nil } objects.each do |obj| map[obj.id] = obj @@ -119,20 +119,18 @@ module HasCustomFields fk = (name.underscore << "_id") - "#{name}CustomField".constantize + "#{name}CustomField" + .constantize .where("#{fk} in (?)", map.keys) .where("name in (?)", fields) - .pluck(fk, :name, :value).each do |id, name, value| + .pluck(fk, :name, :value) + .each do |id, name, value| + preloaded = map[id].preloaded_custom_fields - preloaded = map[id].preloaded_custom_fields - - if preloaded[name].nil? - preloaded.delete(name) - end + preloaded.delete(name) if preloaded[name].nil? HasCustomFields::Helpers.append_field(preloaded, name, value, @custom_field_types) - end - + end end end end @@ -160,7 +158,8 @@ module HasCustomFields @custom_fields_orig = nil end - class NotPreloadedError < StandardError; end + class NotPreloadedError < StandardError + end class PreloadedProxy def initialize(preloaded, klass_with_custom_fields) @preloaded = preloaded @@ -172,7 +171,8 @@ module HasCustomFields @preloaded[key] else # for now you can not mix preload an non preload, it better just to fail - raise NotPreloadedError, "Attempted to access the non preloaded custom field '#{key}' on the '#{@klass_with_custom_fields}' class. This is disallowed to prevent N+1 queries." + raise NotPreloadedError, + "Attempted to access the non preloaded custom field '#{key}' on the '#{@klass_with_custom_fields}' class. This is disallowed to prevent N+1 queries." end end end @@ -210,9 +210,7 @@ module HasCustomFields def upsert_custom_fields(fields) fields.each do |k, v| row_count = _custom_fields.where(name: k).update_all(value: v) - if row_count == 0 - _custom_fields.create!(name: k, value: v) - end + _custom_fields.create!(name: k, value: v) if row_count == 0 custom_fields[k.to_s] = v # We normalize custom_fields as strings end @@ -281,8 +279,8 @@ module HasCustomFields # update the same custom field we should catch the error and perform an update instead. def create_singular(name, value, field_type = nil) write_value = value.is_a?(Hash) || field_type == :json ? value.to_json : value - write_value = 't' if write_value.is_a?(TrueClass) - write_value = 'f' if write_value.is_a?(FalseClass) + write_value = "t" if write_value.is_a?(TrueClass) + write_value = "f" if write_value.is_a?(FalseClass) row_count = DB.exec(<<~SQL, name: name, value: write_value, id: id, now: Time.zone.now) INSERT INTO #{_custom_fields.table_name} (#{custom_fields_fk}, name, value, created_at, updated_at) VALUES (:id, :name, :value, :now, :now) @@ -291,15 +289,15 @@ module HasCustomFields _custom_fields.where(name: name).update_all(value: write_value) if row_count == 0 end -protected + protected def refresh_custom_fields_from_db target = HashWithIndifferentAccess.new - _custom_fields.order('id asc').pluck(:name, :value).each do |key, value| - self.class.append_custom_field(target, key, value) - end + _custom_fields + .order("id asc") + .pluck(:name, :value) + .each { |key, value| self.class.append_custom_field(target, key, value) } @custom_fields_orig = target @custom_fields = @custom_fields_orig.deep_dup end - end diff --git a/app/models/concerns/has_destroyed_web_hook.rb b/app/models/concerns/has_destroyed_web_hook.rb index 0b05f437a1c..85ce31ae6f9 100644 --- a/app/models/concerns/has_destroyed_web_hook.rb +++ b/app/models/concerns/has_destroyed_web_hook.rb @@ -3,9 +3,7 @@ module HasDestroyedWebHook extend ActiveSupport::Concern - included do - around_destroy :enqueue_destroyed_web_hook - end + included { around_destroy :enqueue_destroyed_web_hook } def enqueue_destroyed_web_hook type = self.class.name.underscore.to_sym @@ -13,10 +11,7 @@ module HasDestroyedWebHook if WebHook.active_web_hooks(type).exists? payload = WebHook.generate_payload(type, self) yield - WebHook.enqueue_hooks(type, "#{type}_destroyed".to_sym, - id: id, - payload: payload - ) + WebHook.enqueue_hooks(type, "#{type}_destroyed".to_sym, id: id, payload: payload) else yield end diff --git a/app/models/concerns/has_sanitizable_fields.rb b/app/models/concerns/has_sanitizable_fields.rb index 426c4207479..5897dddd0d4 100644 --- a/app/models/concerns/has_sanitizable_fields.rb +++ b/app/models/concerns/has_sanitizable_fields.rb @@ -15,7 +15,7 @@ module HasSanitizableFields field = CGI.unescape_html(sanitizer.sanitize(field, attributes: allowed_attributes)) # Just replace the characters that our translations use for interpolation. # Calling CGI.unescape removes characters like '+', which will corrupt the original value. - field = field.gsub('%7B', '{').gsub('%7D', '}') + field = field.gsub("%7B", "{").gsub("%7D", "}") end field diff --git a/app/models/concerns/has_search_data.rb b/app/models/concerns/has_search_data.rb index e997083019e..1ff9a16358d 100644 --- a/app/models/concerns/has_search_data.rb +++ b/app/models/concerns/has_search_data.rb @@ -4,7 +4,7 @@ module HasSearchData extend ActiveSupport::Concern included do - _associated_record_name = self.name.sub('SearchData', '').underscore + _associated_record_name = self.name.sub("SearchData", "").underscore self.primary_key = "#{_associated_record_name}_id" belongs_to _associated_record_name.to_sym validates_presence_of :search_data diff --git a/app/models/concerns/has_url.rb b/app/models/concerns/has_url.rb index dc6c58cee1d..d11aa69321e 100644 --- a/app/models/concerns/has_url.rb +++ b/app/models/concerns/has_url.rb @@ -21,10 +21,11 @@ module HasUrl def get_from_url(url) return if url.blank? - uri = begin - URI(UrlHelper.unencode(url)) - rescue URI::Error - end + uri = + begin + URI(UrlHelper.unencode(url)) + rescue URI::Error + end return if uri&.path.blank? data = extract_url(uri.path) @@ -52,10 +53,11 @@ module HasUrl upload_urls.each do |url| next if url.blank? - uri = begin - URI(UrlHelper.unencode(url)) - rescue URI::Error - end + uri = + begin + URI(UrlHelper.unencode(url)) + rescue URI::Error + end next if uri&.path.blank? urls << uri.path diff --git a/app/models/concerns/positionable.rb b/app/models/concerns/positionable.rb index d353e306375..6ee93cdf4c9 100644 --- a/app/models/concerns/positionable.rb +++ b/app/models/concerns/positionable.rb @@ -3,14 +3,9 @@ module Positionable extend ActiveSupport::Concern - included do - before_save do - self.position ||= self.class.count - end - end + included { before_save { self.position ||= self.class.count } } def move_to(position_arg) - position = [[position_arg, 0].max, self.class.count - 1].min if self.position.nil? || position > (self.position) @@ -18,13 +13,15 @@ module Positionable UPDATE #{self.class.table_name} SET position = position - 1 WHERE position > :current_position and position <= :new_position", - current_position: self.position, new_position: position + current_position: self.position, + new_position: position elsif position < self.position DB.exec " UPDATE #{self.class.table_name} SET position = position + 1 WHERE position >= :new_position and position < :current_position", - current_position: self.position, new_position: position + current_position: self.position, + new_position: position else # Not moving to a new position return @@ -33,6 +30,8 @@ module Positionable DB.exec " UPDATE #{self.class.table_name} SET position = :position - WHERE id = :id", id: id, position: position + WHERE id = :id", + id: id, + position: position end end diff --git a/app/models/concerns/reports/bookmarks.rb b/app/models/concerns/reports/bookmarks.rb index c520855b1d4..3dac869021e 100644 --- a/app/models/concerns/reports/bookmarks.rb +++ b/app/models/concerns/reports/bookmarks.rb @@ -5,20 +5,20 @@ module Reports::Bookmarks class_methods do def report_bookmarks(report) - report.icon = 'bookmark' + report.icon = "bookmark" category_filter = report.filters.dig(:category) - report.add_filter('category', default: category_filter) + report.add_filter("category", default: category_filter) report.data = [] - Bookmark.count_per_day( - category_id: category_filter, - start_date: report.start_date, - end_date: report.end_date - ).each do |date, count| - report.data << { x: date, y: count } - end - add_counts report, Bookmark, 'bookmarks.created_at' + Bookmark + .count_per_day( + category_id: category_filter, + start_date: report.start_date, + end_date: report.end_date, + ) + .each { |date, count| report.data << { x: date, y: count } } + add_counts report, Bookmark, "bookmarks.created_at" end end end diff --git a/app/models/concerns/reports/consolidated_api_requests.rb b/app/models/concerns/reports/consolidated_api_requests.rb index ab25b1f73f1..60184f92dde 100644 --- a/app/models/concerns/reports/consolidated_api_requests.rb +++ b/app/models/concerns/reports/consolidated_api_requests.rb @@ -5,29 +5,28 @@ module Reports::ConsolidatedApiRequests class_methods do def report_consolidated_api_requests(report) - filters = %w[ - api - user_api - ] + filters = %w[api user_api] report.modes = [:stacked_chart] - tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc' - danger = ColorScheme.hex_for_name('danger') || 'e45735' + tertiary = ColorScheme.hex_for_name("tertiary") || "0088cc" + danger = ColorScheme.hex_for_name("danger") || "e45735" - requests = filters.map do |filter| - color = filter == "api" ? report.rgba_color(tertiary) : report.rgba_color(danger) + requests = + filters.map do |filter| + color = filter == "api" ? report.rgba_color(tertiary) : report.rgba_color(danger) - { - req: filter, - label: I18n.t("reports.consolidated_api_requests.xaxis.#{filter}"), - color: color, - data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]) - } - end + { + req: filter, + label: I18n.t("reports.consolidated_api_requests.xaxis.#{filter}"), + color: color, + data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]), + } + end requests.each do |request| - request[:data] = request[:data].where('date >= ? AND date <= ?', report.start_date, report.end_date) + request[:data] = request[:data] + .where("date >= ? AND date <= ?", report.start_date, report.end_date) .order(date: :asc) .group(:date) .sum(:count) diff --git a/app/models/concerns/reports/consolidated_page_views.rb b/app/models/concerns/reports/consolidated_page_views.rb index 35757d7fbe8..41854f56686 100644 --- a/app/models/concerns/reports/consolidated_page_views.rb +++ b/app/models/concerns/reports/consolidated_page_views.rb @@ -5,38 +5,32 @@ module Reports::ConsolidatedPageViews class_methods do def report_consolidated_page_views(report) - filters = %w[ - page_view_logged_in - page_view_anon - page_view_crawler - ] + filters = %w[page_view_logged_in page_view_anon page_view_crawler] report.modes = [:stacked_chart] - tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc' - danger = ColorScheme.hex_for_name('danger') || 'e45735' + tertiary = ColorScheme.hex_for_name("tertiary") || "0088cc" + danger = ColorScheme.hex_for_name("danger") || "e45735" - requests = filters.map do |filter| - color = report.rgba_color(tertiary) + requests = + filters.map do |filter| + color = report.rgba_color(tertiary) - if filter == "page_view_anon" - color = report.lighten_color(tertiary, 0.25) + color = report.lighten_color(tertiary, 0.25) if filter == "page_view_anon" + + color = report.rgba_color(danger, 0.75) if filter == "page_view_crawler" + + { + req: filter, + label: I18n.t("reports.consolidated_page_views.xaxis.#{filter}"), + color: color, + data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]), + } end - if filter == "page_view_crawler" - color = report.rgba_color(danger, 0.75) - end - - { - req: filter, - label: I18n.t("reports.consolidated_page_views.xaxis.#{filter}"), - color: color, - data: ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]) - } - end - requests.each do |request| - request[:data] = request[:data].where('date >= ? AND date <= ?', report.start_date, report.end_date) + request[:data] = request[:data] + .where("date >= ? AND date <= ?", report.start_date, report.end_date) .order(date: :asc) .group(:date) .sum(:count) diff --git a/app/models/concerns/reports/daily_engaged_users.rb b/app/models/concerns/reports/daily_engaged_users.rb index 9e4dfdbd59e..f2be595c9a2 100644 --- a/app/models/concerns/reports/daily_engaged_users.rb +++ b/app/models/concerns/reports/daily_engaged_users.rb @@ -12,27 +12,23 @@ module Reports::DailyEngagedUsers data = UserAction.count_daily_engaged_users(report.start_date, report.end_date) if report.facets.include?(:prev30Days) - prev30DaysData = UserAction.count_daily_engaged_users(report.start_date - 30.days, report.start_date) + prev30DaysData = + UserAction.count_daily_engaged_users(report.start_date - 30.days, report.start_date) report.prev30Days = prev30DaysData.sum { |k, v| v } end - if report.facets.include?(:total) - report.total = UserAction.count_daily_engaged_users - end + report.total = UserAction.count_daily_engaged_users if report.facets.include?(:total) if report.facets.include?(:prev_period) - prev_data = UserAction.count_daily_engaged_users(report.prev_start_date, report.prev_end_date) + prev_data = + UserAction.count_daily_engaged_users(report.prev_start_date, report.prev_end_date) prev = prev_data.sum { |k, v| v } - if prev > 0 - prev = prev / ((report.end_date - report.start_date) / 1.day) - end + prev = prev / ((report.end_date - report.start_date) / 1.day) if prev > 0 report.prev_period = prev end - data.each do |key, value| - report.data << { x: key, y: value } - end + data.each { |key, value| report.data << { x: key, y: value } } end end end diff --git a/app/models/concerns/reports/dau_by_mau.rb b/app/models/concerns/reports/dau_by_mau.rb index 69956c8e578..4d97531fed6 100644 --- a/app/models/concerns/reports/dau_by_mau.rb +++ b/app/models/concerns/reports/dau_by_mau.rb @@ -6,16 +6,8 @@ module Reports::DauByMau class_methods do def report_dau_by_mau(report) report.labels = [ - { - type: :date, - property: :x, - title: I18n.t("reports.default.labels.day") - }, - { - type: :percent, - property: :y, - title: I18n.t("reports.default.labels.percent") - }, + { type: :date, property: :x, title: I18n.t("reports.default.labels.day") }, + { type: :percent, property: :y, title: I18n.t("reports.default.labels.percent") }, ] report.average = true @@ -25,21 +17,23 @@ module Reports::DauByMau report.data = [] - compute_dau_by_mau = Proc.new { |data_point| - if data_point["mau"] == 0 - 0 - else - ((data_point["dau"].to_f / data_point["mau"].to_f) * 100).ceil(2) + compute_dau_by_mau = + Proc.new do |data_point| + if data_point["mau"] == 0 + 0 + else + ((data_point["dau"].to_f / data_point["mau"].to_f) * 100).ceil(2) + end end - } - dau_avg = Proc.new { |start_date, end_date| - data_points = UserVisit.count_by_active_users(start_date, end_date) - if !data_points.empty? - sum = data_points.sum { |data_point| compute_dau_by_mau.call(data_point) } - (sum.to_f / data_points.count.to_f).ceil(2) + dau_avg = + Proc.new do |start_date, end_date| + data_points = UserVisit.count_by_active_users(start_date, end_date) + if !data_points.empty? + sum = data_points.sum { |data_point| compute_dau_by_mau.call(data_point) } + (sum.to_f / data_points.count.to_f).ceil(2) + end end - } data_points.each do |data_point| report.data << { x: data_point["date"], y: compute_dau_by_mau.call(data_point) } diff --git a/app/models/concerns/reports/flags.rb b/app/models/concerns/reports/flags.rb index 8c8cd8424a5..99c73085fa5 100644 --- a/app/models/concerns/reports/flags.rb +++ b/app/models/concerns/reports/flags.rb @@ -7,7 +7,7 @@ module Reports::Flags def report_flags(report) category_id, include_subcategories = report.add_category_filter - report.icon = 'flag' + report.icon = "flag" report.higher_is_better = false basic_report_about( @@ -17,20 +17,21 @@ module Reports::Flags report.start_date, report.end_date, category_id, - include_subcategories + include_subcategories, ) countable = ReviewableFlaggedPost.scores_with_topics if category_id if include_subcategories - countable = countable.where('topics.category_id IN (?)', Category.subcategory_ids(category_id)) + countable = + countable.where("topics.category_id IN (?)", Category.subcategory_ids(category_id)) else - countable = countable.where('topics.category_id = ?', category_id) + countable = countable.where("topics.category_id = ?", category_id) end end - add_counts report, countable, 'reviewable_scores.created_at' + add_counts report, countable, "reviewable_scores.created_at" end end end diff --git a/app/models/concerns/reports/flags_status.rb b/app/models/concerns/reports/flags_status.rb index 1349f13b0b8..572185ebc53 100644 --- a/app/models/concerns/reports/flags_status.rb +++ b/app/models/concerns/reports/flags_status.rb @@ -13,42 +13,42 @@ module Reports::FlagsStatus properties: { topic_id: :topic_id, number: :post_number, - truncated_raw: :post_type + truncated_raw: :post_type, }, - title: I18n.t("reports.flags_status.labels.flag") + title: I18n.t("reports.flags_status.labels.flag"), }, { type: :user, properties: { username: :staff_username, id: :staff_id, - avatar: :staff_avatar_template + avatar: :staff_avatar_template, }, - title: I18n.t("reports.flags_status.labels.assigned") + title: I18n.t("reports.flags_status.labels.assigned"), }, { type: :user, properties: { username: :poster_username, id: :poster_id, - avatar: :poster_avatar_template + avatar: :poster_avatar_template, }, - title: I18n.t("reports.flags_status.labels.poster") + title: I18n.t("reports.flags_status.labels.poster"), }, { type: :user, properties: { username: :flagger_username, id: :flagger_id, - avatar: :flagger_avatar_template - }, - title: I18n.t("reports.flags_status.labels.flagger") + avatar: :flagger_avatar_template, + }, + title: I18n.t("reports.flags_status.labels.flagger"), }, { type: :seconds, property: :response_time, - title: I18n.t("reports.flags_status.labels.time_to_resolution") - } + title: I18n.t("reports.flags_status.labels.time_to_resolution"), + }, ] report.data = [] @@ -70,7 +70,7 @@ module Reports::FlagsStatus user_id, COALESCE(disagreed_at, agreed_at, deferred_at) AS responded_at FROM post_actions - WHERE post_action_type_id IN (#{flag_types.values.join(',')}) + WHERE post_action_type_id IN (#{flag_types.values.join(",")}) AND created_at >= '#{report.start_date}' AND created_at <= '#{report.end_date}' ORDER BY created_at DESC @@ -136,43 +136,54 @@ module Reports::FlagsStatus ON pd.id = pa.id SQL - DB.query(sql).each do |row| - data = {} + DB + .query(sql) + .each do |row| + data = {} - data[:post_type] = flag_types.key(row.post_action_type_id).to_s - data[:post_number] = row.post_number - data[:topic_id] = row.topic_id + data[:post_type] = flag_types.key(row.post_action_type_id).to_s + data[:post_number] = row.post_number + data[:topic_id] = row.topic_id - if row.staff_id - data[:staff_username] = row.staff_username - data[:staff_id] = row.staff_id - data[:staff_avatar_template] = User.avatar_template(row.staff_username, row.staff_avatar_id) + if row.staff_id + data[:staff_username] = row.staff_username + data[:staff_id] = row.staff_id + data[:staff_avatar_template] = User.avatar_template( + row.staff_username, + row.staff_avatar_id, + ) + end + + if row.poster_id + data[:poster_username] = row.poster_username + data[:poster_id] = row.poster_id + data[:poster_avatar_template] = User.avatar_template( + row.poster_username, + row.poster_avatar_id, + ) + end + + if row.flagger_id + data[:flagger_id] = row.flagger_id + data[:flagger_username] = row.flagger_username + data[:flagger_avatar_template] = User.avatar_template( + row.flagger_username, + row.flagger_avatar_id, + ) + end + + if row.agreed_by_id + data[:resolution] = I18n.t("reports.flags_status.values.agreed") + elsif row.disagreed_by_id + data[:resolution] = I18n.t("reports.flags_status.values.disagreed") + elsif row.deferred_by_id + data[:resolution] = I18n.t("reports.flags_status.values.deferred") + else + data[:resolution] = I18n.t("reports.flags_status.values.no_action") + end + data[:response_time] = row.responded_at ? row.responded_at - row.created_at : nil + report.data << data end - - if row.poster_id - data[:poster_username] = row.poster_username - data[:poster_id] = row.poster_id - data[:poster_avatar_template] = User.avatar_template(row.poster_username, row.poster_avatar_id) - end - - if row.flagger_id - data[:flagger_id] = row.flagger_id - data[:flagger_username] = row.flagger_username - data[:flagger_avatar_template] = User.avatar_template(row.flagger_username, row.flagger_avatar_id) - end - - if row.agreed_by_id - data[:resolution] = I18n.t("reports.flags_status.values.agreed") - elsif row.disagreed_by_id - data[:resolution] = I18n.t("reports.flags_status.values.disagreed") - elsif row.deferred_by_id - data[:resolution] = I18n.t("reports.flags_status.values.deferred") - else - data[:resolution] = I18n.t("reports.flags_status.values.no_action") - end - data[:response_time] = row.responded_at ? row.responded_at - row.created_at : nil - report.data << data - end end end end diff --git a/app/models/concerns/reports/likes.rb b/app/models/concerns/reports/likes.rb index 0815a9e79c7..9f5f3375e49 100644 --- a/app/models/concerns/reports/likes.rb +++ b/app/models/concerns/reports/likes.rb @@ -5,7 +5,7 @@ module Reports::Likes class_methods do def report_likes(report) - report.icon = 'heart' + report.icon = "heart" post_action_report report, PostActionType.types[:like] end diff --git a/app/models/concerns/reports/mobile_visits.rb b/app/models/concerns/reports/mobile_visits.rb index a5e331891a3..652b12ee468 100644 --- a/app/models/concerns/reports/mobile_visits.rb +++ b/app/models/concerns/reports/mobile_visits.rb @@ -7,7 +7,15 @@ module Reports::MobileVisits def report_mobile_visits(report) basic_report_about report, UserVisit, :mobile_by_day, report.start_date, report.end_date report.total = UserVisit.where(mobile: true).count - report.prev30Days = UserVisit.where(mobile: true).where("visited_at >= ? and visited_at < ?", report.start_date - 30.days, report.start_date).count + report.prev30Days = + UserVisit + .where(mobile: true) + .where( + "visited_at >= ? and visited_at < ?", + report.start_date - 30.days, + report.start_date, + ) + .count end end end diff --git a/app/models/concerns/reports/moderator_warning_private_messages.rb b/app/models/concerns/reports/moderator_warning_private_messages.rb index ba995594288..16047034d7b 100644 --- a/app/models/concerns/reports/moderator_warning_private_messages.rb +++ b/app/models/concerns/reports/moderator_warning_private_messages.rb @@ -5,7 +5,7 @@ module Reports::ModeratorWarningPrivateMessages class_methods do def report_moderator_warning_private_messages(report) - report.icon = 'envelope' + report.icon = "envelope" private_messages_report report, TopicSubtype.moderator_warning end end diff --git a/app/models/concerns/reports/moderators_activity.rb b/app/models/concerns/reports/moderators_activity.rb index 135e4f09460..b66bf0df74d 100644 --- a/app/models/concerns/reports/moderators_activity.rb +++ b/app/models/concerns/reports/moderators_activity.rb @@ -18,33 +18,33 @@ module Reports::ModeratorsActivity { property: :flag_count, type: :number, - title: I18n.t("reports.moderators_activity.labels.flag_count") + title: I18n.t("reports.moderators_activity.labels.flag_count"), }, { type: :seconds, property: :time_read, - title: I18n.t("reports.moderators_activity.labels.time_read") + title: I18n.t("reports.moderators_activity.labels.time_read"), }, { property: :topic_count, type: :number, - title: I18n.t("reports.moderators_activity.labels.topic_count") + title: I18n.t("reports.moderators_activity.labels.topic_count"), }, { property: :pm_count, type: :number, - title: I18n.t("reports.moderators_activity.labels.pm_count") + title: I18n.t("reports.moderators_activity.labels.pm_count"), }, { property: :post_count, type: :number, - title: I18n.t("reports.moderators_activity.labels.post_count") + title: I18n.t("reports.moderators_activity.labels.post_count"), }, { property: :revision_count, type: :number, - title: I18n.t("reports.moderators_activity.labels.revision_count") - } + title: I18n.t("reports.moderators_activity.labels.revision_count"), + }, ] report.modes = [:table] @@ -75,7 +75,7 @@ module Reports::ModeratorsActivity SELECT agreed_by_id, disagreed_by_id FROM post_actions - WHERE post_action_type_id IN (#{PostActionType.flag_types_without_custom.values.join(',')}) + WHERE post_action_type_id IN (#{PostActionType.flag_types_without_custom.values.join(",")}) AND created_at >= '#{report.start_date}' AND created_at <= '#{report.end_date}' ), @@ -173,19 +173,21 @@ module Reports::ModeratorsActivity ORDER BY m.username SQL - DB.query(query).each do |row| - mod = {} - mod[:username] = row.username - mod[:user_id] = row.user_id - mod[:user_avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) - mod[:time_read] = row.time_read - mod[:flag_count] = row.flag_count - mod[:revision_count] = row.revision_count - mod[:topic_count] = row.topic_count - mod[:post_count] = row.post_count - mod[:pm_count] = row.pm_count - report.data << mod - end + DB + .query(query) + .each do |row| + mod = {} + mod[:username] = row.username + mod[:user_id] = row.user_id + mod[:user_avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) + mod[:time_read] = row.time_read + mod[:flag_count] = row.flag_count + mod[:revision_count] = row.revision_count + mod[:topic_count] = row.topic_count + mod[:post_count] = row.post_count + mod[:pm_count] = row.pm_count + report.data << mod + end end end end diff --git a/app/models/concerns/reports/new_contributors.rb b/app/models/concerns/reports/new_contributors.rb index c8a8afe2094..89b2fdda173 100644 --- a/app/models/concerns/reports/new_contributors.rb +++ b/app/models/concerns/reports/new_contributors.rb @@ -10,23 +10,21 @@ module Reports::NewContributors data = User.real.count_by_first_post(report.start_date, report.end_date) if report.facets.include?(:prev30Days) - prev30DaysData = User.real.count_by_first_post(report.start_date - 30.days, report.start_date) + prev30DaysData = + User.real.count_by_first_post(report.start_date - 30.days, report.start_date) report.prev30Days = prev30DaysData.sum { |k, v| v } end - if report.facets.include?(:total) - report.total = User.real.count_by_first_post - end + report.total = User.real.count_by_first_post if report.facets.include?(:total) if report.facets.include?(:prev_period) - prev_period_data = User.real.count_by_first_post(report.prev_start_date, report.prev_end_date) + prev_period_data = + User.real.count_by_first_post(report.prev_start_date, report.prev_end_date) report.prev_period = prev_period_data.sum { |k, v| v } # report.prev_data = prev_period_data.map { |k, v| { x: k, y: v } } end - data.each do |key, value| - report.data << { x: key, y: value } - end + data.each { |key, value| report.data << { x: key, y: value } } end end end diff --git a/app/models/concerns/reports/notify_moderators_private_messages.rb b/app/models/concerns/reports/notify_moderators_private_messages.rb index 77c2df12d56..e7dcfed2e2b 100644 --- a/app/models/concerns/reports/notify_moderators_private_messages.rb +++ b/app/models/concerns/reports/notify_moderators_private_messages.rb @@ -5,7 +5,7 @@ module Reports::NotifyModeratorsPrivateMessages class_methods do def report_notify_moderators_private_messages(report) - report.icon = 'envelope' + report.icon = "envelope" private_messages_report report, TopicSubtype.notify_moderators end end diff --git a/app/models/concerns/reports/notify_user_private_messages.rb b/app/models/concerns/reports/notify_user_private_messages.rb index aac3dca4ccb..655fa812aff 100644 --- a/app/models/concerns/reports/notify_user_private_messages.rb +++ b/app/models/concerns/reports/notify_user_private_messages.rb @@ -5,7 +5,7 @@ module Reports::NotifyUserPrivateMessages class_methods do def report_notify_user_private_messages(report) - report.icon = 'envelope' + report.icon = "envelope" private_messages_report report, TopicSubtype.notify_user end end diff --git a/app/models/concerns/reports/post_edits.rb b/app/models/concerns/reports/post_edits.rb index 554fea563af..af97ddc19a8 100644 --- a/app/models/concerns/reports/post_edits.rb +++ b/app/models/concerns/reports/post_edits.rb @@ -6,7 +6,7 @@ module Reports::PostEdits class_methods do def report_post_edits(report) category_id, include_subcategories = report.add_category_filter - editor_username = report.filters['editor'] + editor_username = report.filters["editor"] report.modes = [:table] @@ -14,16 +14,16 @@ module Reports::PostEdits { type: :date, property: :created_at, - title: I18n.t("reports.post_edits.labels.edited_at") + title: I18n.t("reports.post_edits.labels.edited_at"), }, { type: :post, properties: { topic_id: :topic_id, number: :post_number, - truncated_raw: :post_raw + truncated_raw: :post_raw, }, - title: I18n.t("reports.post_edits.labels.post") + title: I18n.t("reports.post_edits.labels.post"), }, { type: :user, @@ -32,7 +32,7 @@ module Reports::PostEdits id: :editor_id, avatar: :editor_avatar_template, }, - title: I18n.t("reports.post_edits.labels.editor") + title: I18n.t("reports.post_edits.labels.editor"), }, { type: :user, @@ -41,12 +41,12 @@ module Reports::PostEdits id: :author_id, avatar: :author_avatar_template, }, - title: I18n.t("reports.post_edits.labels.author") + title: I18n.t("reports.post_edits.labels.author"), }, { type: :text, property: :edit_reason, - title: I18n.t("reports.post_edits.labels.edit_reason") + title: I18n.t("reports.post_edits.labels.edit_reason"), }, ] @@ -105,10 +105,16 @@ module Reports::PostEdits revision = {} revision[:editor_id] = r.editor_id revision[:editor_username] = r.editor_username - revision[:editor_avatar_template] = User.avatar_template(r.editor_username, r.editor_avatar_id) + revision[:editor_avatar_template] = User.avatar_template( + r.editor_username, + r.editor_avatar_id, + ) revision[:author_id] = r.author_id revision[:author_username] = r.author_username - revision[:author_avatar_template] = User.avatar_template(r.author_username, r.author_avatar_id) + revision[:author_avatar_template] = User.avatar_template( + r.author_username, + r.author_avatar_id, + ) revision[:edit_reason] = r.revision_version == r.post_version ? r.edit_reason : nil revision[:created_at] = r.created_at revision[:post_raw] = r.post_raw diff --git a/app/models/concerns/reports/posts.rb b/app/models/concerns/reports/posts.rb index 5a692d8f5a3..b2ac5199099 100644 --- a/app/models/concerns/reports/posts.rb +++ b/app/models/concerns/reports/posts.rb @@ -5,22 +5,32 @@ module Reports::Posts class_methods do def report_posts(report) - report.modes = [:table, :chart] + report.modes = %i[table chart] category_id, include_subcategories = report.add_category_filter - basic_report_about report, Post, :public_posts_count_per_day, report.start_date, report.end_date, category_id, include_subcategories + basic_report_about report, + Post, + :public_posts_count_per_day, + report.start_date, + report.end_date, + category_id, + include_subcategories countable = Post.public_posts.where(post_type: Post.types[:regular]) if category_id if include_subcategories - countable = countable.joins(:topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id)) + countable = + countable.joins(:topic).where( + "topics.category_id IN (?)", + Category.subcategory_ids(category_id), + ) else - countable = countable.joins(:topic).where('topics.category_id = ?', category_id) + countable = countable.joins(:topic).where("topics.category_id = ?", category_id) end end - add_counts report, countable, 'posts.created_at' + add_counts report, countable, "posts.created_at" end end end diff --git a/app/models/concerns/reports/profile_views.rb b/app/models/concerns/reports/profile_views.rb index 4f630a92c87..0fccea83966 100644 --- a/app/models/concerns/reports/profile_views.rb +++ b/app/models/concerns/reports/profile_views.rb @@ -6,14 +6,24 @@ module Reports::ProfileViews class_methods do def report_profile_views(report) group_filter = report.filters.dig(:group) - report.add_filter('group', type: 'group', default: group_filter) + report.add_filter("group", type: "group", default: group_filter) start_date = report.start_date end_date = report.end_date - basic_report_about report, UserProfileView, :profile_views_by_day, start_date, end_date, group_filter + basic_report_about report, + UserProfileView, + :profile_views_by_day, + start_date, + end_date, + group_filter report.total = UserProfile.sum(:views) - report.prev30Days = UserProfileView.where('viewed_at >= ? AND viewed_at < ?', start_date - 30.days, start_date + 1).count + report.prev30Days = + UserProfileView.where( + "viewed_at >= ? AND viewed_at < ?", + start_date - 30.days, + start_date + 1, + ).count end end end diff --git a/app/models/concerns/reports/signups.rb b/app/models/concerns/reports/signups.rb index 380fb0c9bcc..200d229dfe0 100644 --- a/app/models/concerns/reports/signups.rb +++ b/app/models/concerns/reports/signups.rb @@ -5,14 +5,19 @@ module Reports::Signups class_methods do def report_signups(report) - report.icon = 'user-plus' + report.icon = "user-plus" group_filter = report.filters.dig(:group) - report.add_filter('group', type: 'group', default: group_filter) + report.add_filter("group", type: "group", default: group_filter) if group_filter - basic_report_about report, User.real, :count_by_signup_date, report.start_date, report.end_date, group_filter - add_counts report, User.real, 'users.created_at' + basic_report_about report, + User.real, + :count_by_signup_date, + report.start_date, + report.end_date, + group_filter + add_counts report, User.real, "users.created_at" else report_about report, User.real, :count_by_signup_date end diff --git a/app/models/concerns/reports/staff_logins.rb b/app/models/concerns/reports/staff_logins.rb index 1636150baa0..d65c2e2df05 100644 --- a/app/models/concerns/reports/staff_logins.rb +++ b/app/models/concerns/reports/staff_logins.rb @@ -17,17 +17,14 @@ module Reports::StaffLogins id: :user_id, avatar: :avatar_template, }, - title: I18n.t("reports.staff_logins.labels.user") - }, - { - property: :location, - title: I18n.t("reports.staff_logins.labels.location") + title: I18n.t("reports.staff_logins.labels.user"), }, + { property: :location, title: I18n.t("reports.staff_logins.labels.location") }, { property: :created_at, type: :precise_date, - title: I18n.t("reports.staff_logins.labels.login_at") - } + title: I18n.t("reports.staff_logins.labels.login_at"), + }, ] sql = <<~SQL @@ -40,7 +37,7 @@ module Reports::StaffLogins FROM ( SELECT DISTINCT ON (t.client_ip, t.user_id) t.client_ip, t.user_id, t.created_at FROM user_auth_token_logs t - WHERE t.user_id IN (#{User.admins.pluck(:id).join(',')}) + WHERE t.user_id IN (#{User.admins.pluck(:id).join(",")}) AND t.created_at >= :start_date AND t.created_at <= :end_date ORDER BY t.client_ip, t.user_id, t.created_at DESC @@ -50,16 +47,18 @@ module Reports::StaffLogins ORDER BY created_at DESC SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - data = {} - data[:avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) - data[:user_id] = row.user_id - data[:username] = row.username - data[:location] = DiscourseIpInfo.get(row.client_ip)[:location] - data[:created_at] = row.created_at + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + data = {} + data[:avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) + data[:user_id] = row.user_id + data[:username] = row.username + data[:location] = DiscourseIpInfo.get(row.client_ip)[:location] + data[:created_at] = row.created_at - report.data << data - end + report.data << data + end end end end diff --git a/app/models/concerns/reports/storage_stats.rb b/app/models/concerns/reports/storage_stats.rb index c95c5371351..1efb79057fa 100644 --- a/app/models/concerns/reports/storage_stats.rb +++ b/app/models/concerns/reports/storage_stats.rb @@ -5,18 +5,19 @@ module Reports::StorageStats class_methods do def report_storage_stats(report) - backup_stats = begin - BackupRestore::BackupStore.create.stats - rescue BackupRestore::BackupStore::StorageError - nil - end + backup_stats = + begin + BackupRestore::BackupStore.create.stats + rescue BackupRestore::BackupStore::StorageError + nil + end report.data = { backups: backup_stats, uploads: { used_bytes: DiskSpace.uploads_used_bytes, - free_bytes: DiskSpace.uploads_free_bytes - } + free_bytes: DiskSpace.uploads_free_bytes, + }, } end end diff --git a/app/models/concerns/reports/suspicious_logins.rb b/app/models/concerns/reports/suspicious_logins.rb index 47a726e558e..cd07d427d11 100644 --- a/app/models/concerns/reports/suspicious_logins.rb +++ b/app/models/concerns/reports/suspicious_logins.rb @@ -15,32 +15,17 @@ module Reports::SuspiciousLogins id: :user_id, avatar: :avatar_template, }, - title: I18n.t("reports.suspicious_logins.labels.user") - }, - { - property: :client_ip, - title: I18n.t("reports.suspicious_logins.labels.client_ip") - }, - { - property: :location, - title: I18n.t("reports.suspicious_logins.labels.location") - }, - { - property: :browser, - title: I18n.t("reports.suspicious_logins.labels.browser") - }, - { - property: :device, - title: I18n.t("reports.suspicious_logins.labels.device") - }, - { - property: :os, - title: I18n.t("reports.suspicious_logins.labels.os") + title: I18n.t("reports.suspicious_logins.labels.user"), }, + { property: :client_ip, title: I18n.t("reports.suspicious_logins.labels.client_ip") }, + { property: :location, title: I18n.t("reports.suspicious_logins.labels.location") }, + { property: :browser, title: I18n.t("reports.suspicious_logins.labels.browser") }, + { property: :device, title: I18n.t("reports.suspicious_logins.labels.device") }, + { property: :os, title: I18n.t("reports.suspicious_logins.labels.os") }, { type: :date, property: :login_time, - title: I18n.t("reports.suspicious_logins.labels.login_time") + title: I18n.t("reports.suspicious_logins.labels.login_time"), }, ] @@ -56,26 +41,28 @@ module Reports::SuspiciousLogins ORDER BY t.created_at DESC SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - data = {} + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + data = {} - ipinfo = DiscourseIpInfo.get(row.client_ip) - browser = BrowserDetection.browser(row.user_agent) - device = BrowserDetection.device(row.user_agent) - os = BrowserDetection.os(row.user_agent) + ipinfo = DiscourseIpInfo.get(row.client_ip) + browser = BrowserDetection.browser(row.user_agent) + device = BrowserDetection.device(row.user_agent) + os = BrowserDetection.os(row.user_agent) - data[:username] = row.username - data[:user_id] = row.user_id - data[:avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) - data[:client_ip] = row.client_ip.to_s - data[:location] = ipinfo[:location] - data[:browser] = I18n.t("user_auth_tokens.browser.#{browser}") - data[:device] = I18n.t("user_auth_tokens.device.#{device}") - data[:os] = I18n.t("user_auth_tokens.os.#{os}") - data[:login_time] = row.login_time + data[:username] = row.username + data[:user_id] = row.user_id + data[:avatar_template] = User.avatar_template(row.username, row.uploaded_avatar_id) + data[:client_ip] = row.client_ip.to_s + data[:location] = ipinfo[:location] + data[:browser] = I18n.t("user_auth_tokens.browser.#{browser}") + data[:device] = I18n.t("user_auth_tokens.device.#{device}") + data[:os] = I18n.t("user_auth_tokens.os.#{os}") + data[:login_time] = row.login_time - report.data << data - end + report.data << data + end end end end diff --git a/app/models/concerns/reports/system_private_messages.rb b/app/models/concerns/reports/system_private_messages.rb index 0c046250784..8fd142b0e74 100644 --- a/app/models/concerns/reports/system_private_messages.rb +++ b/app/models/concerns/reports/system_private_messages.rb @@ -5,7 +5,7 @@ module Reports::SystemPrivateMessages class_methods do def report_system_private_messages(report) - report.icon = 'envelope' + report.icon = "envelope" private_messages_report report, TopicSubtype.system_message end end diff --git a/app/models/concerns/reports/time_to_first_response.rb b/app/models/concerns/reports/time_to_first_response.rb index 6217591ab5b..bd710b11eb3 100644 --- a/app/models/concerns/reports/time_to_first_response.rb +++ b/app/models/concerns/reports/time_to_first_response.rb @@ -8,16 +8,27 @@ module Reports::TimeToFirstResponse category_filter = report.filters.dig(:category) category_id, include_subcategories = report.add_category_filter - report.icon = 'reply' + report.icon = "reply" report.higher_is_better = false report.data = [] report.average = true - Topic.time_to_first_response_per_day(report.start_date, report.end_date, category_id: category_id, include_subcategories: include_subcategories).each do |r| - report.data << { x: r['date'], y: r['hours'].to_f.round(2) } - end + Topic + .time_to_first_response_per_day( + report.start_date, + report.end_date, + category_id: category_id, + include_subcategories: include_subcategories, + ) + .each { |r| report.data << { x: r["date"], y: r["hours"].to_f.round(2) } } - report.prev30Days = Topic.time_to_first_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_id, include_subcategories: include_subcategories) + report.prev30Days = + Topic.time_to_first_response_total( + start_date: report.start_date - 30.days, + end_date: report.start_date, + category_id: category_id, + include_subcategories: include_subcategories, + ) end end end diff --git a/app/models/concerns/reports/top_ignored_users.rb b/app/models/concerns/reports/top_ignored_users.rb index 3b02f081956..3403de992e7 100644 --- a/app/models/concerns/reports/top_ignored_users.rb +++ b/app/models/concerns/reports/top_ignored_users.rb @@ -15,22 +15,18 @@ module Reports::TopIgnoredUsers username: :ignored_username, avatar: :ignored_user_avatar_template, }, - title: I18n.t("reports.top_ignored_users.labels.ignored_user") + title: I18n.t("reports.top_ignored_users.labels.ignored_user"), }, { type: :number, - properties: [ - :ignores_count, - ], - title: I18n.t("reports.top_ignored_users.labels.ignores_count") + properties: [:ignores_count], + title: I18n.t("reports.top_ignored_users.labels.ignores_count"), }, { type: :number, - properties: [ - :mutes_count, - ], - title: I18n.t("reports.top_ignored_users.labels.mutes_count") - } + properties: [:mutes_count], + title: I18n.t("reports.top_ignored_users.labels.mutes_count"), + }, ] report.data = [] @@ -69,15 +65,18 @@ module Reports::TopIgnoredUsers ORDER BY total DESC SQL - DB.query(sql, limit: report.limit || 250).each do |row| - report.data << { - ignored_user_id: row.user_id, - ignored_username: row.username, - ignored_user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), - ignores_count: row.ignores_count, - mutes_count: row.mutes_count, - } - end + DB + .query(sql, limit: report.limit || 250) + .each do |row| + report.data << { + ignored_user_id: row.user_id, + ignored_username: row.username, + ignored_user_avatar_template: + User.avatar_template(row.username, row.uploaded_avatar_id), + ignores_count: row.ignores_count, + mutes_count: row.mutes_count, + } + end end end end diff --git a/app/models/concerns/reports/top_referred_topics.rb b/app/models/concerns/reports/top_referred_topics.rb index 348b6537c11..c8cacb79297 100644 --- a/app/models/concerns/reports/top_referred_topics.rb +++ b/app/models/concerns/reports/top_referred_topics.rb @@ -14,15 +14,15 @@ module Reports::TopReferredTopics type: :topic, properties: { title: :topic_title, - id: :topic_id + id: :topic_id, }, - title: I18n.t('reports.top_referred_topics.labels.topic') + title: I18n.t("reports.top_referred_topics.labels.topic"), }, { property: :num_clicks, type: :number, - title: I18n.t('reports.top_referred_topics.labels.num_clicks') - } + title: I18n.t("reports.top_referred_topics.labels.num_clicks"), + }, ] options = { @@ -30,7 +30,7 @@ module Reports::TopReferredTopics start_date: report.start_date, limit: report.limit || 8, category_id: category_id, - include_subcategories: include_subcategories + include_subcategories: include_subcategories, } result = nil result = IncomingLinksReport.find(:top_referred_topics, options) diff --git a/app/models/concerns/reports/top_referrers.rb b/app/models/concerns/reports/top_referrers.rb index 3e8483ef535..3454f17632b 100644 --- a/app/models/concerns/reports/top_referrers.rb +++ b/app/models/concerns/reports/top_referrers.rb @@ -15,24 +15,24 @@ module Reports::TopReferrers id: :user_id, avatar: :user_avatar_template, }, - title: I18n.t("reports.top_referrers.labels.user") + title: I18n.t("reports.top_referrers.labels.user"), }, { property: :num_clicks, type: :number, - title: I18n.t("reports.top_referrers.labels.num_clicks") + title: I18n.t("reports.top_referrers.labels.num_clicks"), }, { property: :num_topics, type: :number, - title: I18n.t("reports.top_referrers.labels.num_topics") - } + title: I18n.t("reports.top_referrers.labels.num_topics"), + }, ] options = { end_date: report.end_date, start_date: report.start_date, - limit: report.limit || 8 + limit: report.limit || 8, } result = IncomingLinksReport.find(:top_referrers, options) diff --git a/app/models/concerns/reports/top_traffic_sources.rb b/app/models/concerns/reports/top_traffic_sources.rb index b6602bd1d2b..a1b313f01f9 100644 --- a/app/models/concerns/reports/top_traffic_sources.rb +++ b/app/models/concerns/reports/top_traffic_sources.rb @@ -10,20 +10,17 @@ module Reports::TopTrafficSources report.modes = [:table] report.labels = [ - { - property: :domain, - title: I18n.t('reports.top_traffic_sources.labels.domain') - }, + { property: :domain, title: I18n.t("reports.top_traffic_sources.labels.domain") }, { property: :num_clicks, type: :number, - title: I18n.t('reports.top_traffic_sources.labels.num_clicks') + title: I18n.t("reports.top_traffic_sources.labels.num_clicks"), }, { property: :num_topics, type: :number, - title: I18n.t('reports.top_traffic_sources.labels.num_topics') - } + title: I18n.t("reports.top_traffic_sources.labels.num_topics"), + }, ] options = { @@ -31,7 +28,7 @@ module Reports::TopTrafficSources start_date: report.start_date, limit: report.limit || 8, category_id: category_id, - include_subcategories: include_subcategories + include_subcategories: include_subcategories, } result = IncomingLinksReport.find(:top_traffic_sources, options) diff --git a/app/models/concerns/reports/top_uploads.rb b/app/models/concerns/reports/top_uploads.rb index a3a53ce969c..d5e2955b7d9 100644 --- a/app/models/concerns/reports/top_uploads.rb +++ b/app/models/concerns/reports/top_uploads.rb @@ -8,20 +8,18 @@ module Reports::TopUploads report.modes = [:table] extension_filter = report.filters.dig(:file_extension) - report.add_filter('file_extension', - type: 'list', - default: extension_filter || 'any', - choices: (SiteSetting.authorized_extensions.split('|') + Array(extension_filter)).uniq + report.add_filter( + "file_extension", + type: "list", + default: extension_filter || "any", + choices: (SiteSetting.authorized_extensions.split("|") + Array(extension_filter)).uniq, ) report.labels = [ { type: :link, - properties: [ - :file_url, - :file_name, - ], - title: I18n.t("reports.top_uploads.labels.filename") + properties: %i[file_url file_name], + title: I18n.t("reports.top_uploads.labels.filename"), }, { type: :user, @@ -30,18 +28,14 @@ module Reports::TopUploads id: :author_id, avatar: :author_avatar_template, }, - title: I18n.t("reports.top_uploads.labels.author") + title: I18n.t("reports.top_uploads.labels.author"), }, { type: :text, property: :extension, - title: I18n.t("reports.top_uploads.labels.extension") - }, - { - type: :bytes, - property: :filesize, - title: I18n.t("reports.top_uploads.labels.filesize") + title: I18n.t("reports.top_uploads.labels.extension"), }, + { type: :bytes, property: :filesize, title: I18n.t("reports.top_uploads.labels.filesize") }, ] report.data = [] @@ -64,12 +58,15 @@ module Reports::TopUploads SQL builder = DB.build(sql) - builder.where("up.id > :seeded_id_threshold", seeded_id_threshold: Upload::SEEDED_ID_THRESHOLD) + builder.where( + "up.id > :seeded_id_threshold", + seeded_id_threshold: Upload::SEEDED_ID_THRESHOLD, + ) builder.where("up.created_at >= :start_date", start_date: report.start_date) builder.where("up.created_at < :end_date", end_date: report.end_date) if extension_filter - builder.where("up.extension = :extension", extension: extension_filter.sub(/^\./, '')) + builder.where("up.extension = :extension", extension: extension_filter.sub(/^\./, "")) end builder.query.each do |row| diff --git a/app/models/concerns/reports/top_users_by_likes_received.rb b/app/models/concerns/reports/top_users_by_likes_received.rb index 2f1b7c11329..2861cbefc51 100644 --- a/app/models/concerns/reports/top_users_by_likes_received.rb +++ b/app/models/concerns/reports/top_users_by_likes_received.rb @@ -5,7 +5,7 @@ module Reports::TopUsersByLikesReceived class_methods do def report_top_users_by_likes_received(report) - report.icon = 'heart' + report.icon = "heart" report.data = [] report.modes = [:table] @@ -20,12 +20,12 @@ module Reports::TopUsersByLikesReceived username: :username, avatar: :user_avatar_template, }, - title: I18n.t("reports.top_users_by_likes_received.labels.user") + title: I18n.t("reports.top_users_by_likes_received.labels.user"), }, { type: :number, property: :qtt_like, - title: I18n.t("reports.top_users_by_likes_received.labels.qtt_like") + title: I18n.t("reports.top_users_by_likes_received.labels.qtt_like"), }, ] @@ -44,15 +44,16 @@ module Reports::TopUsersByLikesReceived LIMIT 10 SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - report.data << { - user_id: row.user_id, - username: row.username, - user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), - qtt_like: row.qtt_like, - } - end - + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + qtt_like: row.qtt_like, + } + end end end end diff --git a/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb b/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb index 0bf6a7348c4..a6a42c72181 100644 --- a/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb +++ b/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb @@ -5,7 +5,7 @@ module Reports::TopUsersByLikesReceivedFromAVarietyOfPeople class_methods do def report_top_users_by_likes_received_from_a_variety_of_people(report) - report.icon = 'heart' + report.icon = "heart" report.data = [] report.modes = [:table] @@ -20,12 +20,13 @@ module Reports::TopUsersByLikesReceivedFromAVarietyOfPeople username: :username, avatar: :user_avatar_template, }, - title: I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.user") + title: I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.user"), }, { type: :number, property: :qtt_like, - title: I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.qtt_like") + title: + I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.qtt_like"), }, ] @@ -46,15 +47,16 @@ module Reports::TopUsersByLikesReceivedFromAVarietyOfPeople LIMIT 10 SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - report.data << { - user_id: row.user_id, - username: row.username, - user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), - qtt_like: row.qtt_like, - } - end - + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + qtt_like: row.qtt_like, + } + end end end end diff --git a/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb b/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb index d0b41d57d63..0daad06783f 100644 --- a/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb +++ b/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb @@ -5,7 +5,7 @@ module Reports::TopUsersByLikesReceivedFromInferiorTrustLevel class_methods do def report_top_users_by_likes_received_from_inferior_trust_level(report) - report.icon = 'heart' + report.icon = "heart" report.data = [] report.modes = [:table] @@ -20,17 +20,22 @@ module Reports::TopUsersByLikesReceivedFromInferiorTrustLevel username: :username, avatar: :user_avatar_template, }, - title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.user") + title: + I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.user"), }, { type: :number, property: :trust_level, - title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.trust_level") + title: + I18n.t( + "reports.top_users_by_likes_received_from_inferior_trust_level.labels.trust_level", + ), }, { type: :number, property: :qtt_like, - title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.qtt_like") + title: + I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.qtt_like"), }, ] @@ -56,16 +61,17 @@ module Reports::TopUsersByLikesReceivedFromInferiorTrustLevel WHERE rank <= 10 SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - report.data << { - user_id: row.user_id, - username: row.username, - user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), - trust_level: row.trust_level, - qtt_like: row.qtt_like, - } - end - + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + trust_level: row.trust_level, + qtt_like: row.qtt_like, + } + end end end end diff --git a/app/models/concerns/reports/topics.rb b/app/models/concerns/reports/topics.rb index 19c527b3140..19412a6a4da 100644 --- a/app/models/concerns/reports/topics.rb +++ b/app/models/concerns/reports/topics.rb @@ -7,12 +7,21 @@ module Reports::Topics def report_topics(report) category_id, include_subcategories = report.add_category_filter - basic_report_about report, Topic, :listable_count_per_day, report.start_date, report.end_date, category_id, include_subcategories + basic_report_about report, + Topic, + :listable_count_per_day, + report.start_date, + report.end_date, + category_id, + include_subcategories countable = Topic.listable_topics - countable = countable.where(category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id) if category_id + countable = + countable.where( + category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id, + ) if category_id - add_counts report, countable, 'topics.created_at' + add_counts report, countable, "topics.created_at" end end end diff --git a/app/models/concerns/reports/topics_with_no_response.rb b/app/models/concerns/reports/topics_with_no_response.rb index e09c3f08c5b..4cd4b762a27 100644 --- a/app/models/concerns/reports/topics_with_no_response.rb +++ b/app/models/concerns/reports/topics_with_no_response.rb @@ -8,13 +8,28 @@ module Reports::TopicsWithNoResponse category_id, include_subcategories = report.add_category_filter report.data = [] - Topic.with_no_response_per_day(report.start_date, report.end_date, category_id, include_subcategories).each do |r| - report.data << { x: r['date'], y: r['count'].to_i } - end + Topic + .with_no_response_per_day( + report.start_date, + report.end_date, + category_id, + include_subcategories, + ) + .each { |r| report.data << { x: r["date"], y: r["count"].to_i } } - report.total = Topic.with_no_response_total(category_id: category_id, include_subcategories: include_subcategories) + report.total = + Topic.with_no_response_total( + category_id: category_id, + include_subcategories: include_subcategories, + ) - report.prev30Days = Topic.with_no_response_total(start_date: report.start_date - 30.days, end_date: report.start_date, category_id: category_id, include_subcategories: include_subcategories) + report.prev30Days = + Topic.with_no_response_total( + start_date: report.start_date - 30.days, + end_date: report.start_date, + category_id: category_id, + include_subcategories: include_subcategories, + ) end end end diff --git a/app/models/concerns/reports/trending_search.rb b/app/models/concerns/reports/trending_search.rb index b30e522744c..8d4f5ff38a3 100644 --- a/app/models/concerns/reports/trending_search.rb +++ b/app/models/concerns/reports/trending_search.rb @@ -6,38 +6,28 @@ module Reports::TrendingSearch class_methods do def report_trending_search(report) report.labels = [ - { - property: :term, - type: :text, - title: I18n.t("reports.trending_search.labels.term") - }, + { property: :term, type: :text, title: I18n.t("reports.trending_search.labels.term") }, { property: :searches, type: :number, - title: I18n.t("reports.trending_search.labels.searches") + title: I18n.t("reports.trending_search.labels.searches"), }, { type: :percent, property: :ctr, - title: I18n.t("reports.trending_search.labels.click_through") - } + title: I18n.t("reports.trending_search.labels.click_through"), + }, ] report.data = [] report.modes = [:table] - trends = SearchLog.trending_from(report.start_date, - end_date: report.end_date, - limit: report.limit - ) + trends = + SearchLog.trending_from(report.start_date, end_date: report.end_date, limit: report.limit) trends.each do |trend| - report.data << { - term: trend.term, - searches: trend.searches, - ctr: trend.ctr - } + report.data << { term: trend.term, searches: trend.searches, ctr: trend.ctr } end end end diff --git a/app/models/concerns/reports/trust_level_growth.rb b/app/models/concerns/reports/trust_level_growth.rb index 0071b579ff1..d1f82f06891 100644 --- a/app/models/concerns/reports/trust_level_growth.rb +++ b/app/models/concerns/reports/trust_level_growth.rb @@ -7,12 +7,7 @@ module Reports::TrustLevelGrowth def report_trust_level_growth(report) report.modes = [:stacked_chart] - filters = %w[ - tl1_reached - tl2_reached - tl3_reached - tl4_reached - ] + filters = %w[tl1_reached tl2_reached tl3_reached tl4_reached] sql = <<~SQL SELECT @@ -42,41 +37,36 @@ module Reports::TrustLevelGrowth ORDER BY date(created_at) SQL - data = Hash[ filters.collect { |x| [x, []] } ] + data = Hash[filters.collect { |x| [x, []] }] builder = DB.build(sql) builder.query.each do |row| filters.each do |filter| data[filter] << { x: row.date.strftime("%Y-%m-%d"), - y: row.instance_variable_get("@#{filter}") + y: row.instance_variable_get("@#{filter}"), } end end - tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc' - quaternary = ColorScheme.hex_for_name('quaternary') || 'e45735' + tertiary = ColorScheme.hex_for_name("tertiary") || "0088cc" + quaternary = ColorScheme.hex_for_name("quaternary") || "e45735" - requests = filters.map do |filter| - color = report.rgba_color(quaternary) + requests = + filters.map do |filter| + color = report.rgba_color(quaternary) - if filter == "tl1_reached" - color = report.lighten_color(tertiary, 0.25) - end - if filter == "tl2_reached" - color = report.rgba_color(tertiary) - end - if filter == "tl3_reached" - color = report.lighten_color(quaternary, 0.25) - end + color = report.lighten_color(tertiary, 0.25) if filter == "tl1_reached" + color = report.rgba_color(tertiary) if filter == "tl2_reached" + color = report.lighten_color(quaternary, 0.25) if filter == "tl3_reached" - { - req: filter, - label: I18n.t("reports.trust_level_growth.xaxis.#{filter}"), - color: color, - data: data[filter] - } - end + { + req: filter, + label: I18n.t("reports.trust_level_growth.xaxis.#{filter}"), + color: color, + data: data[filter], + } + end report.data = requests end diff --git a/app/models/concerns/reports/user_flagging_ratio.rb b/app/models/concerns/reports/user_flagging_ratio.rb index 96c2439106b..0147792a8d8 100644 --- a/app/models/concerns/reports/user_flagging_ratio.rb +++ b/app/models/concerns/reports/user_flagging_ratio.rb @@ -19,27 +19,27 @@ module Reports::UserFlaggingRatio id: :user_id, avatar: :avatar_template, }, - title: I18n.t("reports.user_flagging_ratio.labels.user") + title: I18n.t("reports.user_flagging_ratio.labels.user"), }, { type: :number, property: :disagreed_flags, - title: I18n.t("reports.user_flagging_ratio.labels.disagreed_flags") + title: I18n.t("reports.user_flagging_ratio.labels.disagreed_flags"), }, { type: :number, property: :agreed_flags, - title: I18n.t("reports.user_flagging_ratio.labels.agreed_flags") + title: I18n.t("reports.user_flagging_ratio.labels.agreed_flags"), }, { type: :number, property: :ignored_flags, - title: I18n.t("reports.user_flagging_ratio.labels.ignored_flags") + title: I18n.t("reports.user_flagging_ratio.labels.ignored_flags"), }, { type: :number, property: :score, - title: I18n.t("reports.user_flagging_ratio.labels.score") + title: I18n.t("reports.user_flagging_ratio.labels.score"), }, ] @@ -74,18 +74,20 @@ module Reports::UserFlaggingRatio LIMIT 100 SQL - DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| - flagger = {} - flagger[:user_id] = row.id - flagger[:username] = row.username - flagger[:avatar_template] = User.avatar_template(row.username, row.avatar_id) - flagger[:disagreed_flags] = row.disagreed_flags - flagger[:ignored_flags] = row.ignored_flags - flagger[:agreed_flags] = row.agreed_flags - flagger[:score] = row.score + DB + .query(sql, start_date: report.start_date, end_date: report.end_date) + .each do |row| + flagger = {} + flagger[:user_id] = row.id + flagger[:username] = row.username + flagger[:avatar_template] = User.avatar_template(row.username, row.avatar_id) + flagger[:disagreed_flags] = row.disagreed_flags + flagger[:ignored_flags] = row.ignored_flags + flagger[:agreed_flags] = row.agreed_flags + flagger[:score] = row.score - report.data << flagger - end + report.data << flagger + end end end end diff --git a/app/models/concerns/reports/user_to_user_private_messages.rb b/app/models/concerns/reports/user_to_user_private_messages.rb index 7a996400bcc..5d38191545a 100644 --- a/app/models/concerns/reports/user_to_user_private_messages.rb +++ b/app/models/concerns/reports/user_to_user_private_messages.rb @@ -5,7 +5,7 @@ module Reports::UserToUserPrivateMessages class_methods do def report_user_to_user_private_messages(report) - report.icon = 'envelope' + report.icon = "envelope" private_messages_report report, TopicSubtype.user_to_user end end diff --git a/app/models/concerns/reports/user_to_user_private_messages_with_replies.rb b/app/models/concerns/reports/user_to_user_private_messages_with_replies.rb index 42dfc590e40..76c78e6be79 100644 --- a/app/models/concerns/reports/user_to_user_private_messages_with_replies.rb +++ b/app/models/concerns/reports/user_to_user_private_messages_with_replies.rb @@ -5,12 +5,17 @@ module Reports::UserToUserPrivateMessagesWithReplies class_methods do def report_user_to_user_private_messages_with_replies(report) - report.icon = 'envelope' + report.icon = "envelope" topic_subtype = TopicSubtype.user_to_user - subject = Post.where('posts.user_id > 0') - basic_report_about report, subject, :private_messages_count_per_day, report.start_date, report.end_date, topic_subtype - subject = Post.private_posts.where('posts.user_id > 0').with_topic_subtype(topic_subtype) - add_counts report, subject, 'posts.created_at' + subject = Post.where("posts.user_id > 0") + basic_report_about report, + subject, + :private_messages_count_per_day, + report.start_date, + report.end_date, + topic_subtype + subject = Post.private_posts.where("posts.user_id > 0").with_topic_subtype(topic_subtype) + add_counts report, subject, "posts.created_at" end end end diff --git a/app/models/concerns/reports/users_by_trust_level.rb b/app/models/concerns/reports/users_by_trust_level.rb index ac2d1b94f1d..f0226a90c8a 100644 --- a/app/models/concerns/reports/users_by_trust_level.rb +++ b/app/models/concerns/reports/users_by_trust_level.rb @@ -12,22 +12,20 @@ module Reports::UsersByTrustLevel report.dates_filtering = false report.labels = [ - { - property: :key, - title: I18n.t("reports.users_by_trust_level.labels.level") - }, - { - property: :y, - type: :number, - title: I18n.t("reports.default.labels.count") - } + { property: :key, title: I18n.t("reports.users_by_trust_level.labels.level") }, + { property: :y, type: :number, title: I18n.t("reports.default.labels.count") }, ] - User.real.group('trust_level').count.sort.each do |level, count| - key = TrustLevel.levels.key(level.to_i) - url = Proc.new { |k| "/admin/users/list/#{k}" } - report.data << { url: url.call(key), key: key, x: level.to_i, y: count } - end + User + .real + .group("trust_level") + .count + .sort + .each do |level, count| + key = TrustLevel.levels.key(level.to_i) + url = Proc.new { |k| "/admin/users/list/#{k}" } + report.data << { url: url.call(key), key: key, x: level.to_i, y: count } + end end end end diff --git a/app/models/concerns/reports/users_by_type.rb b/app/models/concerns/reports/users_by_type.rb index 33952f3dbb6..5fd407dd3f5 100644 --- a/app/models/concerns/reports/users_by_type.rb +++ b/app/models/concerns/reports/users_by_type.rb @@ -12,31 +12,56 @@ module Reports::UsersByType report.dates_filtering = false report.labels = [ - { - property: :x, - title: I18n.t("reports.users_by_type.labels.type") - }, - { - property: :y, - type: :number, - title: I18n.t("reports.default.labels.count") - } + { property: :x, title: I18n.t("reports.users_by_type.labels.type") }, + { property: :y, type: :number, title: I18n.t("reports.default.labels.count") }, ] label = Proc.new { |x| I18n.t("reports.users_by_type.xaxis_labels.#{x}") } url = Proc.new { |key| "/admin/users/list/#{key}" } admins = User.real.admins.count - report.data << { url: url.call("admins"), icon: "shield-alt", key: "admins", x: label.call("admin"), y: admins } if admins > 0 + if admins > 0 + report.data << { + url: url.call("admins"), + icon: "shield-alt", + key: "admins", + x: label.call("admin"), + y: admins, + } + end moderators = User.real.moderators.count - report.data << { url: url.call("moderators"), icon: "shield-alt", key: "moderators", x: label.call("moderator"), y: moderators } if moderators > 0 + if moderators > 0 + report.data << { + url: url.call("moderators"), + icon: "shield-alt", + key: "moderators", + x: label.call("moderator"), + y: moderators, + } + end suspended = User.real.suspended.count - report.data << { url: url.call("suspended"), icon: "ban", key: "suspended", x: label.call("suspended"), y: suspended } if suspended > 0 + if suspended > 0 + report.data << { + url: url.call("suspended"), + icon: "ban", + key: "suspended", + x: label.call("suspended"), + y: suspended, + } + end silenced = User.real.silenced.count - report.data << { url: url.call("silenced"), icon: "ban", key: "silenced", x: label.call("silenced"), y: silenced } if silenced > 0 + if silenced > 0 + report.data << { + url: url.call("silenced"), + icon: "ban", + key: "silenced", + x: label.call("silenced"), + y: silenced, + } + end end end end diff --git a/app/models/concerns/reports/visits.rb b/app/models/concerns/reports/visits.rb index 676cf772021..7878d0a3b7d 100644 --- a/app/models/concerns/reports/visits.rb +++ b/app/models/concerns/reports/visits.rb @@ -6,14 +6,24 @@ module Reports::Visits class_methods do def report_visits(report) group_filter = report.filters.dig(:group) - report.add_filter('group', type: 'group', default: group_filter) + report.add_filter("group", type: "group", default: group_filter) - report.icon = 'user' + report.icon = "user" - basic_report_about report, UserVisit, :by_day, report.start_date, report.end_date, group_filter - add_counts report, UserVisit, 'visited_at' + basic_report_about report, + UserVisit, + :by_day, + report.start_date, + report.end_date, + group_filter + add_counts report, UserVisit, "visited_at" - report.prev30Days = UserVisit.where('visited_at >= ? and visited_at < ?', report.start_date - 30.days, report.start_date).count + report.prev30Days = + UserVisit.where( + "visited_at >= ? and visited_at < ?", + report.start_date - 30.days, + report.start_date, + ).count end end end diff --git a/app/models/concerns/reports/web_crawlers.rb b/app/models/concerns/reports/web_crawlers.rb index 6db61d11a97..cdb731eeab5 100644 --- a/app/models/concerns/reports/web_crawlers.rb +++ b/app/models/concerns/reports/web_crawlers.rb @@ -9,22 +9,25 @@ module Reports::WebCrawlers { type: :string, property: :user_agent, - title: I18n.t('reports.web_crawlers.labels.user_agent') + title: I18n.t("reports.web_crawlers.labels.user_agent"), }, { property: :count, type: :number, - title: I18n.t('reports.web_crawlers.labels.page_views') - } + title: I18n.t("reports.web_crawlers.labels.page_views"), + }, ] report.modes = [:table] - report.data = WebCrawlerRequest.where('date >= ? and date <= ?', report.start_date, report.end_date) - .limit(200) - .order('sum_count DESC') - .group(:user_agent).sum(:count) - .map { |ua, count| { user_agent: ua, count: count } } + report.data = + WebCrawlerRequest + .where("date >= ? and date <= ?", report.start_date, report.end_date) + .limit(200) + .order("sum_count DESC") + .group(:user_agent) + .sum(:count) + .map { |ua, count| { user_agent: ua, count: count } } end end end diff --git a/app/models/concerns/roleable.rb b/app/models/concerns/roleable.rb index 0400db789db..ebfe7ac6623 100644 --- a/app/models/concerns/roleable.rb +++ b/app/models/concerns/roleable.rb @@ -19,37 +19,38 @@ module Roleable end def whisperer? - @whisperer ||= begin - whispers_allowed_group_ids = SiteSetting.whispers_allowed_group_ids - return false if whispers_allowed_group_ids.blank? - return true if admin - return true if whispers_allowed_group_ids.include?(primary_group_id) - group_users&.exists?(group_id: whispers_allowed_group_ids) - end + @whisperer ||= + begin + whispers_allowed_group_ids = SiteSetting.whispers_allowed_group_ids + return false if whispers_allowed_group_ids.blank? + return true if admin + return true if whispers_allowed_group_ids.include?(primary_group_id) + group_users&.exists?(group_id: whispers_allowed_group_ids) + end end def grant_moderation! return if moderator - set_permission('moderator', true) + set_permission("moderator", true) auto_approve_user enqueue_staff_welcome_message(:moderator) set_default_notification_levels(:moderators) end def revoke_moderation! - set_permission('moderator', false) + set_permission("moderator", false) end def grant_admin! return if admin - set_permission('admin', true) + set_permission("admin", true) auto_approve_user enqueue_staff_welcome_message(:admin) set_default_notification_levels(:admins) end def revoke_admin! - set_permission('admin', false) + set_permission("admin", false) end def save_and_refresh_staff_groups! diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index ed01ecff8e9..84ba85843b9 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -3,16 +3,7 @@ module Searchable extend ActiveSupport::Concern - PRIORITIES = Enum.new( - ignore: 1, - very_low: 2, - low: 3, - normal: 0, - high: 4, - very_high: 5 - ) + PRIORITIES = Enum.new(ignore: 1, very_low: 2, low: 3, normal: 0, high: 4, very_high: 5) - included do - has_one "#{self.name.underscore}_search_data".to_sym, dependent: :destroy - end + included { has_one "#{self.name.underscore}_search_data".to_sym, dependent: :destroy } end diff --git a/app/models/concerns/second_factor_manager.rb b/app/models/concerns/second_factor_manager.rb index e21957e75ff..dc74d541e08 100644 --- a/app/models/concerns/second_factor_manager.rb +++ b/app/models/concerns/second_factor_manager.rb @@ -5,24 +5,27 @@ module SecondFactorManager extend ActiveSupport::Concern - SecondFactorAuthenticationResult = Struct.new( - :ok, - :error, - :reason, - :backup_enabled, - :security_key_enabled, - :totp_enabled, - :multiple_second_factor_methods, - :used_2fa_method, - ) + SecondFactorAuthenticationResult = + Struct.new( + :ok, + :error, + :reason, + :backup_enabled, + :security_key_enabled, + :totp_enabled, + :multiple_second_factor_methods, + :used_2fa_method, + ) def create_totp(opts = {}) require_rotp - UserSecondFactor.create!({ - user_id: self.id, - method: UserSecondFactor.methods[:totp], - data: ROTP::Base32.random - }.merge(opts)) + UserSecondFactor.create!( + { + user_id: self.id, + method: UserSecondFactor.methods[:totp], + data: ROTP::Base32.random, + }.merge(opts), + ) end def get_totp_object(data) @@ -38,19 +41,18 @@ module SecondFactorManager totps = self&.user_second_factors.totps authenticated = false totps.each do |totp| - last_used = 0 - if totp.last_used - last_used = totp.last_used.to_i - end + last_used = totp.last_used.to_i if totp.last_used - authenticated = !token.blank? && totp.totp_object.verify( - token, - drift_ahead: TOTP_ALLOWED_DRIFT_SECONDS, - drift_behind: TOTP_ALLOWED_DRIFT_SECONDS, - after: last_used - ) + authenticated = + !token.blank? && + totp.totp_object.verify( + token, + drift_ahead: TOTP_ALLOWED_DRIFT_SECONDS, + drift_behind: TOTP_ALLOWED_DRIFT_SECONDS, + after: last_used, + ) if authenticated totp.update!(last_used: DateTime.now) @@ -61,21 +63,21 @@ module SecondFactorManager end def totp_enabled? - !SiteSetting.enable_discourse_connect && - SiteSetting.enable_local_logins && + !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins && self&.user_second_factors.totps.exists? end def backup_codes_enabled? - !SiteSetting.enable_discourse_connect && - SiteSetting.enable_local_logins && + !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins && self&.user_second_factors.backup_codes.exists? end def security_keys_enabled? - !SiteSetting.enable_discourse_connect && - SiteSetting.enable_local_logins && - self&.security_keys.where(factor_type: UserSecurityKey.factor_types[:second_factor], enabled: true).exists? + !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins && + self + &.security_keys + .where(factor_type: UserSecurityKey.factor_types[:second_factor], enabled: true) + .exists? end def has_any_second_factor_methods_enabled? @@ -166,35 +168,35 @@ module SecondFactorManager security_key_credential, challenge: Webauthn.challenge(self, secure_session), rp_id: Webauthn.rp_id(self, secure_session), - origin: Discourse.base_url + origin: Discourse.base_url, ).authenticate_security_key end def invalid_totp_or_backup_code_result invalid_second_factor_authentication_result( I18n.t("login.invalid_second_factor_code"), - "invalid_second_factor" + "invalid_second_factor", ) end def invalid_security_key_result(error_message = nil) invalid_second_factor_authentication_result( error_message || I18n.t("login.invalid_security_key"), - "invalid_security_key" + "invalid_security_key", ) end def invalid_second_factor_method_result invalid_second_factor_authentication_result( I18n.t("login.invalid_second_factor_method"), - "invalid_second_factor_method" + "invalid_second_factor_method", ) end def not_enabled_second_factor_method_result invalid_second_factor_authentication_result( I18n.t("login.not_enabled_second_factor_method"), - "not_enabled_second_factor_method" + "not_enabled_second_factor_method", ) end @@ -206,22 +208,19 @@ module SecondFactorManager backup_codes_enabled?, security_keys_enabled?, totp_enabled?, - has_multiple_second_factor_methods? + has_multiple_second_factor_methods?, ) end def generate_backup_codes codes = [] - 10.times do - codes << SecureRandom.hex(16) - end + 10.times { codes << SecureRandom.hex(16) } - codes_json = codes.map do |code| - salt = SecureRandom.hex(16) - { salt: salt, - code_hash: hash_backup_code(code, salt) - } - end + codes_json = + codes.map do |code| + salt = SecureRandom.hex(16) + { salt: salt, code_hash: hash_backup_code(code, salt) } + end if self.user_second_factors.backup_codes.empty? create_backup_codes(codes_json) @@ -239,7 +238,7 @@ module SecondFactorManager user_id: self.id, data: code.to_json, enabled: true, - method: UserSecondFactor.methods[:backup_codes] + method: UserSecondFactor.methods[:backup_codes], ) end end @@ -264,10 +263,15 @@ module SecondFactorManager end def hash_backup_code(code, salt) - Pbkdf2.hash_password(code, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm) + Pbkdf2.hash_password( + code, + salt, + Rails.configuration.pbkdf2_iterations, + Rails.configuration.pbkdf2_algorithm, + ) end def require_rotp - require 'rotp' if !defined? ROTP + require "rotp" if !defined?(ROTP) end end diff --git a/app/models/concerns/stats_cacheable.rb b/app/models/concerns/stats_cacheable.rb index 31d4908097c..027c02f83fe 100644 --- a/app/models/concerns/stats_cacheable.rb +++ b/app/models/concerns/stats_cacheable.rb @@ -5,11 +5,11 @@ module StatsCacheable module ClassMethods def stats_cache_key - raise 'Stats cache key has not been set.' + raise "Stats cache key has not been set." end def fetch_stats - raise 'Not implemented.' + raise "Not implemented." end # Could be configurable, multisite need to support it. diff --git a/app/models/concerns/topic_tracking_state_publishable.rb b/app/models/concerns/topic_tracking_state_publishable.rb index 77dd75a841a..079bda6dbe0 100644 --- a/app/models/concerns/topic_tracking_state_publishable.rb +++ b/app/models/concerns/topic_tracking_state_publishable.rb @@ -4,17 +4,19 @@ module TopicTrackingStatePublishable extend ActiveSupport::Concern class_methods do - def publish_read_message(message_type:, - channel_name:, - topic_id:, - user:, - last_read_post_number:, - notification_level: nil) - - highest_post_number = DB.query_single( - "SELECT #{user.whisperer? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?", - topic_id - ).first + def publish_read_message( + message_type:, + channel_name:, + topic_id:, + user:, + last_read_post_number:, + notification_level: nil + ) + highest_post_number = + DB.query_single( + "SELECT #{user.whisperer? ? "highest_staff_post_number" : "highest_post_number"} FROM topics WHERE id = ?", + topic_id, + ).first message = { message_type: message_type, @@ -22,8 +24,8 @@ module TopicTrackingStatePublishable payload: { last_read_post_number: last_read_post_number, notification_level: notification_level, - highest_post_number: highest_post_number - } + highest_post_number: highest_post_number, + }, }.as_json MessageBus.publish(channel_name, message, user_ids: [user.id]) diff --git a/app/models/concerns/trashable.rb b/app/models/concerns/trashable.rb index 2a7e51fa81a..cdd52c08425 100644 --- a/app/models/concerns/trashable.rb +++ b/app/models/concerns/trashable.rb @@ -7,7 +7,7 @@ module Trashable default_scope { where(deleted_at: nil) } scope :with_deleted, -> { unscope(where: :deleted_at) } - belongs_to :deleted_by, class_name: 'User' + belongs_to :deleted_by, class_name: "User" end def trashed? @@ -33,5 +33,4 @@ module Trashable def trash_update(deleted_at, deleted_by_id) self.update_columns(deleted_at: deleted_at, deleted_by_id: deleted_by_id) end - end diff --git a/app/models/developer.rb b/app/models/developer.rb index a789dd6dd66..ee25a849c92 100644 --- a/app/models/developer.rb +++ b/app/models/developer.rb @@ -6,7 +6,7 @@ class Developer < ActiveRecord::Base after_save :rebuild_cache after_destroy :rebuild_cache - @id_cache = DistributedCache.new('developer_ids') + @id_cache = DistributedCache.new("developer_ids") def self.user_ids @id_cache["ids"] || rebuild_cache diff --git a/app/models/digest_email_site_setting.rb b/app/models/digest_email_site_setting.rb index 23cc4e28ecb..08055b53098 100644 --- a/app/models/digest_email_site_setting.rb +++ b/app/models/digest_email_site_setting.rb @@ -1,26 +1,23 @@ # frozen_string_literal: true class DigestEmailSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'never', value: 0 }, - { name: 'every_30_minutes', value: 30 }, - { name: 'every_hour', value: 60 }, - { name: 'daily', value: 1440 }, - { name: 'weekly', value: 10080 }, - { name: 'every_month', value: 43200 }, - { name: 'every_six_months', value: 259200 } + { name: "never", value: 0 }, + { name: "every_30_minutes", value: 30 }, + { name: "every_hour", value: 60 }, + { name: "daily", value: 1440 }, + { name: "weekly", value: 10_080 }, + { name: "every_month", value: 43_200 }, + { name: "every_six_months", value: 259_200 }, ] end def self.translate_names? true end - end diff --git a/app/models/directory_column.rb b/app/models/directory_column.rb index 3a290432c2f..73a0762a1e8 100644 --- a/app/models/directory_column.rb +++ b/app/models/directory_column.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class DirectoryColumn < ActiveRecord::Base - # TODO(2021-06-18): Remove automatic column self.ignored_columns = ["automatic"] self.inheritance_column = nil @@ -9,17 +8,23 @@ class DirectoryColumn < ActiveRecord::Base enum type: { automatic: 0, user_field: 1, plugin: 2 }, _scopes: false def self.automatic_column_names - @automatic_column_names ||= [:likes_received, - :likes_given, - :topics_entered, - :topic_count, - :post_count, - :posts_read, - :days_visited] + @automatic_column_names ||= %i[ + likes_received + likes_given + topics_entered + topic_count + post_count + posts_read + days_visited + ] end def self.active_column_names - DirectoryColumn.where(type: [:automatic, :plugin]).where(enabled: true).pluck(:name).map(&:to_sym) + DirectoryColumn + .where(type: %i[automatic plugin]) + .where(enabled: true) + .pluck(:name) + .map(&:to_sym) end @@plugin_directory_columns = [] @@ -35,14 +40,15 @@ class DirectoryColumn < ActiveRecord::Base end def self.find_or_create_plugin_directory_column(attrs) - directory_column = find_or_create_by( - name: attrs[:column_name], - icon: attrs[:icon], - type: DirectoryColumn.types[:plugin] - ) do |column| - column.position = DirectoryColumn.maximum("position") + 1 - column.enabled = false - end + directory_column = + find_or_create_by( + name: attrs[:column_name], + icon: attrs[:icon], + type: DirectoryColumn.types[:plugin], + ) do |column| + column.position = DirectoryColumn.maximum("position") + 1 + column.enabled = false + end unless @@plugin_directory_columns.include?(directory_column.name) @@plugin_directory_columns << directory_column.name diff --git a/app/models/directory_item.rb b/app/models/directory_item.rb index 817697a434a..0dce2080868 100644 --- a/app/models/directory_item.rb +++ b/app/models/directory_item.rb @@ -7,12 +7,7 @@ class DirectoryItem < ActiveRecord::Base @@plugin_queries = [] def self.period_types - @types ||= Enum.new(all: 1, - yearly: 2, - monthly: 3, - weekly: 4, - daily: 5, - quarterly: 6) + @types ||= Enum.new(all: 1, yearly: 2, monthly: 3, weekly: 4, daily: 5, quarterly: 6) end def self.refresh! @@ -43,36 +38,49 @@ class DirectoryItem < ActiveRecord::Base since = case period_type - when :daily then 1.day.ago - when :weekly then 1.week.ago - when :monthly then 1.month.ago - when :quarterly then 3.months.ago - when :yearly then 1.year.ago - else 1000.years.ago + when :daily + 1.day.ago + when :weekly + 1.week.ago + when :monthly + 1.month.ago + when :quarterly + 3.months.ago + when :yearly + 1.year.ago + else + 1000.years.ago end ActiveRecord::Base.transaction do # Delete records that belonged to users who have been deleted - DB.exec("DELETE FROM directory_items + DB.exec( + "DELETE FROM directory_items USING directory_items di LEFT JOIN users u ON (u.id = user_id AND u.active AND u.silenced_till IS NULL AND u.id > 0) WHERE di.id = directory_items.id AND u.id IS NULL AND - di.period_type = :period_type", period_type: period_types[period_type]) + di.period_type = :period_type", + period_type: period_types[period_type], + ) # Create new records for users who don't have one yet - column_names = DirectoryColumn.automatic_column_names + DirectoryColumn.plugin_directory_columns - DB.exec("INSERT INTO directory_items(period_type, user_id, #{column_names.map(&:to_s).join(", ")}) + column_names = + DirectoryColumn.automatic_column_names + DirectoryColumn.plugin_directory_columns + DB.exec( + "INSERT INTO directory_items(period_type, user_id, #{column_names.map(&:to_s).join(", ")}) SELECT :period_type, u.id, - #{Array.new(column_names.count) { |_| 0 }.join(", ") } + #{Array.new(column_names.count) { |_| 0 }.join(", ")} FROM users u LEFT JOIN directory_items di ON di.user_id = u.id AND di.period_type = :period_type WHERE di.id IS NULL AND u.id > 0 AND u.silenced_till IS NULL AND u.active - #{SiteSetting.must_approve_users ? 'AND u.approved' : ''} - ", period_type: period_types[period_type]) + #{SiteSetting.must_approve_users ? "AND u.approved" : ""} + ", + period_type: period_types[period_type], + ) # Calculate new values and update records # @@ -88,10 +96,11 @@ class DirectoryItem < ActiveRecord::Base was_liked_type: UserAction::WAS_LIKED, new_topic_type: UserAction::NEW_TOPIC, reply_type: UserAction::REPLY, - regular_post_type: Post.types[:regular] + regular_post_type: Post.types[:regular], } - DB.exec("WITH x AS (SELECT + DB.exec( + "WITH x AS (SELECT u.id user_id, SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :was_liked_type THEN 1 ELSE 0 END) likes_received, SUM(CASE WHEN p.id IS NOT NULL AND t.id IS NOT NULL AND ua.action_type = :like_type THEN 1 ELSE 0 END) likes_given, @@ -131,15 +140,12 @@ class DirectoryItem < ActiveRecord::Base di.post_count <> x.post_count ) ", - query_args - ) + query_args, + ) - @@plugin_queries.each do |plugin_query| - DB.exec(plugin_query, query_args) - end + @@plugin_queries.each { |plugin_query| DB.exec(plugin_query, query_args) } - if period_type == :all - DB.exec <<~SQL + DB.exec <<~SQL if period_type == :all UPDATE user_stats s SET likes_given = d.likes_given, likes_received = d.likes_received, @@ -155,7 +161,6 @@ class DirectoryItem < ActiveRecord::Base s.post_count <> d.post_count ) SQL - end end end end diff --git a/app/models/discourse_connect.rb b/app/models/discourse_connect.rb index ede77372f82..850784173f4 100644 --- a/app/models/discourse_connect.rb +++ b/app/models/discourse_connect.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true class DiscourseConnect < DiscourseConnectBase - - class BlankExternalId < StandardError; end - class BannedExternalId < StandardError; end + class BlankExternalId < StandardError + end + class BannedExternalId < StandardError + end def self.sso_url SiteSetting.discourse_connect_url @@ -34,7 +35,11 @@ class DiscourseConnect < DiscourseConnectBase if SiteSetting.discourse_connect_csrf_protection @secure_session.set(nonce_key, return_path, expires: DiscourseConnectBase.nonce_expiry_time) else - Discourse.cache.write(nonce_key, return_path, expires_in: DiscourseConnectBase.nonce_expiry_time) + Discourse.cache.write( + nonce_key, + return_path, + expires_in: DiscourseConnectBase.nonce_expiry_time, + ) end end end @@ -73,7 +78,11 @@ class DiscourseConnect < DiscourseConnectBase Discourse.cache.delete nonce_key end - Discourse.cache.write(used_nonce_key, return_path, expires_in: DiscourseConnectBase.used_nonce_expiry_time) + Discourse.cache.write( + used_nonce_key, + return_path, + expires_in: DiscourseConnectBase.used_nonce_expiry_time, + ) end end @@ -85,20 +94,15 @@ class DiscourseConnect < DiscourseConnectBase "USED_SSO_NONCE_#{nonce}" end - BANNED_EXTERNAL_IDS = %w{none nil blank null} + BANNED_EXTERNAL_IDS = %w[none nil blank null] def lookup_or_create_user(ip_address = nil) - # we don't want to ban 0 from being an external id external_id = self.external_id.to_s - if external_id.blank? - raise BlankExternalId - end + raise BlankExternalId if external_id.blank? - if BANNED_EXTERNAL_IDS.include?(external_id.downcase) - raise BannedExternalId, external_id - end + raise BannedExternalId, external_id if BANNED_EXTERNAL_IDS.include?(external_id.downcase) # we protect here to ensure there is no situation where the same external id # concurrently attempts to create or update sso records @@ -130,13 +134,11 @@ class DiscourseConnect < DiscourseConnectBase if sso_record && (user = sso_record.user) && !user.active && !require_activation user.active = true user.save! - user.enqueue_welcome_message('welcome_user') unless suppress_welcome_message + user.enqueue_welcome_message("welcome_user") unless suppress_welcome_message user.set_automatic_groups end - custom_fields.each do |k, v| - user.custom_fields[k] = v - end + custom_fields.each { |k, v| user.custom_fields[k] = v } user.ip_address = ip_address @@ -149,9 +151,7 @@ class DiscourseConnect < DiscourseConnectBase user.user_avatar.save! if user.user_avatar user.save! - if @email_changed && user.active - user.set_automatic_groups - end + user.set_automatic_groups if @email_changed && user.active # The user might require approval user.create_reviewable @@ -177,9 +177,7 @@ class DiscourseConnect < DiscourseConnectBase sso_record.save! - if sso_record.user - apply_group_rules(sso_record.user) - end + apply_group_rules(sso_record.user) if sso_record.user sso_record && sso_record.user end @@ -188,7 +186,7 @@ class DiscourseConnect < DiscourseConnectBase names = (groups || "").split(",").map(&:downcase) current_groups = user.groups.where(automatic: false) - desired_groups = Group.where('LOWER(NAME) in (?) AND NOT automatic', names) + desired_groups = Group.where("LOWER(NAME) in (?) AND NOT automatic", names) to_be_added = desired_groups if current_groups.present? @@ -218,7 +216,7 @@ class DiscourseConnect < DiscourseConnectBase if add_groups split = add_groups.split(",").map(&:downcase) if split.length > 0 - to_be_added = Group.where('LOWER(name) in (?) AND NOT automatic', split) + to_be_added = Group.where("LOWER(name) in (?) AND NOT automatic", split) if already_member = GroupUser.where(user_id: user.id).pluck(:group_id).presence to_be_added = to_be_added.where("id NOT IN (?)", already_member) end @@ -229,10 +227,11 @@ class DiscourseConnect < DiscourseConnectBase if remove_groups split = remove_groups.split(",").map(&:downcase) if split.length > 0 - to_be_removed = Group - .joins(:group_users) - .where(automatic: false, group_users: { user_id: user.id }) - .where("LOWER(name) IN (?)", split) + to_be_removed = + Group + .joins(:group_users) + .where(automatic: false, group_users: { user_id: user.id }) + .where("LOWER(name) IN (?)", split) end end @@ -255,7 +254,7 @@ class DiscourseConnect < DiscourseConnectBase primary_email: UserEmail.new(email: email, primary: true), name: resolve_name, username: resolve_username, - ip_address: ip_address + ip_address: ip_address, } if SiteSetting.allow_user_locale && locale && LocaleSiteSetting.valid_value?(locale) @@ -271,7 +270,9 @@ class DiscourseConnect < DiscourseConnectBase user.save! if SiteSetting.verbose_discourse_connect_logging - Rails.logger.warn("Verbose SSO log: New User (user_id: #{user.id}) Params: #{user_params} User Params: #{user.attributes} User Errors: #{user.errors.full_messages} Email: #{user.primary_email.attributes} Email Error: #{user.primary_email.errors.full_messages}") + Rails.logger.warn( + "Verbose SSO log: New User (user_id: #{user.id}) Params: #{user_params} User Params: #{user.attributes} User Errors: #{user.errors.full_messages} Email: #{user.primary_email.attributes} Email Error: #{user.primary_email.errors.full_messages}", + ) end end @@ -281,26 +282,29 @@ class DiscourseConnect < DiscourseConnectBase sso_record.external_id = external_id else if avatar_url.present? - Jobs.enqueue(:download_avatar_from_url, + Jobs.enqueue( + :download_avatar_from_url, url: avatar_url, user_id: user.id, - override_gravatar: SiteSetting.discourse_connect_overrides_avatar + override_gravatar: SiteSetting.discourse_connect_overrides_avatar, ) end if profile_background_url.present? - Jobs.enqueue(:download_profile_background_from_url, + Jobs.enqueue( + :download_profile_background_from_url, url: profile_background_url, user_id: user.id, - is_card_background: false + is_card_background: false, ) end if card_background_url.present? - Jobs.enqueue(:download_profile_background_from_url, + Jobs.enqueue( + :download_profile_background_from_url, url: card_background_url, user_id: user.id, - is_card_background: true + is_card_background: true, ) end @@ -312,7 +316,7 @@ class DiscourseConnect < DiscourseConnectBase external_name: name, external_avatar_url: avatar_url, external_profile_background_url: profile_background_url, - external_card_background_url: card_background_url + external_card_background_url: card_background_url, ) end end @@ -338,44 +342,58 @@ class DiscourseConnect < DiscourseConnectBase user.name = name || User.suggest_name(username.blank? ? email : username) end - if locale_force_update && SiteSetting.allow_user_locale && locale && LocaleSiteSetting.valid_value?(locale) + if locale_force_update && SiteSetting.allow_user_locale && locale && + LocaleSiteSetting.valid_value?(locale) user.locale = locale end avatar_missing = user.uploaded_avatar_id.nil? || !Upload.exists?(user.uploaded_avatar_id) - if (avatar_missing || avatar_force_update || SiteSetting.discourse_connect_overrides_avatar) && avatar_url.present? + if (avatar_missing || avatar_force_update || SiteSetting.discourse_connect_overrides_avatar) && + avatar_url.present? avatar_changed = sso_record.external_avatar_url != avatar_url if avatar_force_update || avatar_changed || avatar_missing - Jobs.enqueue(:download_avatar_from_url, url: avatar_url, user_id: user.id, override_gravatar: SiteSetting.discourse_connect_overrides_avatar) + Jobs.enqueue( + :download_avatar_from_url, + url: avatar_url, + user_id: user.id, + override_gravatar: SiteSetting.discourse_connect_overrides_avatar, + ) end end if profile_background_url.present? - profile_background_missing = user.user_profile.profile_background_upload.blank? || Upload.get_from_url(user.user_profile.profile_background_upload.url).blank? + profile_background_missing = + user.user_profile.profile_background_upload.blank? || + Upload.get_from_url(user.user_profile.profile_background_upload.url).blank? if profile_background_missing || SiteSetting.discourse_connect_overrides_profile_background - profile_background_changed = sso_record.external_profile_background_url != profile_background_url + profile_background_changed = + sso_record.external_profile_background_url != profile_background_url if profile_background_changed || profile_background_missing - Jobs.enqueue(:download_profile_background_from_url, - url: profile_background_url, - user_id: user.id, - is_card_background: false + Jobs.enqueue( + :download_profile_background_from_url, + url: profile_background_url, + user_id: user.id, + is_card_background: false, ) end end end if card_background_url.present? - card_background_missing = user.user_profile.card_background_upload.blank? || Upload.get_from_url(user.user_profile.card_background_upload.url).blank? + card_background_missing = + user.user_profile.card_background_upload.blank? || + Upload.get_from_url(user.user_profile.card_background_upload.url).blank? if card_background_missing || SiteSetting.discourse_connect_overrides_card_background card_background_changed = sso_record.external_card_background_url != card_background_url if card_background_changed || card_background_missing - Jobs.enqueue(:download_profile_background_from_url, - url: card_background_url, - user_id: user.id, - is_card_background: true + Jobs.enqueue( + :download_profile_background_from_url, + url: card_background_url, + user_id: user.id, + is_card_background: true, ) end end diff --git a/app/models/do_not_disturb_timing.rb b/app/models/do_not_disturb_timing.rb index 62d7d65cbda..56bbb6524e6 100644 --- a/app/models/do_not_disturb_timing.rb +++ b/app/models/do_not_disturb_timing.rb @@ -6,9 +6,7 @@ class DoNotDisturbTiming < ActiveRecord::Base validate :ends_at_greater_than_starts_at def ends_at_greater_than_starts_at - if starts_at > ends_at - errors.add(:ends_at, :invalid) - end + errors.add(:ends_at, :invalid) if starts_at > ends_at end end diff --git a/app/models/draft.rb b/app/models/draft.rb index ced0d4df5b4..a9dff7f2774 100644 --- a/app/models/draft.rb +++ b/app/models/draft.rb @@ -1,21 +1,23 @@ # frozen_string_literal: true class Draft < ActiveRecord::Base - NEW_TOPIC ||= 'new_topic' - NEW_PRIVATE_MESSAGE ||= 'new_private_message' - EXISTING_TOPIC ||= 'topic_' + NEW_TOPIC ||= "new_topic" + NEW_PRIVATE_MESSAGE ||= "new_private_message" + EXISTING_TOPIC ||= "topic_" belongs_to :user - after_commit :update_draft_count, on: [:create, :destroy] + after_commit :update_draft_count, on: %i[create destroy] - class OutOfSequence < StandardError; end + class OutOfSequence < StandardError + end def self.set(user, key, sequence, data, owner = nil, force_save: false) return 0 if !User.human_user_id?(user.id) force_save = force_save.to_s == "true" - if SiteSetting.backup_drafts_to_pm_length > 0 && SiteSetting.backup_drafts_to_pm_length < data.length + if SiteSetting.backup_drafts_to_pm_length > 0 && + SiteSetting.backup_drafts_to_pm_length < data.length backup_draft(user, key, sequence, data) end @@ -44,21 +46,16 @@ class Draft < ActiveRecord::Base current_sequence ||= 0 if draft_id - if !force_save && (current_sequence != sequence) - raise Draft::OutOfSequence - end + raise Draft::OutOfSequence if !force_save && (current_sequence != sequence) sequence = current_sequence if force_save sequence += 1 # we need to keep upping our sequence on every save # if we do not do that there are bad race conditions - DraftSequence.upsert({ - sequence: sequence, - draft_key: key, - user_id: user.id, - }, - unique_by: [:user_id, :draft_key] + DraftSequence.upsert( + { sequence: sequence, draft_key: key, user_id: user.id }, + unique_by: %i[user_id draft_key], ) DB.exec(<<~SQL, id: draft_id, sequence: sequence, data: data, owner: owner || current_owner) @@ -70,17 +67,10 @@ class Draft < ActiveRecord::Base , updated_at = CURRENT_TIMESTAMP WHERE id = :id SQL - elsif sequence != current_sequence raise Draft::OutOfSequence else - opts = { - user_id: user.id, - draft_key: key, - data: data, - sequence: sequence, - owner: owner - } + opts = { user_id: user.id, draft_key: key, data: data, sequence: sequence, owner: owner } draft_id = DB.query_single(<<~SQL, opts).first INSERT INTO drafts (user_id, draft_key, data, sequence, owner, created_at, updated_at) @@ -101,8 +91,8 @@ class Draft < ActiveRecord::Base UploadReference.ensure_exist!( upload_ids: Upload.extract_upload_ids(data), - target_type: 'Draft', - target_id: draft_id + target_type: "Draft", + target_id: draft_id, ) sequence @@ -111,11 +101,7 @@ class Draft < ActiveRecord::Base def self.get(user, key, sequence) return if !user || !user.id || !User.human_user_id?(user.id) - opts = { - user_id: user.id, - draft_key: key, - sequence: sequence - } + opts = { user_id: user.id, draft_key: key, sequence: sequence } current_sequence, data, draft_sequence = DB.query_single(<<~SQL, opts) WITH draft AS ( @@ -136,9 +122,7 @@ class Draft < ActiveRecord::Base current_sequence ||= 0 - if sequence != current_sequence - raise Draft::OutOfSequence - end + raise Draft::OutOfSequence if sequence != current_sequence data if current_sequence == draft_sequence end @@ -155,9 +139,7 @@ class Draft < ActiveRecord::Base current_sequence = DraftSequence.current(user, key) # bad caller is a reason to complain - if sequence != current_sequence - raise Draft::OutOfSequence - end + raise Draft::OutOfSequence if sequence != current_sequence # corrupt data is not a reason not to leave data Draft.where(user_id: user.id, draft_key: key).destroy_all @@ -176,9 +158,7 @@ class Draft < ActiveRecord::Base end def topic_id - if draft_key.starts_with?(EXISTING_TOPIC) - draft_key.gsub(EXISTING_TOPIC, "").to_i - end + draft_key.gsub(EXISTING_TOPIC, "").to_i if draft_key.starts_with?(EXISTING_TOPIC) end def topic_preloaded? @@ -186,7 +166,9 @@ class Draft < ActiveRecord::Base end def topic - topic_preloaded? ? @topic : @topic = Draft.allowed_draft_topics_for_user(user).find_by(id: topic_id) + topic_preloaded? ? + @topic : + @topic = Draft.allowed_draft_topics_for_user(user).find_by(id: topic_id) end def preload_topic(topic) @@ -240,10 +222,7 @@ class Draft < ActiveRecord::Base offset = (opts[:offset] || 0).to_i limit = (opts[:limit] || 30).to_i - stream = Draft.where(user_id: user_id) - .order(updated_at: :desc) - .offset(offset) - .limit(limit) + stream = Draft.where(user_id: user_id).order(updated_at: :desc).offset(offset).limit(limit) # Preload posts and topics to avoid N+1 queries Draft.preload_data(stream, opts[:user]) @@ -276,13 +255,9 @@ class Draft < ActiveRecord::Base post_id = BackupDraftPost.where(user_id: user.id, key: key).pluck_first(:post_id) post = Post.where(id: post_id).first if post_id - if post_id && !post - BackupDraftPost.where(user_id: user.id, key: key).delete_all - end + BackupDraftPost.where(user_id: user.id, key: key).delete_all if post_id && !post - indented_reply = reply.split("\n").map! do |l| - " #{l}" - end + indented_reply = reply.split("\n").map! { |l| " #{l}" } draft_body = <<~MD #{indented_reply.join("\n")} @@ -297,13 +272,14 @@ class Draft < ActiveRecord::Base if !post topic = ensure_draft_topic!(user) Post.transaction do - post = PostCreator.new( - user, - raw: draft_body, - skip_jobs: true, - skip_validations: true, - topic_id: topic.id, - ).create + post = + PostCreator.new( + user, + raw: draft_body, + skip_jobs: true, + skip_validations: true, + topic_id: topic.id, + ).create BackupDraftPost.create!(user_id: user.id, key: key, post_id: post.id) end elsif post.last_version_at > 5.minutes.ago @@ -311,18 +287,19 @@ class Draft < ActiveRecord::Base post.update_columns( raw: draft_body, cooked: PrettyText.cook(draft_body), - updated_at: Time.zone.now + updated_at: Time.zone.now, ) else revisor = PostRevisor.new(post, post.topic) - revisor.revise!(user, { raw: draft_body }, + revisor.revise!( + user, + { raw: draft_body }, bypass_bump: true, skip_validations: true, skip_staff_log: true, - bypass_rate_limiter: true + bypass_rate_limiter: true, ) end - rescue => e Discourse.warn_exception(e, message: "Failed to backup draft") end @@ -331,28 +308,26 @@ class Draft < ActiveRecord::Base topic_id = BackupDraftTopic.where(user_id: user.id).pluck_first(:topic_id) topic = Topic.find_by(id: topic_id) if topic_id - if topic_id && !topic - BackupDraftTopic.where(user_id: user.id).delete_all - end + BackupDraftTopic.where(user_id: user.id).delete_all if topic_id && !topic if !topic Topic.transaction do - creator = PostCreator.new( - user, - title: I18n.t("draft_backup.pm_title"), - archetype: Archetype.private_message, - raw: I18n.t("draft_backup.pm_body"), - skip_jobs: true, - skip_validations: true, - target_usernames: user.username - ) + creator = + PostCreator.new( + user, + title: I18n.t("draft_backup.pm_title"), + archetype: Archetype.private_message, + raw: I18n.t("draft_backup.pm_body"), + skip_jobs: true, + skip_validations: true, + target_usernames: user.username, + ) topic = creator.create.topic BackupDraftTopic.create!(topic_id: topic.id, user_id: user.id) end end topic - end def update_draft_count diff --git a/app/models/draft_sequence.rb b/app/models/draft_sequence.rb index cfe1a27befb..3570df5aa35 100644 --- a/app/models/draft_sequence.rb +++ b/app/models/draft_sequence.rb @@ -9,8 +9,7 @@ class DraftSequence < ActiveRecord::Base return 0 if !User.human_user_id?(user_id) - sequence = - DB.query_single(<<~SQL, user_id: user_id, draft_key: key).first + sequence = DB.query_single(<<~SQL, user_id: user_id, draft_key: key).first INSERT INTO draft_sequences (user_id, draft_key, sequence) VALUES (:user_id, :draft_key, 1) ON CONFLICT (user_id, draft_key) DO @@ -21,7 +20,12 @@ class DraftSequence < ActiveRecord::Base RETURNING sequence SQL - DB.exec("DELETE FROM drafts WHERE user_id = :user_id AND draft_key = :draft_key AND sequence < :sequence", draft_key: key, user_id: user_id, sequence: sequence) + DB.exec( + "DELETE FROM drafts WHERE user_id = :user_id AND draft_key = :draft_key AND sequence < :sequence", + draft_key: key, + user_id: user_id, + sequence: sequence, + ) UserStat.update_draft_count(user_id) @@ -37,7 +41,12 @@ class DraftSequence < ActiveRecord::Base return 0 if !User.human_user_id?(user_id) # perf critical path - r, _ = DB.query_single('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key) + r, _ = + DB.query_single( + "select sequence from draft_sequences where user_id = ? and draft_key = ?", + user_id, + key, + ) r.to_i end end diff --git a/app/models/email_change_request.rb b/app/models/email_change_request.rb index 44ac085c2da..e0e175e0336 100644 --- a/app/models/email_change_request.rb +++ b/app/models/email_change_request.rb @@ -2,8 +2,8 @@ class EmailChangeRequest < ActiveRecord::Base belongs_to :user - belongs_to :old_email_token, class_name: 'EmailToken', dependent: :destroy - belongs_to :new_email_token, class_name: 'EmailToken', dependent: :destroy + belongs_to :old_email_token, class_name: "EmailToken", dependent: :destroy + belongs_to :new_email_token, class_name: "EmailToken", dependent: :destroy belongs_to :requested_by, class_name: "User", foreign_key: :requested_by_user_id validates :new_email, presence: true, format: { with: EmailAddressValidator.email_regex } @@ -14,7 +14,9 @@ class EmailChangeRequest < ActiveRecord::Base def self.find_by_new_token(token) EmailChangeRequest - .joins("INNER JOIN email_tokens ON email_tokens.id = email_change_requests.new_email_token_id") + .joins( + "INNER JOIN email_tokens ON email_tokens.id = email_change_requests.new_email_token_id", + ) .where("email_tokens.token_hash = ?", EmailToken.hash_token(token)) .last end diff --git a/app/models/email_level_site_setting.rb b/app/models/email_level_site_setting.rb index 9f805dd2536..9d5131e50cf 100644 --- a/app/models/email_level_site_setting.rb +++ b/app/models/email_level_site_setting.rb @@ -1,22 +1,19 @@ # frozen_string_literal: true class EmailLevelSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.email_level.always', value: 0 }, - { name: 'user.email_level.only_when_away', value: 1 }, - { name: 'user.email_level.never', value: 2 }, + { name: "user.email_level.always", value: 0 }, + { name: "user.email_level.only_when_away", value: 1 }, + { name: "user.email_level.never", value: 2 }, ] end def self.translate_names? true end - end diff --git a/app/models/email_log.rb b/app/models/email_log.rb index b5ab6d6402d..c3dc39e0363 100644 --- a/app/models/email_log.rb +++ b/app/models/email_log.rb @@ -1,32 +1,32 @@ # frozen_string_literal: true class EmailLog < ActiveRecord::Base - CRITICAL_EMAIL_TYPES ||= Set.new %w{ - account_created - admin_login - confirm_new_email - confirm_old_email - confirm_old_email_add - forgot_password - notify_old_email - notify_old_email_add - signup - signup_after_approval - } + CRITICAL_EMAIL_TYPES ||= + Set.new %w[ + account_created + admin_login + confirm_new_email + confirm_old_email + confirm_old_email_add + forgot_password + notify_old_email + notify_old_email_add + signup + signup_after_approval + ] # cf. https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml SMTP_ERROR_CODE_REGEXP = Regexp.new(/\d\.\d\.\d+|\d{3}/).freeze belongs_to :user belongs_to :post - belongs_to :smtp_group, class_name: 'Group' + belongs_to :smtp_group, class_name: "Group" validates :email_type, :to_address, presence: true scope :bounced, -> { where(bounced: true) } - scope :addressed_to_user, ->(user) do - where(<<~SQL, user_id: user.id) + scope :addressed_to_user, ->(user) { where(<<~SQL, user_id: user.id) } EXISTS( SELECT 1 FROM user_emails @@ -35,7 +35,6 @@ class EmailLog < ActiveRecord::Base email_logs.cc_addresses ILIKE '%' || user_emails.email || '%') ) SQL - end before_save do if self.bounce_error_code.present? @@ -66,11 +65,11 @@ class EmailLog < ActiveRecord::Base end def self.reached_max_emails?(user, email_type = nil) - return false if SiteSetting.max_emails_per_day_per_user == 0 || CRITICAL_EMAIL_TYPES.include?(email_type) + if SiteSetting.max_emails_per_day_per_user == 0 || CRITICAL_EMAIL_TYPES.include?(email_type) + return false + end - count = where('created_at > ?', 1.day.ago) - .where(user_id: user.id) - .count + count = where("created_at > ?", 1.day.ago).where(user_id: user.id).count count >= SiteSetting.max_emails_per_day_per_user end @@ -87,15 +86,11 @@ class EmailLog < ActiveRecord::Base end def self.last_sent_email_address - self.where(email_type: "signup") - .order(created_at: :desc) - .limit(1) - .pluck(:to_address) - .first + self.where(email_type: "signup").order(created_at: :desc).limit(1).pluck(:to_address).first end def bounce_key - super&.delete('-') + super&.delete("-") end def cc_users diff --git a/app/models/email_style.rb b/app/models/email_style.rb index 9aeb483f01b..88b4b7692ad 100644 --- a/app/models/email_style.rb +++ b/app/models/email_style.rb @@ -6,7 +6,7 @@ class EmailStyle attr_accessor :html, :css, :default_html, :default_css def id - 'email-style' + "email-style" end def html @@ -30,12 +30,11 @@ class EmailStyle end def self.default_template - @_default_template ||= File.read( - File.join(Rails.root, 'app', 'views', 'email', 'default_template.html') - ) + @_default_template ||= + File.read(File.join(Rails.root, "app", "views", "email", "default_template.html")) end def self.default_css - '' + "" end end diff --git a/app/models/email_token.rb b/app/models/email_token.rb index 8cd7dbdd03a..7a04e7c2296 100644 --- a/app/models/email_token.rb +++ b/app/models/email_token.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true class EmailToken < ActiveRecord::Base - class TokenAccessError < StandardError; end + class TokenAccessError < StandardError + end belongs_to :user validates :user_id, :email, :token_hash, presence: true scope :unconfirmed, -> { where(confirmed: false) } - scope :active, -> { where(expired: false).where('created_at >= ?', SiteSetting.email_token_valid_hours.hours.ago) } + scope :active, + -> { + where(expired: false).where( + "created_at >= ?", + SiteSetting.email_token_valid_hours.hours.ago, + ) + } after_initialize do if self.token_hash.blank? @@ -25,9 +32,7 @@ class EmailToken < ActiveRecord::Base .update_all(expired: true) end - before_validation do - self.email = self.email.downcase if self.email - end + before_validation { self.email = self.email.downcase if self.email } before_save do if self.scope.blank? @@ -36,15 +41,10 @@ class EmailToken < ActiveRecord::Base end # TODO(2022-01-01): Remove - self.ignored_columns = %w{token} + self.ignored_columns = %w[token] def self.scopes - @scopes ||= Enum.new( - signup: 1, - password_reset: 2, - email_login: 3, - email_update: 4, - ) + @scopes ||= Enum.new(signup: 1, password_reset: 2, email_login: 3, email_update: 4) end def token @@ -64,7 +64,7 @@ class EmailToken < ActiveRecord::Base user.send_welcome_message = !user.active? user.email = email_token.email user.active = true - user.custom_fields.delete('activation_reminder') + user.custom_fields.delete("activation_reminder") user.save! user.create_reviewable if !skip_reviewable user.set_automatic_groups @@ -80,9 +80,7 @@ class EmailToken < ActiveRecord::Base def self.confirmable(token, scope: nil) return nil if token.blank? - relation = unconfirmed.active - .includes(:user) - .where(token_hash: hash_token(token)) + relation = unconfirmed.active.includes(:user).where(token_hash: hash_token(token)) # TODO(2022-01-01): All email tokens should have scopes by now if !scope @@ -98,7 +96,7 @@ class EmailToken < ActiveRecord::Base type: "signup", user_id: email_token.user_id, email_token: email_token.token, - to_address: to_address + to_address: to_address, ) end diff --git a/app/models/embeddable_host.rb b/app/models/embeddable_host.rb index f638ff235ab..b9c63aff5b3 100644 --- a/app/models/embeddable_host.rb +++ b/app/models/embeddable_host.rb @@ -6,8 +6,8 @@ class EmbeddableHost < ActiveRecord::Base after_destroy :reset_embedding_settings before_validation do - self.host.sub!(/^https?:\/\//, '') - self.host.sub!(/\/.*$/, '') + self.host.sub!(%r{^https?://}, "") + self.host.sub!(%r{/.*$}, "") end # TODO(2021-07-23): Remove @@ -15,10 +15,11 @@ class EmbeddableHost < ActiveRecord::Base def self.record_for_url(uri) if uri.is_a?(String) - uri = begin - URI(UrlHelper.normalized_encode(uri)) - rescue URI::Error, Addressable::URI::InvalidURIError - end + uri = + begin + URI(UrlHelper.normalized_encode(uri)) + rescue URI::Error, Addressable::URI::InvalidURIError + end end return false unless uri.present? @@ -26,9 +27,7 @@ class EmbeddableHost < ActiveRecord::Base host = uri.host return false unless host.present? - if uri.port.present? && uri.port != 80 && uri.port != 443 - host << ":#{uri.port}" - end + host << ":#{uri.port}" if uri.port.present? && uri.port != 80 && uri.port != 443 path = uri.path path << "?" << uri.query if uri.query.present? @@ -49,10 +48,11 @@ class EmbeddableHost < ActiveRecord::Base # Work around IFRAME reload on WebKit where the referer will be set to the Forum URL return true if url&.starts_with?(Discourse.base_url) && EmbeddableHost.exists? - uri = begin - URI(UrlHelper.normalized_encode(url)) - rescue URI::Error - end + uri = + begin + URI(UrlHelper.normalized_encode(url)) + rescue URI::Error + end uri.present? && record_for_url(uri).present? end @@ -67,9 +67,9 @@ class EmbeddableHost < ActiveRecord::Base def host_must_be_valid if host !~ /\A[a-z0-9]+([\-\.]+{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i && - host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ && - host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i - errors.add(:host, I18n.t('errors.messages.invalid')) + host !~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(:[0-9]{1,5})?(\/.*)?\Z/ && + host !~ /\A([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.)?localhost(\:[0-9]{1,5})?(\/.*)?\Z/i + errors.add(:host, I18n.t("errors.messages.invalid")) end end end diff --git a/app/models/embedding.rb b/app/models/embedding.rb index 909ba1c2727..cf6585130e8 100644 --- a/app/models/embedding.rb +++ b/app/models/embedding.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true -require 'has_errors' +require "has_errors" class Embedding < OpenStruct include HasErrors def self.settings - %i(embed_by_username - embed_post_limit - embed_title_scrubber - embed_truncate - embed_unlisted - allowed_embed_selectors - blocked_embed_selectors - allowed_embed_classnames) + %i[ + embed_by_username + embed_post_limit + embed_title_scrubber + embed_truncate + embed_unlisted + allowed_embed_selectors + blocked_embed_selectors + allowed_embed_classnames + ] end def base_url @@ -21,9 +23,7 @@ class Embedding < OpenStruct end def save - Embedding.settings.each do |s| - SiteSetting.set(s, public_send(s)) - end + Embedding.settings.each { |s| SiteSetting.set(s, public_send(s)) } true rescue Discourse::InvalidParameters => p errors.add :base, p.to_s @@ -35,7 +35,7 @@ class Embedding < OpenStruct end def self.find - embedding_args = { id: 'default' } + embedding_args = { id: "default" } Embedding.settings.each { |s| embedding_args[s] = SiteSetting.get(s) } Embedding.new(embedding_args) end diff --git a/app/models/emoji.rb b/app/models/emoji.rb index 750be812834..94b85eb154d 100644 --- a/app/models/emoji.rb +++ b/app/models/emoji.rb @@ -4,7 +4,7 @@ class Emoji # update this to clear the cache EMOJI_VERSION = "12" - FITZPATRICK_SCALE ||= [ "1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff" ] + FITZPATRICK_SCALE ||= %w[1f3fb 1f3fc 1f3fd 1f3fe 1f3ff] DEFAULT_GROUP ||= "default" @@ -29,11 +29,11 @@ class Emoji end def self.aliases - db['aliases'] + db["aliases"] end def self.search_aliases - db['searchAliases'] + db["searchAliases"] end def self.translations @@ -45,11 +45,11 @@ class Emoji end def self.tonable_emojis - db['tonableEmojis'] + db["tonableEmojis"] end def self.custom?(name) - name = name.delete_prefix(':').delete_suffix(':') + name = name.delete_prefix(":").delete_suffix(":") Emoji.custom.detect { |e| e.name == name }.present? end @@ -58,29 +58,28 @@ class Emoji end def self.[](name) - name = name.delete_prefix(':').delete_suffix(':') + name = name.delete_prefix(":").delete_suffix(":") is_toned = name.match?(/.+:t[1-6]/) normalized_name = name.gsub(/(.+):t[1-6]/, '\1') found_emoji = nil [[global_emoji_cache, :standard], [site_emoji_cache, :custom]].each do |cache, list_key| - cache_postfix, found_emoji = cache.defer_get_set(normalized_name) do - emoji = Emoji.public_send(list_key).detect do |e| - e.name == normalized_name && - (!is_toned || (is_toned && e.tonable)) + cache_postfix, found_emoji = + cache.defer_get_set(normalized_name) do + emoji = + Emoji + .public_send(list_key) + .detect { |e| e.name == normalized_name && (!is_toned || (is_toned && e.tonable)) } + [self.cache_postfix, emoji] end - [self.cache_postfix, emoji] - end if found_emoji && (cache_postfix != self.cache_postfix) cache.delete(normalized_name) redo end - if found_emoji - break - end + break if found_emoji end found_emoji @@ -89,7 +88,7 @@ class Emoji def self.create_from_db_item(emoji) name = emoji["name"] return unless group = groups[name] - filename = emoji['filename'] || name + filename = emoji["filename"] || name Emoji.new.tap do |e| e.name = name @@ -101,7 +100,7 @@ class Emoji end def self.url_for(name) - name = name.delete_prefix(':').delete_suffix(':').gsub(/(.+):t([1-6])/, '\1/\2') + name = name.delete_prefix(":").delete_suffix(":").gsub(/(.+):t([1-6])/, '\1/\2') if SiteSetting.external_emoji_url.blank? "#{Discourse.base_path}/images/emoji/#{SiteSetting.emoji_set}/#{name}.png?v=#{EMOJI_VERSION}" else @@ -118,7 +117,7 @@ class Emoji end def self.clear_cache - %w{custom standard translations all}.each do |key| + %w[custom standard translations all].each do |key| Discourse.cache.delete(cache_key("#{key}_emojis")) end global_emoji_cache.clear @@ -130,17 +129,16 @@ class Emoji end def self.groups - @groups ||= begin - groups = {} + @groups ||= + begin + groups = {} - File.open(groups_file, "r:UTF-8") { |f| JSON.parse(f.read) }.each do |group| - group["icons"].each do |icon| - groups[icon["name"]] = group["name"] - end + File + .open(groups_file, "r:UTF-8") { |f| JSON.parse(f.read) } + .each { |group| group["icons"].each { |icon| groups[icon["name"]] = group["name"] } } + + groups end - - groups - end end def self.db_file @@ -152,27 +150,30 @@ class Emoji end def self.load_standard - db['emojis'].map { |e| Emoji.create_from_db_item(e) }.compact + db["emojis"].map { |e| Emoji.create_from_db_item(e) }.compact end def self.load_custom result = [] if !GlobalSetting.skip_db? - CustomEmoji.includes(:upload).order(:name).each do |emoji| - result << Emoji.new.tap do |e| - e.name = emoji.name - e.url = emoji.upload&.url - e.group = emoji.group || DEFAULT_GROUP + CustomEmoji + .includes(:upload) + .order(:name) + .each do |emoji| + result << Emoji.new.tap do |e| + e.name = emoji.name + e.url = emoji.upload&.url + e.group = emoji.group || DEFAULT_GROUP + end end - end end Plugin::CustomEmoji.emojis.each do |group, emojis| emojis.each do |name, url| result << Emoji.new.tap do |e| e.name = name - url = (Discourse.base_path + url) if url[/^\/[^\/]/] + url = (Discourse.base_path + url) if url[%r{^/[^/]}] e.url = url e.group = group || DEFAULT_GROUP end @@ -196,47 +197,45 @@ class Emoji end def self.replacement_code(code) - code - .split('-') - .map!(&:hex) - .pack("U*") + code.split("-").map!(&:hex).pack("U*") end def self.unicode_replacements - @unicode_replacements ||= begin - replacements = {} - is_tonable_emojis = Emoji.tonable_emojis - fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) } + @unicode_replacements ||= + begin + replacements = {} + is_tonable_emojis = Emoji.tonable_emojis + fitzpatrick_scales = FITZPATRICK_SCALE.map { |scale| scale.to_i(16) } - db['emojis'].each do |e| - name = e['name'] + db["emojis"].each do |e| + name = e["name"] - # special cased as we prefer to keep these as symbols - next if name == 'registered' - next if name == 'copyright' - next if name == 'tm' - next if name == 'left_right_arrow' + # special cased as we prefer to keep these as symbols + next if name == "registered" + next if name == "copyright" + next if name == "tm" + next if name == "left_right_arrow" - code = replacement_code(e['code']) - next unless code + code = replacement_code(e["code"]) + next unless code - replacements[code] = name - if is_tonable_emojis.include?(name) - fitzpatrick_scales.each_with_index do |scale, index| - toned_code = code.codepoints.insert(1, scale).pack("U*") - replacements[toned_code] = "#{name}:t#{index + 2}" + replacements[code] = name + if is_tonable_emojis.include?(name) + fitzpatrick_scales.each_with_index do |scale, index| + toned_code = code.codepoints.insert(1, scale).pack("U*") + replacements[toned_code] = "#{name}:t#{index + 2}" + end end end + + replacements["\u{2639}"] = "frowning" + replacements["\u{263B}"] = "slight_smile" + replacements["\u{2661}"] = "heart" + replacements["\u{2665}"] = "heart" + replacements["\u{263A}"] = "relaxed" + + replacements end - - replacements["\u{2639}"] = 'frowning' - replacements["\u{263B}"] = 'slight_smile' - replacements["\u{2661}"] = 'heart' - replacements["\u{2665}"] = 'heart' - replacements["\u{263A}"] = 'relaxed' - - replacements - end end def self.unicode_unescape(string) @@ -244,40 +243,37 @@ class Emoji end def self.gsub_emoji_to_unicode(str) - if str - str.gsub(/:([\w\-+]*(?::t\d)?):/) { |name| Emoji.lookup_unicode($1) || name } - end + str.gsub(/:([\w\-+]*(?::t\d)?):/) { |name| Emoji.lookup_unicode($1) || name } if str end def self.lookup_unicode(name) - @reverse_map ||= begin - map = {} - is_tonable_emojis = Emoji.tonable_emojis + @reverse_map ||= + begin + map = {} + is_tonable_emojis = Emoji.tonable_emojis - db['emojis'].each do |e| - next if e['name'] == 'tm' + db["emojis"].each do |e| + next if e["name"] == "tm" - code = replacement_code(e['code']) - next unless code + code = replacement_code(e["code"]) + next unless code - map[e['name']] = code - if is_tonable_emojis.include?(e['name']) - FITZPATRICK_SCALE.each_with_index do |scale, index| - toned_code = (code.codepoints.insert(1, scale.to_i(16))).pack("U*") - map["#{e['name']}:t#{index + 2}"] = toned_code + map[e["name"]] = code + if is_tonable_emojis.include?(e["name"]) + FITZPATRICK_SCALE.each_with_index do |scale, index| + toned_code = (code.codepoints.insert(1, scale.to_i(16))).pack("U*") + map["#{e["name"]}:t#{index + 2}"] = toned_code + end end end - end - Emoji.aliases.each do |key, alias_names| - next unless alias_code = map[key] - alias_names.each do |alias_name| - map[alias_name] = alias_code + Emoji.aliases.each do |key, alias_names| + next unless alias_code = map[key] + alias_names.each { |alias_name| map[alias_name] = alias_code } end - end - map - end + map + end @reverse_map[name] end @@ -288,17 +284,18 @@ class Emoji def self.codes_to_img(str) return if str.blank? - str = str.gsub(/:([\w\-+]*(?::t\d)?):/) do |name| - code = $1 + str = + str.gsub(/:([\w\-+]*(?::t\d)?):/) do |name| + code = $1 - if code && Emoji.custom?(code) - emoji = Emoji[code] - "\"#{code}\"" - elsif code && Emoji.exists?(code) - "\"#{code}\"" - else - name + if code && Emoji.custom?(code) + emoji = Emoji[code] + "\"#{code}\"" + elsif code && Emoji.exists?(code) + "\"#{code}\"" + else + name + end end - end end end diff --git a/app/models/emoji_set_site_setting.rb b/app/models/emoji_set_site_setting.rb index 0fe47723900..172b249deec 100644 --- a/app/models/emoji_set_site_setting.rb +++ b/app/models/emoji_set_site_setting.rb @@ -1,26 +1,24 @@ # frozen_string_literal: true -require 'enum_site_setting' +require "enum_site_setting" class EmojiSetSiteSetting < EnumSiteSetting - def self.valid_value?(val) values.any? { |v| v[:value] == val.to_s } end def self.values @values ||= [ - { name: 'emoji_set.apple_international', value: 'apple' }, - { name: 'emoji_set.google', value: 'google' }, - { name: 'emoji_set.twitter', value: 'twitter' }, - { name: 'emoji_set.win10', value: 'win10' }, - { name: 'emoji_set.google_classic', value: 'google_classic' }, - { name: 'emoji_set.facebook_messenger', value: 'facebook_messenger' }, + { name: "emoji_set.apple_international", value: "apple" }, + { name: "emoji_set.google", value: "google" }, + { name: "emoji_set.twitter", value: "twitter" }, + { name: "emoji_set.win10", value: "win10" }, + { name: "emoji_set.google_classic", value: "google_classic" }, + { name: "emoji_set.facebook_messenger", value: "facebook_messenger" }, ] end def self.translate_names? true end - end diff --git a/app/models/external_upload_stub.rb b/app/models/external_upload_stub.rb index 00fe2479423..99ed046b97e 100644 --- a/app/models/external_upload_stub.rb +++ b/app/models/external_upload_stub.rb @@ -7,27 +7,32 @@ class ExternalUploadStub < ActiveRecord::Base UPLOADED_EXPIRY_HOURS = 24 FAILED_EXPIRY_HOURS = 48 - belongs_to :created_by, class_name: 'User' + belongs_to :created_by, class_name: "User" - validates :filesize, numericality: { - allow_nil: false, only_integer: true, greater_than_or_equal_to: 1 - } + validates :filesize, + numericality: { + allow_nil: false, + only_integer: true, + greater_than_or_equal_to: 1, + } - scope :expired_created, -> { - where( - "status = ? AND created_at <= ?", - ExternalUploadStub.statuses[:created], - CREATED_EXPIRY_HOURS.hours.ago - ) - } + scope :expired_created, + -> { + where( + "status = ? AND created_at <= ?", + ExternalUploadStub.statuses[:created], + CREATED_EXPIRY_HOURS.hours.ago, + ) + } - scope :expired_uploaded, -> { - where( - "status = ? AND created_at <= ?", - ExternalUploadStub.statuses[:uploaded], - UPLOADED_EXPIRY_HOURS.hours.ago - ) - } + scope :expired_uploaded, + -> { + where( + "status = ? AND created_at <= ?", + ExternalUploadStub.statuses[:uploaded], + UPLOADED_EXPIRY_HOURS.hours.ago, + ) + } before_create do self.unique_identifier = SecureRandom.uuid @@ -35,11 +40,7 @@ class ExternalUploadStub < ActiveRecord::Base end def self.statuses - @statuses ||= Enum.new( - created: 1, - uploaded: 2, - failed: 3, - ) + @statuses ||= Enum.new(created: 1, uploaded: 2, failed: 3) end # TODO (martin): Lifecycle rule would be best to clean stuff up in the external diff --git a/app/models/given_daily_like.rb b/app/models/given_daily_like.rb index e5483ce3600..5b2096d867d 100644 --- a/app/models/given_daily_like.rb +++ b/app/models/given_daily_like.rb @@ -12,14 +12,15 @@ class GivenDailyLike < ActiveRecord::Base given_date = Date.today # upsert would be nice here - rows = find_for(user_id, given_date).update_all('likes_given = likes_given + 1') + rows = find_for(user_id, given_date).update_all("likes_given = likes_given + 1") if rows == 0 create(user_id: user_id, given_date: given_date, likes_given: 1) else - find_for(user_id, given_date) - .where('limit_reached = false AND likes_given >= :limit', limit: SiteSetting.max_likes_per_day) - .update_all(limit_reached: true) + find_for(user_id, given_date).where( + "limit_reached = false AND likes_given >= :limit", + limit: SiteSetting.max_likes_per_day, + ).update_all(limit_reached: true) end end @@ -28,10 +29,11 @@ class GivenDailyLike < ActiveRecord::Base given_date = Date.today - find_for(user_id, given_date).update_all('likes_given = likes_given - 1') - find_for(user_id, given_date) - .where('limit_reached = true AND likes_given < :limit', limit: SiteSetting.max_likes_per_day) - .update_all(limit_reached: false) + find_for(user_id, given_date).update_all("likes_given = likes_given - 1") + find_for(user_id, given_date).where( + "limit_reached = true AND likes_given < :limit", + limit: SiteSetting.max_likes_per_day, + ).update_all(limit_reached: false) end end diff --git a/app/models/global_setting.rb b/app/models/global_setting.rb index ff1ac7a5066..efd83403a06 100644 --- a/app/models/global_setting.rb +++ b/app/models/global_setting.rb @@ -1,17 +1,14 @@ # frozen_string_literal: true class GlobalSetting - def self.register(key, default) - define_singleton_method(key) do - provider.lookup(key, default) - end + define_singleton_method(key) { provider.lookup(key, default) } end VALID_SECRET_KEY ||= /^[0-9a-f]{128}$/ # this is named SECRET_TOKEN as opposed to SECRET_KEY_BASE # for legacy reasons - REDIS_SECRET_KEY ||= 'SECRET_TOKEN' + REDIS_SECRET_KEY ||= "SECRET_TOKEN" REDIS_VALIDATE_SECONDS ||= 30 @@ -23,58 +20,59 @@ class GlobalSetting # - generate a token on the fly if needed and cache in redis # - enforce rules about token format falling back to redis if needed def self.safe_secret_key_base - - if @safe_secret_key_base && @token_in_redis && (@token_last_validated + REDIS_VALIDATE_SECONDS) < Time.now + if @safe_secret_key_base && @token_in_redis && + (@token_last_validated + REDIS_VALIDATE_SECONDS) < Time.now @token_last_validated = Time.now token = Discourse.redis.without_namespace.get(REDIS_SECRET_KEY) - if token.nil? - Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, @safe_secret_key_base) - end + Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, @safe_secret_key_base) if token.nil? end - @safe_secret_key_base ||= begin - token = secret_key_base - if token.blank? || token !~ VALID_SECRET_KEY + @safe_secret_key_base ||= + begin + token = secret_key_base + if token.blank? || token !~ VALID_SECRET_KEY + @token_in_redis = true + @token_last_validated = Time.now - @token_in_redis = true - @token_last_validated = Time.now - - token = Discourse.redis.without_namespace.get(REDIS_SECRET_KEY) - unless token && token =~ VALID_SECRET_KEY - token = SecureRandom.hex(64) - Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, token) + token = Discourse.redis.without_namespace.get(REDIS_SECRET_KEY) + unless token && token =~ VALID_SECRET_KEY + token = SecureRandom.hex(64) + Discourse.redis.without_namespace.set(REDIS_SECRET_KEY, token) + end end + if !secret_key_base.blank? && token != secret_key_base + STDERR.puts "WARNING: DISCOURSE_SECRET_KEY_BASE is invalid, it was re-generated" + end + token end - if !secret_key_base.blank? && token != secret_key_base - STDERR.puts "WARNING: DISCOURSE_SECRET_KEY_BASE is invalid, it was re-generated" - end - token - end rescue Redis::CommandError => e @safe_secret_key_base = SecureRandom.hex(64) if e.message =~ /READONLY/ end def self.load_defaults - default_provider = FileProvider.from(File.expand_path('../../../config/discourse_defaults.conf', __FILE__)) - default_provider.keys.concat(@provider.keys).uniq.each do |key| - default = default_provider.lookup(key, nil) + default_provider = + FileProvider.from(File.expand_path("../../../config/discourse_defaults.conf", __FILE__)) + default_provider + .keys + .concat(@provider.keys) + .uniq + .each do |key| + default = default_provider.lookup(key, nil) - instance_variable_set("@#{key}_cache", nil) + instance_variable_set("@#{key}_cache", nil) - define_singleton_method(key) do - val = instance_variable_get("@#{key}_cache") - unless val.nil? - val == :missing ? nil : val - else - val = provider.lookup(key, default) - if val.nil? - val = :missing + define_singleton_method(key) do + val = instance_variable_get("@#{key}_cache") + unless val.nil? + val == :missing ? nil : val + else + val = provider.lookup(key, default) + val = :missing if val.nil? + instance_variable_set("@#{key}_cache", val) + val == :missing ? nil : val end - instance_variable_set("@#{key}_cache", val) - val == :missing ? nil : val end end - end end def self.skip_db=(v) @@ -94,13 +92,17 @@ class GlobalSetting end def self.use_s3? - (@use_s3 ||= - begin - s3_bucket && - s3_region && ( - s3_use_iam_profile || (s3_access_key_id && s3_secret_access_key) - ) ? :true : :false - end) == :true + ( + @use_s3 ||= + begin + if s3_bucket && s3_region && + (s3_use_iam_profile || (s3_access_key_id && s3_secret_access_key)) + :true + else + :false + end + end + ) == :true end def self.s3_bucket_name @@ -122,7 +124,7 @@ class GlobalSetting def self.database_config hash = { "adapter" => "postgresql" } - %w{ + %w[ pool connect_timeout timeout @@ -135,13 +137,13 @@ class GlobalSetting password replica_host replica_port - }.each do |s| + ].each do |s| if val = self.public_send("db_#{s}") hash[s] = val end end - hostnames = [ hostname ] + hostnames = [hostname] hostnames << backup_hostname if backup_hostname.present? hostnames << URI.parse(cdn_url).host if cdn_url.present? @@ -154,11 +156,11 @@ class GlobalSetting hash["reaping_frequency"] = connection_reaper_interval if connection_reaper_interval.present? hash["advisory_locks"] = !!self.db_advisory_locks - db_variables = provider.keys.filter { |k| k.to_s.starts_with? 'db_variables_' } + db_variables = provider.keys.filter { |k| k.to_s.starts_with? "db_variables_" } if db_variables.length > 0 hash["variables"] = {} db_variables.each do |k| - hash["variables"][k.slice(('db_variables_'.length)..)] = self.public_send(k) + hash["variables"][k.slice(("db_variables_".length)..)] = self.public_send(k) end end @@ -183,12 +185,16 @@ class GlobalSetting def self.get_message_bus_redis_replica_host return message_bus_redis_replica_host if message_bus_redis_replica_host.present? - message_bus_redis_slave_host if respond_to?(:message_bus_redis_slave_host) && message_bus_redis_slave_host.present? + if respond_to?(:message_bus_redis_slave_host) && message_bus_redis_slave_host.present? + message_bus_redis_slave_host + end end def self.get_message_bus_redis_replica_port return message_bus_redis_replica_port if message_bus_redis_replica_port.present? - message_bus_redis_slave_port if respond_to?(:message_bus_redis_slave_port) && message_bus_redis_slave_port.present? + if respond_to?(:message_bus_redis_slave_port) && message_bus_redis_slave_port.present? + message_bus_redis_slave_port + end end def self.redis_config @@ -239,11 +245,7 @@ class GlobalSetting end def self.add_default(name, default) - unless self.respond_to? name - define_singleton_method(name) do - default - end - end + define_singleton_method(name) { default } unless self.respond_to? name end class BaseProvider @@ -259,7 +261,7 @@ class GlobalSetting current else default.present? ? default : nil - end + end, ) end end @@ -267,9 +269,7 @@ class GlobalSetting class FileProvider < BaseProvider attr_reader :data def self.from(file) - if File.exist?(file) - parse(file) - end + parse(file) if File.exist?(file) end def initialize(file) @@ -278,11 +278,15 @@ class GlobalSetting end def read - ERB.new(File.read(@file)).result().split("\n").each do |line| - if line =~ /^\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/ - @data[$1.strip.to_sym] = ($4 || $3 || $2).strip + ERB + .new(File.read(@file)) + .result() + .split("\n") + .each do |line| + if line =~ /^\s*([a-z_]+[a-z0-9_]*)\s*=\s*(\"([^\"]*)\"|\'([^\']*)\'|[^#]*)/ + @data[$1.strip.to_sym] = ($4 || $3 || $2).strip + end end - end end def lookup(key, default) @@ -306,7 +310,7 @@ class GlobalSetting class EnvProvider < BaseProvider def lookup(key, default) var = ENV["DISCOURSE_" + key.to_s.upcase] - resolve(var , var.nil? ? default : nil) + resolve(var, var.nil? ? default : nil) end def keys @@ -316,7 +320,6 @@ class GlobalSetting class BlankProvider < BaseProvider def lookup(key, default) - if key == :redis_port return ENV["DISCOURSE_REDIS_PORT"] if ENV["DISCOURSE_REDIS_PORT"] end @@ -337,8 +340,8 @@ class GlobalSetting @provider = BlankProvider.new else @provider = - FileProvider.from(File.expand_path('../../../config/discourse.conf', __FILE__)) || - EnvProvider.new + FileProvider.from(File.expand_path("../../../config/discourse.conf", __FILE__)) || + EnvProvider.new end end diff --git a/app/models/group.rb b/app/models/group.rb index b4dbd85eabb..c31436815a9 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true -require 'net/imap' +require "net/imap" class Group < ActiveRecord::Base # TODO(2021-05-26): remove - self.ignored_columns = %w{ - flair_url - } + self.ignored_columns = %w[flair_url] include HasCustomFields include AnonCacheInvalidator @@ -28,17 +26,20 @@ class Group < ActiveRecord::Base has_many :users, through: :group_users has_many :requesters, through: :group_requests, source: :user has_many :group_histories, dependent: :destroy - has_many :category_reviews, class_name: 'Category', foreign_key: :reviewable_by_group_id, dependent: :nullify + has_many :category_reviews, + class_name: "Category", + foreign_key: :reviewable_by_group_id, + dependent: :nullify has_many :reviewables, foreign_key: :reviewable_by_group_id, dependent: :nullify has_many :group_category_notification_defaults, dependent: :destroy has_many :group_tag_notification_defaults, dependent: :destroy has_many :associated_groups, through: :group_associated_groups, dependent: :destroy - belongs_to :flair_upload, class_name: 'Upload' + belongs_to :flair_upload, class_name: "Upload" has_many :upload_references, as: :target, dependent: :destroy - belongs_to :smtp_updated_by, class_name: 'User' - belongs_to :imap_updated_by, class_name: 'User' + belongs_to :smtp_updated_by, class_name: "User" + belongs_to :imap_updated_by, class_name: "User" has_and_belongs_to_many :web_hooks @@ -50,7 +51,7 @@ class Group < ActiveRecord::Base after_save :update_title after_save :enqueue_update_mentions_job, - if: Proc.new { |g| g.name_before_last_save && g.saved_change_to_name? } + if: Proc.new { |g| g.name_before_last_save && g.saved_change_to_name? } after_save do if saved_change_to_flair_upload_id? @@ -61,11 +62,11 @@ class Group < ActiveRecord::Base after_save :expire_cache after_destroy :expire_cache - after_commit :automatic_group_membership, on: [:create, :update] + after_commit :automatic_group_membership, on: %i[create update] after_commit :trigger_group_created_event, on: :create after_commit :trigger_group_updated_event, on: :update after_commit :trigger_group_destroyed_event, on: :destroy - after_commit :set_default_notifications, on: [:create, :update] + after_commit :set_default_notifications, on: %i[create update] def expire_cache ApplicationSerializer.expire_cache_fragment!("group_names") @@ -97,31 +98,31 @@ class Group < ActiveRecord::Base trust_level_1: 11, trust_level_2: 12, trust_level_3: 13, - trust_level_4: 14 + trust_level_4: 14, } AUTO_GROUP_IDS = Hash[*AUTO_GROUPS.to_a.flatten.reverse] - STAFF_GROUPS = [:admins, :moderators, :staff] + STAFF_GROUPS = %i[admins moderators staff] AUTO_GROUPS_ADD = "add" AUTO_GROUPS_REMOVE = "remove" - IMAP_SETTING_ATTRIBUTES = [ - "imap_server", - "imap_port", - "imap_ssl", - "imap_mailbox_name", - "email_username", - "email_password" + IMAP_SETTING_ATTRIBUTES = %w[ + imap_server + imap_port + imap_ssl + imap_mailbox_name + email_username + email_password ] - SMTP_SETTING_ATTRIBUTES = [ - "imap_server", - "imap_port", - "imap_ssl", - "email_username", - "email_password", - "email_from_alias" + SMTP_SETTING_ATTRIBUTES = %w[ + imap_server + imap_port + imap_ssl + email_username + email_password + email_from_alias ] ALIAS_LEVELS = { @@ -130,45 +131,38 @@ class Group < ActiveRecord::Base mods_and_admins: 2, members_mods_and_admins: 3, owners_mods_and_admins: 4, - everyone: 99 + everyone: 99, } VALID_DOMAIN_REGEX = /\A[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,24}(:[0-9]{1,5})?(\/.*)?\Z/i def self.visibility_levels - @visibility_levels = Enum.new( - public: 0, - logged_on_users: 1, - members: 2, - staff: 3, - owners: 4 - ) + @visibility_levels = Enum.new(public: 0, logged_on_users: 1, members: 2, staff: 3, owners: 4) end validates :mentionable_level, inclusion: { in: ALIAS_LEVELS.values } validates :messageable_level, inclusion: { in: ALIAS_LEVELS.values } - scope :with_imap_configured, -> { where(imap_enabled: true).where.not(imap_mailbox_name: '') } + scope :with_imap_configured, -> { where(imap_enabled: true).where.not(imap_mailbox_name: "") } scope :with_smtp_configured, -> { where(smtp_enabled: true) } - scope :visible_groups, Proc.new { |user, order, opts| - groups = self - groups = groups.order(order) if order - groups = groups.order("groups.name ASC") unless order&.include?("name") + scope :visible_groups, + Proc.new { |user, order, opts| + groups = self + groups = groups.order(order) if order + groups = groups.order("groups.name ASC") unless order&.include?("name") - if !opts || !opts[:include_everyone] - groups = groups.where("groups.id > 0") - end + groups = groups.where("groups.id > 0") if !opts || !opts[:include_everyone] - if !user&.admin - is_staff = !!user&.staff? + if !user&.admin + is_staff = !!user&.staff? - if user.blank? - sql = "groups.visibility_level = :public" - elsif is_staff - sql = "groups.visibility_level IN (:public, :logged_on_users, :members, :staff)" - else - sql = <<~SQL + if user.blank? + sql = "groups.visibility_level = :public" + elsif is_staff + sql = "groups.visibility_level IN (:public, :logged_on_users, :members, :staff)" + else + sql = <<~SQL groups.id IN ( SELECT id FROM groups @@ -189,31 +183,31 @@ class Group < ActiveRecord::Base WHERE g.visibility_level IN (:staff, :owners) ) SQL - end + end - params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff) - groups = groups.where(sql, params) - end + params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff) + groups = groups.where(sql, params) + end - groups - } + groups + } - scope :members_visible_groups, Proc.new { |user, order, opts| - groups = self.order(order || "name ASC") + scope :members_visible_groups, + Proc.new { |user, order, opts| + groups = self.order(order || "name ASC") - if !opts || !opts[:include_everyone] - groups = groups.where("groups.id > 0") - end + groups = groups.where("groups.id > 0") if !opts || !opts[:include_everyone] - if !user&.admin - is_staff = !!user&.staff? + if !user&.admin + is_staff = !!user&.staff? - if user.blank? - sql = "groups.members_visibility_level = :public" - elsif is_staff - sql = "groups.members_visibility_level IN (:public, :logged_on_users, :members, :staff)" - else - sql = <<~SQL + if user.blank? + sql = "groups.members_visibility_level = :public" + elsif is_staff + sql = + "groups.members_visibility_level IN (:public, :logged_on_users, :members, :staff)" + else + sql = <<~SQL groups.id IN ( SELECT id FROM groups @@ -234,32 +228,39 @@ class Group < ActiveRecord::Base WHERE g.members_visibility_level IN (:staff, :owners) ) SQL - end + end - params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff) - groups = groups.where(sql, params) - end + params = Group.visibility_levels.to_h.merge(user_id: user&.id, is_staff: is_staff) + groups = groups.where(sql, params) + end - groups - } + groups + } - scope :mentionable, lambda { |user, include_public: true| - where(self.mentionable_sql_clause(include_public: include_public), - levels: alias_levels(user), - user_id: user&.id - ) - } + scope :mentionable, + lambda { |user, include_public: true| + where( + self.mentionable_sql_clause(include_public: include_public), + levels: alias_levels(user), + user_id: user&.id, + ) + } - scope :messageable, lambda { |user| - where("messageable_level in (:levels) OR + scope :messageable, + lambda { |user| + where( + "messageable_level in (:levels) OR ( messageable_level = #{ALIAS_LEVELS[:members_mods_and_admins]} AND id in ( SELECT group_id FROM group_users WHERE user_id = :user_id) ) OR ( messageable_level = #{ALIAS_LEVELS[:owners_mods_and_admins]} AND id in ( SELECT group_id FROM group_users WHERE user_id = :user_id AND owner IS TRUE) - )", levels: alias_levels(user), user_id: user && user.id) - } + )", + levels: alias_levels(user), + user_id: user && user.id, + ) + } def self.mentionable_sql_clause(include_public: true) clause = +<<~SQL @@ -275,9 +276,7 @@ class Group < ActiveRecord::Base ) SQL - if include_public - clause << "OR visibility_level = #{Group.visibility_levels[:public]}" - end + clause << "OR visibility_level = #{Group.visibility_levels[:public]}" if include_public clause end @@ -330,8 +329,18 @@ class Group < ActiveRecord::Base self.smtp_updated_by_id = user.id end - self.smtp_enabled = [self.smtp_port, self.smtp_server, self.email_password, self.email_username].all?(&:present?) - self.imap_enabled = [self.imap_port, self.imap_server, self.email_password, self.email_username].all?(&:present?) + self.smtp_enabled = [ + self.smtp_port, + self.smtp_server, + self.email_password, + self.email_username, + ].all?(&:present?) + self.imap_enabled = [ + self.imap_port, + self.imap_server, + self.email_password, + self.email_username, + ].all?(&:present?) self.save end @@ -339,71 +348,96 @@ class Group < ActiveRecord::Base def incoming_email_validator return if self.automatic || self.incoming_email.blank? - incoming_email.split("|").each do |email| - escaped = Rack::Utils.escape_html(email) - if !Email.is_valid?(email) - self.errors.add(:base, I18n.t('groups.errors.invalid_incoming_email', email: escaped)) - elsif group = Group.where.not(id: self.id).find_by_email(email) - self.errors.add(:base, I18n.t('groups.errors.email_already_used_in_group', email: escaped, group_name: Rack::Utils.escape_html(group.name))) - elsif category = Category.find_by_email(email) - self.errors.add(:base, I18n.t('groups.errors.email_already_used_in_category', email: escaped, category_name: Rack::Utils.escape_html(category.name))) + incoming_email + .split("|") + .each do |email| + escaped = Rack::Utils.escape_html(email) + if !Email.is_valid?(email) + self.errors.add(:base, I18n.t("groups.errors.invalid_incoming_email", email: escaped)) + elsif group = Group.where.not(id: self.id).find_by_email(email) + self.errors.add( + :base, + I18n.t( + "groups.errors.email_already_used_in_group", + email: escaped, + group_name: Rack::Utils.escape_html(group.name), + ), + ) + elsif category = Category.find_by_email(email) + self.errors.add( + :base, + I18n.t( + "groups.errors.email_already_used_in_category", + email: escaped, + category_name: Rack::Utils.escape_html(category.name), + ), + ) + end end - end end def posts_for(guardian, opts = nil) opts ||= {} - result = Post.joins(:topic, user: :groups, topic: :category) - .preload(:topic, user: :groups, topic: :category) - .references(:posts, :topics, :category) - .where(groups: { id: id }) - .where('topics.archetype <> ?', Archetype.private_message) - .where('topics.visible') - .where(post_type: [Post.types[:regular], Post.types[:moderator_action]]) + result = + Post + .joins(:topic, user: :groups, topic: :category) + .preload(:topic, user: :groups, topic: :category) + .references(:posts, :topics, :category) + .where(groups: { id: id }) + .where("topics.archetype <> ?", Archetype.private_message) + .where("topics.visible") + .where(post_type: [Post.types[:regular], Post.types[:moderator_action]]) if opts[:category_id].present? - result = result.where('topics.category_id = ?', opts[:category_id].to_i) + result = result.where("topics.category_id = ?", opts[:category_id].to_i) end result = guardian.filter_allowed_categories(result) - result = result.where('posts.id < ?', opts[:before_post_id].to_i) if opts[:before_post_id] - result.order('posts.created_at desc') + result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id] + result.order("posts.created_at desc") end def messages_for(guardian, opts = nil) opts ||= {} - result = Post.includes(:user, :topic, topic: :category) - .references(:posts, :topics, :category) - .where('topics.archetype = ?', Archetype.private_message) - .where(post_type: Post.types[:regular]) - .where('topics.id IN (SELECT topic_id FROM topic_allowed_groups WHERE group_id = ?)', self.id) + result = + Post + .includes(:user, :topic, topic: :category) + .references(:posts, :topics, :category) + .where("topics.archetype = ?", Archetype.private_message) + .where(post_type: Post.types[:regular]) + .where( + "topics.id IN (SELECT topic_id FROM topic_allowed_groups WHERE group_id = ?)", + self.id, + ) if opts[:category_id].present? - result = result.where('topics.category_id = ?', opts[:category_id].to_i) + result = result.where("topics.category_id = ?", opts[:category_id].to_i) end result = guardian.filter_allowed_categories(result) - result = result.where('posts.id < ?', opts[:before_post_id].to_i) if opts[:before_post_id] - result.order('posts.created_at desc') + result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id] + result.order("posts.created_at desc") end def mentioned_posts_for(guardian, opts = nil) opts ||= {} - result = Post.joins(:group_mentions) - .includes(:user, :topic, topic: :category) - .references(:posts, :topics, :category) - .where('topics.archetype <> ?', Archetype.private_message) - .where(post_type: Post.types[:regular]) - .where('group_mentions.group_id = ?', self.id) + result = + Post + .joins(:group_mentions) + .includes(:user, :topic, topic: :category) + .references(:posts, :topics, :category) + .where("topics.archetype <> ?", Archetype.private_message) + .where(post_type: Post.types[:regular]) + .where("group_mentions.group_id = ?", self.id) if opts[:category_id].present? - result = result.where('topics.category_id = ?', opts[:category_id].to_i) + result = result.where("topics.category_id = ?", opts[:category_id].to_i) end result = guardian.filter_allowed_categories(result) - result = result.where('posts.id < ?', opts[:before_post_id].to_i) if opts[:before_post_id] - result.order('posts.created_at desc') + result = result.where("posts.id < ?", opts[:before_post_id].to_i) if opts[:before_post_id] + result.order("posts.created_at desc") end def self.trust_group_ids @@ -411,22 +445,29 @@ class Group < ActiveRecord::Base end def set_message_default_notification_levels!(topic, ignore_existing: false) - group_users.pluck(:user_id, :notification_level).each do |user_id, notification_level| - next if user_id == -1 - next if user_id == topic.user_id - next if ignore_existing && TopicUser.where(user_id: user_id, topic_id: topic.id).exists? + group_users + .pluck(:user_id, :notification_level) + .each do |user_id, notification_level| + next if user_id == -1 + next if user_id == topic.user_id + next if ignore_existing && TopicUser.where(user_id: user_id, topic_id: topic.id).exists? - action = - case notification_level - when TopicUser.notification_levels[:tracking] then "track!" - when TopicUser.notification_levels[:regular] then "regular!" - when TopicUser.notification_levels[:muted] then "mute!" - when TopicUser.notification_levels[:watching] then "watch!" - else "track!" - end + action = + case notification_level + when TopicUser.notification_levels[:tracking] + "track!" + when TopicUser.notification_levels[:regular] + "regular!" + when TopicUser.notification_levels[:muted] + "mute!" + when TopicUser.notification_levels[:watching] + "watch!" + else + "track!" + end - topic.notifier.public_send(action, user_id) - end + topic.notifier.public_send(action, user_id) + end end def self.set_category_and_tag_default_notification_levels!(user, group_name) @@ -455,9 +496,7 @@ class Group < ActiveRecord::Base localized_name = I18n.t("groups.default_names.#{name}", locale: SiteSetting.default_locale) validator = UsernameValidator.new(localized_name) - if validator.valid_format? && !User.username_exists?(localized_name) - group.name = localized_name - end + group.name = localized_name if validator.valid_format? && !User.username_exists?(localized_name) # the everyone group is special, it can include non-users so there is no # way to have the membership in a table @@ -470,7 +509,9 @@ class Group < ActiveRecord::Base group.update!(messageable_level: ALIAS_LEVELS[:everyone]) end - group.update!(visibility_level: Group.visibility_levels[:logged_on_users]) if group.visibility_level == Group.visibility_levels[:public] + if group.visibility_level == Group.visibility_levels[:public] + group.update!(visibility_level: Group.visibility_levels[:logged_on_users]) + end # Remove people from groups they don't belong in. remove_subquery = @@ -496,7 +537,9 @@ class Group < ActiveRecord::Base if removed_user_ids.present? Jobs.enqueue( :publish_group_membership_updates, - user_ids: removed_user_ids, group_id: group.id, type: AUTO_GROUPS_REMOVE + user_ids: removed_user_ids, + group_id: group.id, + type: AUTO_GROUPS_REMOVE, ) end @@ -529,7 +572,9 @@ class Group < ActiveRecord::Base if added_user_ids.present? Jobs.enqueue( :publish_group_membership_updates, - user_ids: added_user_ids, group_id: group.id, type: AUTO_GROUPS_ADD + user_ids: added_user_ids, + group_id: group.id, + type: AUTO_GROUPS_ADD, ) end @@ -554,7 +599,7 @@ class Group < ActiveRecord::Base end def self.reset_groups_user_count!(only_group_ids: []) - where_sql = '' + where_sql = "" if only_group_ids.present? where_sql = "WHERE group_id IN (#{only_group_ids.map(&:to_i).join(",")})" @@ -596,9 +641,7 @@ class Group < ActiveRecord::Base end def self.ensure_automatic_groups! - AUTO_GROUPS.each_key do |name| - refresh_automatic_group!(name) unless lookup_group(name) - end + AUTO_GROUPS.each_key { |name| refresh_automatic_group!(name) unless lookup_group(name) } end def self.[](name) @@ -608,18 +651,18 @@ class Group < ActiveRecord::Base def self.search_groups(name, groups: nil, custom_scope: {}, sort: :none) groups ||= Group - relation = groups.where( - "name ILIKE :term_like OR full_name ILIKE :term_like", term_like: "%#{name}%" - ) + relation = + groups.where("name ILIKE :term_like OR full_name ILIKE :term_like", term_like: "%#{name}%") if sort == :auto prefix = "#{name.gsub("_", "\\_")}%" - relation = relation.reorder( - DB.sql_fragment( - "CASE WHEN name ILIKE :like OR full_name ILIKE :like THEN 0 ELSE 1 END ASC, name ASC", - like: prefix + relation = + relation.reorder( + DB.sql_fragment( + "CASE WHEN name ILIKE :like OR full_name ILIKE :like THEN 0 ELSE 1 END ASC, name ASC", + like: prefix, + ), ) - ) end relation @@ -652,9 +695,7 @@ class Group < ActiveRecord::Base end def self.desired_trust_level_groups(trust_level) - trust_group_ids.keep_if do |id| - id == AUTO_GROUPS[:trust_level_0] || (trust_level + 10) >= id - end + trust_group_ids.keep_if { |id| id == AUTO_GROUPS[:trust_level_0] || (trust_level + 10) >= id } end def self.user_trust_level_change!(user_id, trust_level) @@ -696,21 +737,21 @@ class Group < ActiveRecord::Base additions = expected - current deletions = current - expected - map = Hash[*User.where(username: additions + deletions) - .select('id,username') - .map { |u| [u.username, u.id] }.flatten] + map = + Hash[ + *User + .where(username: additions + deletions) + .select("id,username") + .map { |u| [u.username, u.id] } + .flatten + ] deletions = Set.new(deletions.map { |d| map[d] }) @deletions = [] - group_users.each do |gu| - @deletions << gu if deletions.include?(gu.user_id) - end - - additions.each do |a| - group_users.build(user_id: map[a]) - end + group_users.each { |gu| @deletions << gu if deletions.include?(gu.user_id) } + additions.each { |a| group_users.build(user_id: map[a]) } end def usernames @@ -728,14 +769,16 @@ class Group < ActiveRecord::Base Notification.create!( notification_type: Notification.types[:membership_request_accepted], user_id: user.id, - data: { group_id: id, group_name: name }.to_json + data: { group_id: id, group_name: name }.to_json, ) end if self.categories.count < PUBLISH_CATEGORIES_LIMIT - MessageBus.publish('/categories', { - categories: ActiveModel::ArraySerializer.new(self.categories).as_json - }, user_ids: [user.id]) + MessageBus.publish( + "/categories", + { categories: ActiveModel::ArraySerializer.new(self.categories).as_json }, + user_ids: [user.id], + ) else Discourse.request_refresh!(user_ids: [user.id]) end @@ -750,13 +793,18 @@ class Group < ActiveRecord::Base return false if group_user.blank? has_webhooks = WebHook.active_web_hooks(:group_user) - payload = WebHook.generate_payload(:group_user, group_user, WebHookGroupUserSerializer) if has_webhooks + payload = + WebHook.generate_payload(:group_user, group_user, WebHookGroupUserSerializer) if has_webhooks group_user.destroy trigger_user_removed_event(user) - WebHook.enqueue_hooks(:group_user, :user_removed_from_group, - id: group_user.id, - payload: payload - ) if has_webhooks + if has_webhooks + WebHook.enqueue_hooks( + :group_user, + :user_removed_from_group, + id: group_user.id, + payload: payload, + ) + end true end @@ -781,7 +829,7 @@ class Group < ActiveRecord::Base "email_username = :email OR string_to_array(incoming_email, '|') @> ARRAY[:email] OR email_from_alias = :email", - email: Email.downcase(email) + email: Email.downcase(email), ).first end @@ -810,17 +858,11 @@ class Group < ActiveRecord::Base user_attributes = {} - if self.primary_group? - user_attributes[:primary_group_id] = self.id - end + user_attributes[:primary_group_id] = self.id if self.primary_group? - if self.title.present? - user_attributes[:title] = self.title - end + user_attributes[:title] = self.title if self.title.present? - if user_attributes.present? - User.where(id: user_ids).update_all(user_attributes) - end + User.where(id: user_ids).update_all(user_attributes) if user_attributes.present? # update group user count DB.exec <<~SQL @@ -834,10 +876,7 @@ class Group < ActiveRecord::Base end if self.grant_trust_level.present? - Jobs.enqueue(:bulk_grant_trust_level, - user_ids: user_ids, - trust_level: self.grant_trust_level - ) + Jobs.enqueue(:bulk_grant_trust_level, user_ids: user_ids, trust_level: self.grant_trust_level) end self @@ -862,20 +901,17 @@ class Group < ActiveRecord::Base end def self.member_of(groups, user) - groups - .joins("LEFT JOIN group_users gu ON gu.group_id = groups.id ") - .where("gu.user_id = ?", user.id) + groups.joins("LEFT JOIN group_users gu ON gu.group_id = groups.id ").where( + "gu.user_id = ?", + user.id, + ) end def self.owner_of(groups, user) self.member_of(groups, user).where("gu.owner") end - %i{ - group_created - group_updated - group_destroyed - }.each do |event| + %i[group_created group_updated group_destroyed].each do |event| define_method("trigger_#{event}_event") do DiscourseEvent.trigger(event, self) true @@ -894,7 +930,7 @@ class Group < ActiveRecord::Base nil end - [:muted, :regular, :tracking, :watching, :watching_first_post].each do |level| + %i[muted regular tracking watching watching_first_post].each do |level| define_method("#{level}_category_ids=") do |category_ids| @category_notifications ||= {} @category_notifications[level] = category_ids @@ -923,26 +959,28 @@ class Group < ActiveRecord::Base def imap_mailboxes return [] if !self.imap_enabled || !SiteSetting.enable_imap - Discourse.cache.fetch("group_imap_mailboxes_#{self.id}", expires_in: 30.minutes) do - Rails.logger.info("[IMAP] Refreshing mailboxes list for group #{self.name}") - mailboxes = [] + Discourse + .cache + .fetch("group_imap_mailboxes_#{self.id}", expires_in: 30.minutes) do + Rails.logger.info("[IMAP] Refreshing mailboxes list for group #{self.name}") + mailboxes = [] - begin - imap_provider = Imap::Providers::Detector.init_with_detected_provider( - self.imap_config - ) - imap_provider.connect! - mailboxes = imap_provider.filter_mailboxes(imap_provider.list_mailboxes_with_attributes) - imap_provider.disconnect! + begin + imap_provider = Imap::Providers::Detector.init_with_detected_provider(self.imap_config) + imap_provider.connect! + mailboxes = imap_provider.filter_mailboxes(imap_provider.list_mailboxes_with_attributes) + imap_provider.disconnect! - update_columns(imap_last_error: nil) - rescue => ex - Rails.logger.warn("[IMAP] Mailbox refresh failed for group #{self.name} with error: #{ex}") - update_columns(imap_last_error: ex.message) + update_columns(imap_last_error: nil) + rescue => ex + Rails.logger.warn( + "[IMAP] Mailbox refresh failed for group #{self.name} with error: #{ex}", + ) + update_columns(imap_last_error: ex.message) + end + + mailboxes end - - mailboxes - end end def imap_config @@ -951,16 +989,16 @@ class Group < ActiveRecord::Base port: self.imap_port, ssl: self.imap_ssl, username: self.email_username, - password: self.email_password + password: self.email_password, } end def email_username_domain - email_username.split('@').last + email_username.split("@").last end def email_username_user - email_username.split('@').first + email_username.split("@").first end def email_username_regex @@ -976,7 +1014,7 @@ class Group < ActiveRecord::Base user, owner ? :user_added_to_group_as_owner : :user_added_to_group_as_member, group_name: name_full_preferred, - group_path: "/g/#{self.name}" + group_path: "/g/#{self.name}", ) end @@ -1005,21 +1043,25 @@ class Group < ActiveRecord::Base self.name = stripped end - UsernameValidator.perform_validation(self, 'name') || begin - normalized_name = User.normalize_username(self.name) + UsernameValidator.perform_validation(self, "name") || + begin + normalized_name = User.normalize_username(self.name) - if self.will_save_change_to_name? && User.normalize_username(self.name_was) != normalized_name && User.username_exists?(self.name) - errors.add(:name, I18n.t("activerecord.errors.messages.taken")) + if self.will_save_change_to_name? && + User.normalize_username(self.name_was) != normalized_name && + User.username_exists?(self.name) + errors.add(:name, I18n.t("activerecord.errors.messages.taken")) + end end - end end def automatic_membership_email_domains_format_validator return if self.automatic_membership_email_domains.blank? - domains = Group.get_valid_email_domains(self.automatic_membership_email_domains) do |domain| - self.errors.add :base, (I18n.t('groups.errors.invalid_domain', domain: domain)) - end + domains = + Group.get_valid_email_domains(self.automatic_membership_email_domains) do |domain| + self.errors.add :base, (I18n.t("groups.errors.invalid_domain", domain: domain)) + end self.automatic_membership_email_domains = domains.join("|") end @@ -1029,7 +1071,11 @@ class Group < ActiveRecord::Base if @deletions @deletions.each do |gu| gu.destroy - User.where('id = ? AND primary_group_id = ?', gu.user_id, gu.group_id).update_all 'primary_group_id = NULL' + User.where( + "id = ? AND primary_group_id = ?", + gu.user_id, + gu.group_id, + ).update_all "primary_group_id = NULL" end end @deletions = nil @@ -1067,7 +1113,7 @@ class Group < ActiveRecord::Base /*where*/ SQL - [:primary_group_id, :flair_group_id].each do |column| + %i[primary_group_id flair_group_id].each do |column| builder = DB.build(sql) builder.where(<<~SQL, id: id) id IN ( @@ -1091,10 +1137,19 @@ class Group < ActiveRecord::Base end def self.automatic_membership_users(domains, group_id = nil) - pattern = "@(#{domains.gsub('.', '\.')})$" + pattern = "@(#{domains.gsub(".", '\.')})$" - users = User.joins(:user_emails).where("user_emails.email ~* ?", pattern).activated.where(staged: false) - users = users.where("users.id NOT IN (SELECT user_id FROM group_users WHERE group_users.group_id = ?)", group_id) if group_id.present? + users = + User + .joins(:user_emails) + .where("user_emails.email ~* ?", pattern) + .activated + .where(staged: false) + users = + users.where( + "users.id NOT IN (SELECT user_id FROM group_users WHERE group_users.group_id = ?)", + group_id, + ) if group_id.present? users end @@ -1102,16 +1157,18 @@ class Group < ActiveRecord::Base def self.get_valid_email_domains(value) valid_domains = [] - value.split("|").each do |domain| - domain.sub!(/^https?:\/\//, '') - domain.sub!(/\/.*$/, '') + value + .split("|") + .each do |domain| + domain.sub!(%r{^https?://}, "") + domain.sub!(%r{/.*$}, "") - if domain =~ Group::VALID_DOMAIN_REGEX - valid_domains << domain - else - yield domain if block_given? + if domain =~ Group::VALID_DOMAIN_REGEX + valid_domains << domain + else + yield domain if block_given? + end end - end valid_domains end @@ -1120,10 +1177,10 @@ class Group < ActiveRecord::Base def validate_grant_trust_level unless TrustLevel.valid?(self.grant_trust_level) - self.errors.add(:base, I18n.t( - 'groups.errors.grant_trust_level_not_valid', - trust_level: self.grant_trust_level - )) + self.errors.add( + :base, + I18n.t("groups.errors.grant_trust_level_not_valid", trust_level: self.grant_trust_level), + ) end end @@ -1137,15 +1194,14 @@ class Group < ActiveRecord::Base self.group_users.any?(&:owner) end - if !valid - self.errors.add(:base, I18n.t('groups.errors.cant_allow_membership_requests')) - end + self.errors.add(:base, I18n.t("groups.errors.cant_allow_membership_requests")) if !valid end def enqueue_update_mentions_job - Jobs.enqueue(:update_group_mentions, + Jobs.enqueue( + :update_group_mentions, previous_name: self.name_before_last_save, - group_id: self.id + group_id: self.id, ) end end diff --git a/app/models/group_archived_message.rb b/app/models/group_archived_message.rb index 72d71af025c..30d9feb5ce7 100644 --- a/app/models/group_archived_message.rb +++ b/app/models/group_archived_message.rb @@ -15,7 +15,7 @@ class GroupArchivedMessage < ActiveRecord::Base :group_pm_update_summary, group_id: group_id, topic_id: topic_id, - acting_user_id: opts[:acting_user_id] + acting_user_id: opts[:acting_user_id], ) end @@ -31,20 +31,19 @@ class GroupArchivedMessage < ActiveRecord::Base :group_pm_update_summary, group_id: group_id, topic_id: topic_id, - acting_user_id: opts[:acting_user_id] + acting_user_id: opts[:acting_user_id], ) end def self.trigger(event, group_id, topic_id) group = Group.find_by(id: group_id) topic = Topic.find_by(id: topic_id) - if group && topic - DiscourseEvent.trigger(event, group: group, topic: topic) - end + DiscourseEvent.trigger(event, group: group, topic: topic) if group && topic end def self.set_imap_sync(topic_id) - IncomingEmail.joins(:post) + IncomingEmail + .joins(:post) .where.not(imap_uid: nil) .where(topic_id: topic_id, posts: { post_number: 1 }) .update_all(imap_sync: true) @@ -55,7 +54,7 @@ class GroupArchivedMessage < ActiveRecord::Base PrivateMessageTopicTrackingState.publish_group_archived( topic: topic, group_id: group_id, - acting_user_id: acting_user_id + acting_user_id: acting_user_id, ) end private_class_method :publish_topic_tracking_state diff --git a/app/models/group_associated_group.rb b/app/models/group_associated_group.rb index be71f09eb08..4e9f963fbd1 100644 --- a/app/models/group_associated_group.rb +++ b/app/models/group_associated_group.rb @@ -3,22 +3,22 @@ class GroupAssociatedGroup < ActiveRecord::Base belongs_to :group belongs_to :associated_group - after_commit :add_associated_users, on: [:create, :update] + after_commit :add_associated_users, on: %i[create update] before_destroy :remove_associated_users def add_associated_users with_mutex do associated_group.users.in_batches do |users| - users.each do |user| - group.add_automatically(user, subject: associated_group.label) - end + users.each { |user| group.add_automatically(user, subject: associated_group.label) } end end end def remove_associated_users with_mutex do - User.where("NOT EXISTS( + User + .where( + "NOT EXISTS( SELECT 1 FROM user_associated_groups uag JOIN group_associated_groups gag @@ -26,11 +26,13 @@ class GroupAssociatedGroup < ActiveRecord::Base WHERE uag.user_id = users.id AND gag.id != :gag_id AND gag.group_id = :group_id - )", gag_id: id, group_id: group_id).in_batches do |users| - users.each do |user| - group.remove_automatically(user, subject: associated_group.label) + )", + gag_id: id, + group_id: group_id, + ) + .in_batches do |users| + users.each { |user| group.remove_automatically(user, subject: associated_group.label) } end - end end end diff --git a/app/models/group_category_notification_default.rb b/app/models/group_category_notification_default.rb index 858ef422b51..b75183ef1fd 100644 --- a/app/models/group_category_notification_default.rb +++ b/app/models/group_category_notification_default.rb @@ -19,28 +19,24 @@ class GroupCategoryNotificationDefault < ActiveRecord::Base changed = false # Update pre-existing - if category_ids.present? && GroupCategoryNotificationDefault - .where(group_id: group.id, category_id: category_ids) - .where.not(notification_level: level_num) - .update_all(notification_level: level_num) > 0 - + if category_ids.present? && + GroupCategoryNotificationDefault + .where(group_id: group.id, category_id: category_ids) + .where.not(notification_level: level_num) + .update_all(notification_level: level_num) > 0 changed = true end # Remove extraneous category users if GroupCategoryNotificationDefault - .where(group_id: group.id, notification_level: level_num) - .where.not(category_id: category_ids) - .delete_all > 0 - + .where(group_id: group.id, notification_level: level_num) + .where.not(category_id: category_ids) + .delete_all > 0 changed = true end if category_ids.present? - params = { - group_id: group.id, - level_num: level_num, - } + params = { group_id: group.id, level_num: level_num } sql = <<~SQL INSERT INTO group_category_notification_defaults (group_id, category_id, notification_level) @@ -52,9 +48,7 @@ class GroupCategoryNotificationDefault < ActiveRecord::Base # into the query, plus it is a bit of a micro optimisation category_ids.each do |category_id| params[:category_id] = category_id - if DB.exec(sql, params) > 0 - changed = true - end + changed = true if DB.exec(sql, params) > 0 end end diff --git a/app/models/group_history.rb b/app/models/group_history.rb index 83583824a6c..9f58072e581 100644 --- a/app/models/group_history.rb +++ b/app/models/group_history.rb @@ -2,43 +2,43 @@ class GroupHistory < ActiveRecord::Base belongs_to :group - belongs_to :acting_user, class_name: 'User' - belongs_to :target_user, class_name: 'User' + belongs_to :acting_user, class_name: "User" + belongs_to :target_user, class_name: "User" validates :acting_user_id, presence: true validates :group_id, presence: true validates :action, presence: true def self.actions - @actions ||= Enum.new( - change_group_setting: 1, - add_user_to_group: 2, - remove_user_from_group: 3, - make_user_group_owner: 4, - remove_user_as_group_owner: 5 - ) + @actions ||= + Enum.new( + change_group_setting: 1, + add_user_to_group: 2, + remove_user_from_group: 3, + make_user_group_owner: 4, + remove_user_as_group_owner: 5, + ) end def self.filters - [ - :acting_user, - :target_user, - :action, - :subject - ] + %i[acting_user target_user action subject] end def self.with_filters(group, params = {}) - records = self.includes(:acting_user, :target_user) - .where(group_id: group.id) - .order('group_histories.created_at DESC') + records = + self + .includes(:acting_user, :target_user) + .where(group_id: group.id) + .order("group_histories.created_at DESC") if !params.blank? params = params.slice(*filters) - records = records.where(action: self.actions[params[:action].to_sym]) unless params[:action].blank? + records = records.where(action: self.actions[params[:action].to_sym]) unless params[ + :action + ].blank? records = records.where(subject: params[:subject]) unless params[:subject].blank? - [:acting_user, :target_user].each do |filter| + %i[acting_user target_user].each do |filter| unless params[filter].blank? id = User.where(username_lower: params[filter]).pluck(:id) records = records.where("#{filter}_id" => id) diff --git a/app/models/group_tag_notification_default.rb b/app/models/group_tag_notification_default.rb index a72d554b5be..312dd2bd68e 100644 --- a/app/models/group_tag_notification_default.rb +++ b/app/models/group_tag_notification_default.rb @@ -21,15 +21,16 @@ class GroupTagNotificationDefault < ActiveRecord::Base tag_ids = tag_names.empty? ? [] : Tag.where_name(tag_names).pluck(:id) - Tag.where_name(tag_names).joins(:target_tag).each do |tag| - tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id - end + Tag + .where_name(tag_names) + .joins(:target_tag) + .each { |tag| tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id } tag_ids.uniq! remove = (old_ids - tag_ids) if remove.present? - records.where('tag_id in (?)', remove).destroy_all + records.where("tag_id in (?)", remove).destroy_all changed = true end diff --git a/app/models/group_user.rb b/app/models/group_user.rb index 1c5df59922e..cfd27f01f39 100644 --- a/app/models/group_user.rb +++ b/app/models/group_user.rb @@ -29,7 +29,8 @@ class GroupUser < ActiveRecord::Base def self.update_first_unread_pm(last_seen, limit: 10_000) whisperers_group_ids = SiteSetting.whispers_allowed_group_ids - DB.exec(<<~SQL, archetype: Archetype.private_message, last_seen: last_seen, limit: limit, now: 10.minutes.ago, whisperers_group_ids: whisperers_group_ids) + DB.exec( + <<~SQL, UPDATE group_users gu SET first_unread_pm_at = Y.min_date FROM ( @@ -56,7 +57,7 @@ class GroupUser < ActiveRecord::Base WHERE t.deleted_at IS NULL AND t.archetype = :archetype AND tu.last_read_post_number < CASE - WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu2.group_id IN (:whisperers_group_ids)' : ''} + WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? "OR gu2.group_id IN (:whisperers_group_ids)" : ""} THEN t.highest_staff_post_number ELSE t.highest_post_number END @@ -75,6 +76,12 @@ class GroupUser < ActiveRecord::Base ) Y WHERE gu.user_id = Y.user_id AND gu.group_id = Y.group_id SQL + archetype: Archetype.private_message, + last_seen: last_seen, + limit: limit, + now: 10.minutes.ago, + whisperers_group_ids: whisperers_group_ids, + ) end protected @@ -105,10 +112,12 @@ class GroupUser < ActiveRecord::Base def update_title if group.title.present? - DB.exec(" + DB.exec( + " UPDATE users SET title = :title WHERE (title IS NULL OR title = '') AND id = :id", - id: user_id, title: group.title + id: user_id, + title: group.title, ) end end @@ -131,28 +140,34 @@ class GroupUser < ActiveRecord::Base end def self.set_category_notifications(group, user) - group_levels = group.group_category_notification_defaults.each_with_object({}) do |r, h| - h[r.notification_level] ||= [] - h[r.notification_level] << r.category_id - end + group_levels = + group + .group_category_notification_defaults + .each_with_object({}) do |r, h| + h[r.notification_level] ||= [] + h[r.notification_level] << r.category_id + end return if group_levels.empty? - user_levels = CategoryUser.where(user_id: user.id).each_with_object({}) do |r, h| - h[r.notification_level] ||= [] - h[r.notification_level] << r.category_id - end + user_levels = + CategoryUser + .where(user_id: user.id) + .each_with_object({}) do |r, h| + h[r.notification_level] ||= [] + h[r.notification_level] << r.category_id + end higher_level_category_ids = user_levels.values.flatten - [:muted, :regular, :tracking, :watching_first_post, :watching].each do |level| + %i[muted regular tracking watching_first_post watching].each do |level| level_num = NotificationLevels.all[level] higher_level_category_ids -= (user_levels[level_num] || []) if group_category_ids = group_levels[level_num] CategoryUser.batch_set( user, level, - group_category_ids + (user_levels[level_num] || []) - higher_level_category_ids + group_category_ids + (user_levels[level_num] || []) - higher_level_category_ids, ) end end @@ -163,28 +178,34 @@ class GroupUser < ActiveRecord::Base end def self.set_tag_notifications(group, user) - group_levels = group.group_tag_notification_defaults.each_with_object({}) do |r, h| - h[r.notification_level] ||= [] - h[r.notification_level] << r.tag_id - end + group_levels = + group + .group_tag_notification_defaults + .each_with_object({}) do |r, h| + h[r.notification_level] ||= [] + h[r.notification_level] << r.tag_id + end return if group_levels.empty? - user_levels = TagUser.where(user_id: user.id).each_with_object({}) do |r, h| - h[r.notification_level] ||= [] - h[r.notification_level] << r.tag_id - end + user_levels = + TagUser + .where(user_id: user.id) + .each_with_object({}) do |r, h| + h[r.notification_level] ||= [] + h[r.notification_level] << r.tag_id + end higher_level_tag_ids = user_levels.values.flatten - [:muted, :regular, :tracking, :watching_first_post, :watching].each do |level| + %i[muted regular tracking watching_first_post watching].each do |level| level_num = NotificationLevels.all[level] higher_level_tag_ids -= (user_levels[level_num] || []) if group_tag_ids = group_levels[level_num] TagUser.batch_set( user, level, - group_tag_ids + (user_levels[level_num] || []) - higher_level_tag_ids + group_tag_ids + (user_levels[level_num] || []) - higher_level_tag_ids, ) end end diff --git a/app/models/imap_sync_log.rb b/app/models/imap_sync_log.rb index e1073dcd16a..28f216c4cb2 100644 --- a/app/models/imap_sync_log.rb +++ b/app/models/imap_sync_log.rb @@ -12,14 +12,18 @@ class ImapSyncLog < ActiveRecord::Base def self.log(message, level, group_id = nil, db = true) now = Time.now.strftime("%Y-%m-%d %H:%M:%S.%L") - new_log = if db - create(message: message, level: ImapSyncLog.levels[level], group_id: group_id) - end + new_log = (create(message: message, level: ImapSyncLog.levels[level], group_id: group_id) if db) if ENV["DEBUG_IMAP"] - Rails.logger.send(:warn, "#{level[0].upcase}, [#{now}] [IMAP] (group_id #{group_id}) #{message}") + Rails.logger.send( + :warn, + "#{level[0].upcase}, [#{now}] [IMAP] (group_id #{group_id}) #{message}", + ) else - Rails.logger.send(level, "#{level[0].upcase}, [#{now}] [IMAP] (group_id #{group_id}) #{message}") + Rails.logger.send( + level, + "#{level[0].upcase}, [#{now}] [IMAP] (group_id #{group_id}) #{message}", + ) end new_log diff --git a/app/models/incoming_domain.rb b/app/models/incoming_domain.rb index e9ef69ffaac..94d56cecd9f 100644 --- a/app/models/incoming_domain.rb +++ b/app/models/incoming_domain.rb @@ -25,9 +25,7 @@ class IncomingDomain < ActiveRecord::Base def to_url url = +"http#{https ? "s" : ""}://#{name}" - if https && port != 443 || !https && port != 80 - url << ":#{port}" - end + url << ":#{port}" if https && port != 443 || !https && port != 80 url end diff --git a/app/models/incoming_email.rb b/app/models/incoming_email.rb index b33ae191a8a..f565bbce67c 100644 --- a/app/models/incoming_email.rb +++ b/app/models/incoming_email.rb @@ -4,22 +4,19 @@ class IncomingEmail < ActiveRecord::Base belongs_to :user belongs_to :topic belongs_to :post - belongs_to :group, foreign_key: :imap_group_id, class_name: 'Group' + belongs_to :group, foreign_key: :imap_group_id, class_name: "Group" validates :created_via, presence: true - scope :errored, -> { where("NOT is_bounce AND error IS NOT NULL") } + scope :errored, -> { where("NOT is_bounce AND error IS NOT NULL") } - scope :addressed_to, -> (email) do - where(<<~SQL, email: "%#{email}%") + scope :addressed_to, ->(email) { where(<<~SQL, email: "%#{email}%") } incoming_emails.from_address = :email OR incoming_emails.to_addresses ILIKE :email OR incoming_emails.cc_addresses ILIKE :email SQL - end - scope :addressed_to_user, ->(user) do - where(<<~SQL, user_id: user.id) + scope :addressed_to_user, ->(user) { where(<<~SQL, user_id: user.id) } EXISTS( SELECT 1 FROM user_emails @@ -29,18 +26,11 @@ class IncomingEmail < ActiveRecord::Base incoming_emails.cc_addresses ILIKE '%' || user_emails.email || '%') ) SQL - end scope :without_raw, -> { select(self.column_names - ["raw"]) } def self.created_via_types - @types ||= Enum.new( - unknown: 0, - handle_mail: 1, - pop3_poll: 2, - imap: 3, - group_smtp: 4 - ) + @types ||= Enum.new(unknown: 0, handle_mail: 1, pop3_poll: 2, imap: 3, group_smtp: 4) end def as_mail_message @@ -64,23 +54,17 @@ class IncomingEmail < ActiveRecord::Base end def to_addresses=(to) - if to&.is_a?(Array) - to = to.map(&:downcase).join(";") - end + to = to.map(&:downcase).join(";") if to&.is_a?(Array) super(to) end def cc_addresses=(cc) - if cc&.is_a?(Array) - cc = cc.map(&:downcase).join(";") - end + cc = cc.map(&:downcase).join(";") if cc&.is_a?(Array) super(cc) end def from_address=(from) - if from&.is_a?(Array) - from = from.first - end + from = from.first if from&.is_a?(Array) super(from) end end diff --git a/app/models/incoming_link.rb b/app/models/incoming_link.rb index 4e51a668231..acb7d0b3955 100644 --- a/app/models/incoming_link.rb +++ b/app/models/incoming_link.rb @@ -35,35 +35,32 @@ class IncomingLink < ActiveRecord::Base end if host != opts[:host] && (user_id || referer) - post_id = opts[:post_id] - post_id ||= Post.where(topic_id: opts[:topic_id], - post_number: opts[:post_number] || 1) - .pluck_first(:id) + post_id ||= + Post.where(topic_id: opts[:topic_id], post_number: opts[:post_number] || 1).pluck_first(:id) cid = current_user ? (current_user.id) : (nil) ip_address = nil if cid unless cid && cid == user_id - - create(referer: referer, - user_id: user_id, - post_id: post_id, - current_user_id: cid, - ip_address: ip_address) if post_id - + if post_id + create( + referer: referer, + user_id: user_id, + post_id: post_id, + current_user_id: cid, + ip_address: ip_address, + ) + end end end - end def referer=(referer) self.incoming_referer_id = nil # will set incoming_referer_id - unless referer.present? - return - end + return unless referer.present? parsed = URI.parse(referer) @@ -73,7 +70,6 @@ class IncomingLink < ActiveRecord::Base referer_record = IncomingReferer.add!(path: parsed.path, incoming_domain: domain) if domain self.incoming_referer_id = referer_record.id if referer_record end - rescue URI::Error # ignore end @@ -85,19 +81,23 @@ class IncomingLink < ActiveRecord::Base end def domain - if incoming_referer - incoming_referer.incoming_domain.name - end + incoming_referer.incoming_domain.name if incoming_referer end # Internal: Update appropriate link counts. def update_link_counts - DB.exec("UPDATE topics + DB.exec( + "UPDATE topics SET incoming_link_count = incoming_link_count + 1 - WHERE id = (SELECT topic_id FROM posts where id = ?)", post_id) - DB.exec("UPDATE posts + WHERE id = (SELECT topic_id FROM posts where id = ?)", + post_id, + ) + DB.exec( + "UPDATE posts SET incoming_link_count = incoming_link_count + 1 - WHERE id = ?", post_id) + WHERE id = ?", + post_id, + ) end protected @@ -106,7 +106,7 @@ class IncomingLink < ActiveRecord::Base return true unless referer if (referer.length < 3 || referer.length > 100) || (domain.length < 1 || domain.length > 100) # internal, no need to localize - errors.add(:referer, 'referer is invalid') + errors.add(:referer, "referer is invalid") false else true diff --git a/app/models/incoming_links_report.rb b/app/models/incoming_links_report.rb index e17f893a939..3c17ff38100 100644 --- a/app/models/incoming_links_report.rb +++ b/app/models/incoming_links_report.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true class IncomingLinksReport - - attr_accessor :type, :data, :y_titles, :start_date, :end_date, :limit, :category_id, :include_subcategories + attr_accessor :type, + :data, + :y_titles, + :start_date, + :end_date, + :limit, + :category_id, + :include_subcategories def initialize(type) @type = type @@ -20,7 +26,7 @@ class IncomingLinksReport ytitles: self.y_titles, data: self.data, start_date: start_date, - end_date: end_date + end_date: end_date, } end @@ -46,18 +52,31 @@ class IncomingLinksReport report.y_titles[:num_clicks] = I18n.t("reports.#{report.type}.num_clicks") report.y_titles[:num_topics] = I18n.t("reports.#{report.type}.num_topics") - num_clicks = link_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories) - num_topics = topic_count_per_user(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories) - user_id_lookup = User - .where(username: num_clicks.keys) - .select(:id, :username, :uploaded_avatar_id) - .inject({}) { |sum, v| - sum[v.username] = { - id: v.id, - user_avatar_template: User.avatar_template(v.username, v.uploaded_avatar_id) - } - sum - } + num_clicks = + link_count_per_user( + start_date: report.start_date, + end_date: report.end_date, + category_id: report.category_id, + include_subcategories: report.include_subcategories, + ) + num_topics = + topic_count_per_user( + start_date: report.start_date, + end_date: report.end_date, + category_id: report.category_id, + include_subcategories: report.include_subcategories, + ) + user_id_lookup = + User + .where(username: num_clicks.keys) + .select(:id, :username, :uploaded_avatar_id) + .inject({}) do |sum, v| + sum[v.username] = { + id: v.id, + user_avatar_template: User.avatar_template(v.username, v.uploaded_avatar_id), + } + sum + end report.data = [] num_clicks.each_key do |username| @@ -66,7 +85,7 @@ class IncomingLinksReport user_id: user_id_lookup[username][:id], user_avatar_template: user_id_lookup[username][:user_avatar_template], num_clicks: num_clicks[username], - num_topics: num_topics[username] + num_topics: num_topics[username], } end report.data = report.data.sort_by { |x| x[:num_clicks] }.reverse[0, 10] @@ -74,17 +93,31 @@ class IncomingLinksReport def self.per_user(start_date:, end_date:, category_id:, include_subcategories:) public_incoming_links(category_id: category_id, include_subcategories: include_subcategories) - .where('incoming_links.created_at > ? AND incoming_links.created_at < ? AND incoming_links.user_id IS NOT NULL', start_date, end_date) + .where( + "incoming_links.created_at > ? AND incoming_links.created_at < ? AND incoming_links.user_id IS NOT NULL", + start_date, + end_date, + ) .joins(:user) - .group('users.username') + .group("users.username") end def self.link_count_per_user(start_date:, end_date:, category_id:, include_subcategories:) - per_user(start_date: start_date, end_date: end_date, category_id: category_id, include_subcategories: include_subcategories).count + per_user( + start_date: start_date, + end_date: end_date, + category_id: category_id, + include_subcategories: include_subcategories, + ).count end def self.topic_count_per_user(start_date:, end_date:, category_id:, include_subcategories:) - per_user(start_date: start_date, end_date: end_date, category_id: category_id, include_subcategories: include_subcategories).joins(:post).count("DISTINCT posts.topic_id") + per_user( + start_date: start_date, + end_date: end_date, + category_id: category_id, + include_subcategories: include_subcategories, + ).joins(:post).count("DISTINCT posts.topic_id") end # Return top 10 domains that brought traffic to the site within the last 30 days @@ -93,30 +126,58 @@ class IncomingLinksReport report.y_titles[:num_topics] = I18n.t("reports.#{report.type}.num_topics") report.y_titles[:num_users] = I18n.t("reports.#{report.type}.num_users") - num_clicks = link_count_per_domain(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories) - num_topics = topic_count_per_domain(num_clicks.keys, category_id: report.category_id, include_subcategories: report.include_subcategories) + num_clicks = + link_count_per_domain( + start_date: report.start_date, + end_date: report.end_date, + category_id: report.category_id, + include_subcategories: report.include_subcategories, + ) + num_topics = + topic_count_per_domain( + num_clicks.keys, + category_id: report.category_id, + include_subcategories: report.include_subcategories, + ) report.data = [] num_clicks.each_key do |domain| - report.data << { domain: domain, num_clicks: num_clicks[domain], num_topics: num_topics[domain] } + report.data << { + domain: domain, + num_clicks: num_clicks[domain], + num_topics: num_topics[domain], + } end report.data = report.data.sort_by { |x| x[:num_clicks] }.reverse[0, 10] end - def self.link_count_per_domain(limit: 10, start_date:, end_date:, category_id:, include_subcategories:) + def self.link_count_per_domain( + limit: 10, + start_date:, + end_date:, + category_id:, + include_subcategories: + ) public_incoming_links(category_id: category_id, include_subcategories: include_subcategories) - .where('incoming_links.created_at > ? AND incoming_links.created_at < ?', start_date, end_date) + .where( + "incoming_links.created_at > ? AND incoming_links.created_at < ?", + start_date, + end_date, + ) .joins(incoming_referer: :incoming_domain) - .group('incoming_domains.name') - .order('count_all DESC') + .group("incoming_domains.name") + .order("count_all DESC") .limit(limit) .count end def self.per_domain(domains, options = {}) - public_incoming_links(category_id: options[:category_id], include_subcategories: options[:include_subcategories]) + public_incoming_links( + category_id: options[:category_id], + include_subcategories: options[:include_subcategories], + ) .joins(incoming_referer: :incoming_domain) - .where('incoming_links.created_at > ? AND incoming_domains.name IN (?)', 30.days.ago, domains) - .group('incoming_domains.name') + .where("incoming_links.created_at > ? AND incoming_domains.name IN (?)", 30.days.ago, domains) + .group("incoming_domains.name") end def self.topic_count_per_domain(domains, options = {}) @@ -126,17 +187,38 @@ class IncomingLinksReport def self.report_top_referred_topics(report) report.y_titles[:num_clicks] = I18n.t("reports.#{report.type}.labels.num_clicks") - num_clicks = link_count_per_topic(start_date: report.start_date, end_date: report.end_date, category_id: report.category_id, include_subcategories: report.include_subcategories) + num_clicks = + link_count_per_topic( + start_date: report.start_date, + end_date: report.end_date, + category_id: report.category_id, + include_subcategories: report.include_subcategories, + ) num_clicks = num_clicks.to_a.sort_by { |x| x[1] }.last(report.limit || 10).reverse report.data = [] - topics = Topic.select('id, slug, title').where('id in (?)', num_clicks.map { |z| z[0] }) + topics = Topic.select("id, slug, title").where("id in (?)", num_clicks.map { |z| z[0] }) if report.category_id - topics = topics.where(category_id: report.include_subcategories ? Category.subcategory_ids(report.category_id) : report.category_id) + topics = + topics.where( + category_id: + ( + if report.include_subcategories + Category.subcategory_ids(report.category_id) + else + report.category_id + end + ), + ) end num_clicks.each do |topic_id, num_clicks_element| topic = topics.find { |t| t.id == topic_id } if topic - report.data << { topic_id: topic_id, topic_title: topic.title, topic_url: topic.relative_url, num_clicks: num_clicks_element } + report.data << { + topic_id: topic_id, + topic_title: topic.title, + topic_url: topic.relative_url, + num_clicks: num_clicks_element, + } end end report.data @@ -144,15 +226,17 @@ class IncomingLinksReport def self.link_count_per_topic(start_date:, end_date:, category_id:, include_subcategories:) public_incoming_links(category_id: category_id, include_subcategories: include_subcategories) - .where('incoming_links.created_at > ? AND incoming_links.created_at < ? AND topic_id IS NOT NULL', start_date, end_date) - .group('topic_id') + .where( + "incoming_links.created_at > ? AND incoming_links.created_at < ? AND topic_id IS NOT NULL", + start_date, + end_date, + ) + .group("topic_id") .count end def self.public_incoming_links(category_id: nil, include_subcategories: nil) - links = IncomingLink - .joins(post: :topic) - .where("topics.archetype = ?", Archetype.default) + links = IncomingLink.joins(post: :topic).where("topics.archetype = ?", Archetype.default) if category_id if include_subcategories diff --git a/app/models/invite.rb b/app/models/invite.rb index 2712760ab1c..dae62e32317 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -1,25 +1,26 @@ # frozen_string_literal: true class Invite < ActiveRecord::Base - class UserExists < StandardError; end - class RedemptionFailed < StandardError; end - class ValidationFailed < StandardError; end + class UserExists < StandardError + end + class RedemptionFailed < StandardError + end + class ValidationFailed < StandardError + end include RateLimiter::OnCreateRecord include Trashable # TODO(2021-05-22): remove - self.ignored_columns = %w{ - user_id - redeemed_at - } + self.ignored_columns = %w[user_id redeemed_at] BULK_INVITE_EMAIL_LIMIT = 200 - DOMAIN_REGEX = /\A(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\z/ + DOMAIN_REGEX = + /\A(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])\z/ rate_limit :limit_invites_per_day - belongs_to :invited_by, class_name: 'User' + belongs_to :invited_by, class_name: "User" has_many :invited_users has_many :users, through: :invited_users @@ -42,19 +43,16 @@ class Invite < ActiveRecord::Base end before_save do - if will_save_change_to_email? - self.email_token = email.present? ? SecureRandom.hex : nil - end + self.email_token = email.present? ? SecureRandom.hex : nil if will_save_change_to_email? end - before_validation do - self.email = Email.downcase(email) unless email.nil? - end + before_validation { self.email = Email.downcase(email) unless email.nil? } attribute :email_already_exists def self.emailed_status_types - @emailed_status_types ||= Enum.new(not_required: 0, pending: 1, bulk_pending: 2, sending: 3, sent: 4) + @emailed_status_types ||= + Enum.new(not_required: 0, pending: 1, bulk_pending: 2, sending: 3, sent: 4) end def user_doesnt_already_exist @@ -69,9 +67,7 @@ class Invite < ActiveRecord::Base end def email_xor_domain - if email.present? && domain.present? - errors.add(:base, I18n.t('invite.email_xor_domain')) - end + errors.add(:base, I18n.t("invite.email_xor_domain")) if email.present? && domain.present? end # Even if a domain is specified on the invite, it still counts as @@ -107,7 +103,7 @@ class Invite < ActiveRecord::Base end def domain_matches?(email) - _, domain = email.split('@') + _, domain = email.split("@") self.domain == domain end @@ -124,8 +120,11 @@ class Invite < ActiveRecord::Base end def link(with_email_token: false) - with_email_token ? "#{Discourse.base_url}/invites/#{invite_key}?t=#{email_token}" - : "#{Discourse.base_url}/invites/#{invite_key}" + if with_email_token + "#{Discourse.base_url}/invites/#{invite_key}?t=#{email_token}" + else + "#{Discourse.base_url}/invites/#{invite_key}" + end end def link_valid? @@ -140,11 +139,12 @@ class Invite < ActiveRecord::Base raise UserExists.new(new.user_exists_error_msg(email)) if find_user_by_email(email) if email.present? - invite = Invite - .with_deleted - .where(email: email, invited_by_id: invited_by.id) - .order('created_at DESC') - .first + invite = + Invite + .with_deleted + .where(email: email, invited_by_id: invited_by.id) + .order("created_at DESC") + .first if invite && (invite.expired? || invite.deleted_at) invite.destroy @@ -154,25 +154,27 @@ class Invite < ActiveRecord::Base RateLimiter.new(invited_by, "reinvites-per-day-#{email_digest}", 3, 1.day.to_i).performed! end - emailed_status = if opts[:skip_email] || invite&.emailed_status == emailed_status_types[:not_required] - emailed_status_types[:not_required] - elsif opts[:emailed_status].present? - opts[:emailed_status] - elsif email.present? - emailed_status_types[:pending] - else - emailed_status_types[:not_required] - end + emailed_status = + if opts[:skip_email] || invite&.emailed_status == emailed_status_types[:not_required] + emailed_status_types[:not_required] + elsif opts[:emailed_status].present? + opts[:emailed_status] + elsif email.present? + emailed_status_types[:pending] + else + emailed_status_types[:not_required] + end if invite invite.update_columns( created_at: Time.zone.now, updated_at: Time.zone.now, expires_at: opts[:expires_at] || SiteSetting.invite_expiry_days.days.from_now, - emailed_status: emailed_status + emailed_status: emailed_status, ) else - create_args = opts.slice(:email, :domain, :moderator, :custom_message, :max_redemptions_allowed) + create_args = + opts.slice(:email, :domain, :moderator, :custom_message, :max_redemptions_allowed) create_args[:invited_by] = invited_by create_args[:email] = email create_args[:emailed_status] = emailed_status @@ -182,15 +184,11 @@ class Invite < ActiveRecord::Base end topic_id = opts[:topic]&.id || opts[:topic_id] - if topic_id.present? - invite.topic_invites.find_or_create_by!(topic_id: topic_id) - end + invite.topic_invites.find_or_create_by!(topic_id: topic_id) if topic_id.present? group_ids = opts[:group_ids] if group_ids.present? - group_ids.each do |group_id| - invite.invited_groups.find_or_create_by!(group_id: group_id) - end + group_ids.each { |group_id| invite.invited_groups.find_or_create_by!(group_id: group_id) } end if emailed_status == emailed_status_types[:pending] @@ -224,7 +222,7 @@ class Invite < ActiveRecord::Base ip_address: ip_address, session: session, email_token: email_token, - redeeming_user: redeeming_user + redeeming_user: redeeming_user, ).redeem end @@ -241,35 +239,37 @@ class Invite < ActiveRecord::Base end def self.pending(inviter) - Invite.distinct + Invite + .distinct .joins("LEFT JOIN invited_users ON invites.id = invited_users.invite_id") .joins("LEFT JOIN users ON invited_users.user_id = users.id") .where(invited_by_id: inviter.id) - .where('redemption_count < max_redemptions_allowed') - .where('expires_at > ?', Time.zone.now) - .order('invites.updated_at DESC') + .where("redemption_count < max_redemptions_allowed") + .where("expires_at > ?", Time.zone.now) + .order("invites.updated_at DESC") end def self.expired(inviter) - Invite.distinct + Invite + .distinct .joins("LEFT JOIN invited_users ON invites.id = invited_users.invite_id") .joins("LEFT JOIN users ON invited_users.user_id = users.id") .where(invited_by_id: inviter.id) - .where('redemption_count < max_redemptions_allowed') - .where('expires_at < ?', Time.zone.now) - .order('invites.expires_at ASC') + .where("redemption_count < max_redemptions_allowed") + .where("expires_at < ?", Time.zone.now) + .order("invites.expires_at ASC") end def self.redeemed_users(inviter) InvitedUser .joins("LEFT JOIN invites ON invites.id = invited_users.invite_id") .includes(user: :user_stat) - .where('invited_users.user_id IS NOT NULL') - .where('invites.invited_by_id = ?', inviter.id) - .order('invited_users.redeemed_at DESC') - .references('invite') - .references('user') - .references('user_stat') + .where("invited_users.user_id IS NOT NULL") + .where("invites.invited_by_id = ?", inviter.id) + .order("invited_users.redeemed_at DESC") + .references("invite") + .references("user") + .references("user_stat") end def self.invalidate_for_email(email) @@ -280,7 +280,11 @@ class Invite < ActiveRecord::Base end def resend_invite - self.update_columns(updated_at: Time.zone.now, invalidated_at: nil, expires_at: SiteSetting.invite_expiry_days.days.from_now) + self.update_columns( + updated_at: Time.zone.now, + invalidated_at: nil, + expires_at: SiteSetting.invite_expiry_days.days.from_now, + ) Jobs.enqueue(:invite_email, invite_id: self.id) end @@ -289,27 +293,48 @@ class Invite < ActiveRecord::Base end def self.base_directory - File.join(Rails.root, "public", "uploads", "csv", RailsMultisite::ConnectionManagement.current_db) + File.join( + Rails.root, + "public", + "uploads", + "csv", + RailsMultisite::ConnectionManagement.current_db, + ) end def ensure_max_redemptions_allowed if self.max_redemptions_allowed.nil? self.max_redemptions_allowed = 1 else - limit = invited_by&.staff? ? SiteSetting.invite_link_max_redemptions_limit - : SiteSetting.invite_link_max_redemptions_limit_users + limit = + ( + if invited_by&.staff? + SiteSetting.invite_link_max_redemptions_limit + else + SiteSetting.invite_link_max_redemptions_limit_users + end + ) if self.email.present? && self.max_redemptions_allowed != 1 errors.add(:max_redemptions_allowed, I18n.t("invite.max_redemptions_allowed_one")) elsif !self.max_redemptions_allowed.between?(1, limit) - errors.add(:max_redemptions_allowed, I18n.t("invite_link.max_redemptions_limit", max_limit: limit)) + errors.add( + :max_redemptions_allowed, + I18n.t("invite_link.max_redemptions_limit", max_limit: limit), + ) end end end def valid_redemption_count if self.redemption_count > self.max_redemptions_allowed - errors.add(:redemption_count, I18n.t("invite.redemption_count_less_than_max", max_redemptions_allowed: self.max_redemptions_allowed)) + errors.add( + :redemption_count, + I18n.t( + "invite.redemption_count_less_than_max", + max_redemptions_allowed: self.max_redemptions_allowed, + ), + ) end end @@ -319,7 +344,7 @@ class Invite < ActiveRecord::Base self.domain.downcase! if self.domain !~ Invite::DOMAIN_REGEX - self.errors.add(:base, I18n.t('invite.domain_not_allowed')) + self.errors.add(:base, I18n.t("invite.domain_not_allowed")) end end diff --git a/app/models/invite_redeemer.rb b/app/models/invite_redeemer.rb index 6a7ae4b22a0..3fa9ac7313f 100644 --- a/app/models/invite_redeemer.rb +++ b/app/models/invite_redeemer.rb @@ -15,15 +15,15 @@ # (email IS NULL) on the Invite model. class InviteRedeemer attr_reader :invite, - :email, - :username, - :name, - :password, - :user_custom_fields, - :ip_address, - :session, - :email_token, - :redeeming_user + :email, + :username, + :name, + :password, + :user_custom_fields, + :ip_address, + :session, + :email_token, + :redeeming_user def initialize( invite:, @@ -35,7 +35,8 @@ class InviteRedeemer ip_address: nil, session: nil, email_token: nil, - redeeming_user: nil) + redeeming_user: nil + ) @invite = invite @username = username @name = name @@ -81,8 +82,19 @@ class InviteRedeemer # This will _never_ be called if there is a redeeming_user being passed # in to InviteRedeemer -- see invited_user below. - def self.create_user_from_invite(email:, invite:, username: nil, name: nil, password: nil, user_custom_fields: nil, ip_address: nil, session: nil, email_token: nil) - if username && UsernameValidator.new(username).valid_format? && User.username_available?(username, email) + def self.create_user_from_invite( + email:, + invite:, + username: nil, + name: nil, + password: nil, + user_custom_fields: nil, + ip_address: nil, + session: nil, + email_token: nil + ) + if username && UsernameValidator.new(username).valid_format? && + User.username_available?(username, email) available_username = username else available_username = UserNameSuggester.suggest(email) @@ -99,12 +111,11 @@ class InviteRedeemer active: false, trust_level: SiteSetting.default_invitee_trust_level, ip_address: ip_address, - registration_ip_address: ip_address + registration_ip_address: ip_address, } if (!SiteSetting.must_approve_users && SiteSetting.invite_only) || - (SiteSetting.must_approve_users? && EmailValidator.can_auto_approve_user?(user.email)) - + (SiteSetting.must_approve_users? && EmailValidator.can_auto_approve_user?(user.email)) ReviewableUser.set_approved_fields!(user, Discourse.system_user) end @@ -115,7 +126,9 @@ class InviteRedeemer user_fields.each do |f| field_val = field_params[f.id.to_s] - fields["#{User::USER_FIELD_PREFIX}#{f.id}"] = field_val[0...UserField.max_length] unless field_val.blank? + fields["#{User::USER_FIELD_PREFIX}#{f.id}"] = field_val[ + 0...UserField.max_length + ] unless field_val.blank? end user.custom_fields = fields end @@ -143,9 +156,7 @@ class InviteRedeemer authenticator.finish if invite.emailed_status != Invite.emailed_status_types[:not_required] && - email == invite.email && - invite.email_token.present? && - email_token == invite.email_token + email == invite.email && invite.email_token.present? && email_token == invite.email_token user.activate end @@ -159,9 +170,7 @@ class InviteRedeemer return false if email.blank? # Invite scoped to email has already been redeemed by anyone. - if invite.is_email_invite? && InvitedUser.exists?(invite_id: invite.id) - return false - end + return false if invite.is_email_invite? && InvitedUser.exists?(invite_id: invite.id) # The email will be present for either an invite link (where the user provides # us the email manually) or for an invite scoped to an email, where we @@ -171,17 +180,18 @@ class InviteRedeemer email_to_check = redeeming_user&.email || email if invite.email.present? && !invite.email_matches?(email_to_check) - raise ActiveRecord::RecordNotSaved.new(I18n.t('invite.not_matching_email')) + raise ActiveRecord::RecordNotSaved.new(I18n.t("invite.not_matching_email")) end if invite.domain.present? && !invite.domain_matches?(email_to_check) - raise ActiveRecord::RecordNotSaved.new(I18n.t('invite.domain_not_allowed')) + raise ActiveRecord::RecordNotSaved.new(I18n.t("invite.domain_not_allowed")) end # Anon user is trying to redeem an invitation, if an existing user already # redeemed it then we cannot redeem now. redeeming_user ||= User.where(admin: false, staged: false).find_by_email(email) - if redeeming_user.present? && InvitedUser.exists?(user_id: redeeming_user.id, invite_id: invite.id) + if redeeming_user.present? && + InvitedUser.exists?(user_id: redeeming_user.id, invite_id: invite.id) return false end @@ -205,17 +215,18 @@ class InviteRedeemer # If there was no logged in user then we must attempt to create # one based on the provided params. - invited_user ||= InviteRedeemer.create_user_from_invite( - email: email, - invite: invite, - username: username, - name: name, - password: password, - user_custom_fields: user_custom_fields, - ip_address: ip_address, - session: session, - email_token: email_token - ) + invited_user ||= + InviteRedeemer.create_user_from_invite( + email: email, + invite: invite, + username: username, + name: name, + password: password, + user_custom_fields: user_custom_fields, + ip_address: ip_address, + session: session, + email_token: email_token, + ) invited_user.send_welcome_message = false @invited_user = invited_user @invited_user @@ -243,11 +254,13 @@ class InviteRedeemer # Should not happen because of ensure_email_is_present!, but better to cover bases. return if email.blank? - topic_ids = TopicInvite.joins(:invite) - .joins(:topic) - .where("topics.archetype = ?", Archetype::private_message) - .where("invites.email = ?", email) - .pluck(:topic_id) + topic_ids = + TopicInvite + .joins(:invite) + .joins(:topic) + .where("topics.archetype = ?", Archetype.private_message) + .where("invites.email = ?", email) + .pluck(:topic_id) topic_ids.each do |id| if !TopicAllowedUser.exists?(user_id: invited_user.id, topic_id: id) TopicAllowedUser.create!(user_id: invited_user.id, topic_id: id) @@ -277,7 +290,7 @@ class InviteRedeemer return if invite.invited_by.blank? invite.invited_by.notifications.create!( notification_type: Notification.types[:invitee_accepted], - data: { display_username: invited_user.username }.to_json + data: { display_username: invited_user.username }.to_json, ) end @@ -286,10 +299,10 @@ class InviteRedeemer return if email.blank? Invite - .where('invites.max_redemptions_allowed = 1') + .where("invites.max_redemptions_allowed = 1") .joins("LEFT JOIN invited_users ON invites.id = invited_users.invite_id") - .where('invited_users.user_id IS NULL') - .where('invites.email = ? AND invites.id != ?', email, invite.id) + .where("invited_users.user_id IS NULL") + .where("invites.email = ? AND invites.id != ?", email, invite.id) .delete_all end end diff --git a/app/models/like_notification_frequency_site_setting.rb b/app/models/like_notification_frequency_site_setting.rb index 19d7043627d..5d8622e7b53 100644 --- a/app/models/like_notification_frequency_site_setting.rb +++ b/app/models/like_notification_frequency_site_setting.rb @@ -1,23 +1,20 @@ # frozen_string_literal: true class LikeNotificationFrequencySiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.like_notification_frequency.always', value: 0 }, - { name: 'user.like_notification_frequency.first_time_and_daily', value: 1 }, - { name: 'user.like_notification_frequency.first_time', value: 2 }, - { name: 'user.like_notification_frequency.never', value: 3 }, + { name: "user.like_notification_frequency.always", value: 0 }, + { name: "user.like_notification_frequency.first_time_and_daily", value: 1 }, + { name: "user.like_notification_frequency.first_time", value: 2 }, + { name: "user.like_notification_frequency.never", value: 3 }, ] end def self.translate_names? true end - end diff --git a/app/models/locale_site_setting.rb b/app/models/locale_site_setting.rb index 68431871648..8d48568d675 100644 --- a/app/models/locale_site_setting.rb +++ b/app/models/locale_site_setting.rb @@ -1,19 +1,16 @@ # frozen_string_literal: true class LocaleSiteSetting < EnumSiteSetting - def self.valid_value?(val) supported_locales.include?(val) end def self.values - @values ||= supported_locales.map do |locale| - lang = language_names[locale] || language_names[locale.split("_")[0]] - { - name: lang ? lang['nativeName'] : locale, - value: locale - } - end + @values ||= + supported_locales.map do |locale| + lang = language_names[locale] || language_names[locale.split("_")[0]] + { name: lang ? lang["nativeName"] : locale, value: locale } + end end @lock = Mutex.new @@ -22,42 +19,41 @@ class LocaleSiteSetting < EnumSiteSetting return @language_names if @language_names @lock.synchronize do - @language_names ||= begin - names = YAML.safe_load(File.read(File.join(Rails.root, 'config', 'locales', 'names.yml'))) + @language_names ||= + begin + names = YAML.safe_load(File.read(File.join(Rails.root, "config", "locales", "names.yml"))) - DiscoursePluginRegistry.locales.each do |locale, options| - if !names.key?(locale) && options[:name] && options[:nativeName] - names[locale] = { "name" => options[:name], "nativeName" => options[:nativeName] } + DiscoursePluginRegistry.locales.each do |locale, options| + if !names.key?(locale) && options[:name] && options[:nativeName] + names[locale] = { "name" => options[:name], "nativeName" => options[:nativeName] } + end end - end - names - end + names + end end end def self.supported_locales @lock.synchronize do - @supported_locales ||= begin - locales = Dir.glob( - File.join(Rails.root, 'config', 'locales', 'client.*.yml') - ).map { |x| x.split('.')[-2] } + @supported_locales ||= + begin + locales = + Dir + .glob(File.join(Rails.root, "config", "locales", "client.*.yml")) + .map { |x| x.split(".")[-2] } - locales += DiscoursePluginRegistry.locales.keys - locales.uniq.sort - end + locales += DiscoursePluginRegistry.locales.keys + locales.uniq.sort + end end end def self.reset! - @lock.synchronize do - @values = @language_names = @supported_locales = nil - end + @lock.synchronize { @values = @language_names = @supported_locales = nil } end - FALLBACKS ||= { - en_GB: :en - } + FALLBACKS ||= { en_GB: :en } def self.fallback_locale(locale) fallback_locale = FALLBACKS[locale.to_sym] diff --git a/app/models/mailing_list_mode_site_setting.rb b/app/models/mailing_list_mode_site_setting.rb index 2192a6fbf7d..859b780dd39 100644 --- a/app/models/mailing_list_mode_site_setting.rb +++ b/app/models/mailing_list_mode_site_setting.rb @@ -2,14 +2,13 @@ class MailingListModeSiteSetting < EnumSiteSetting def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.mailing_list_mode.individual', value: 1 }, - { name: 'user.mailing_list_mode.individual_no_echo', value: 2 } + { name: "user.mailing_list_mode.individual", value: 1 }, + { name: "user.mailing_list_mode.individual_no_echo", value: 2 }, ] end diff --git a/app/models/muted_user.rb b/app/models/muted_user.rb index 4be90200cd3..dd0a544c63d 100644 --- a/app/models/muted_user.rb +++ b/app/models/muted_user.rb @@ -2,7 +2,7 @@ class MutedUser < ActiveRecord::Base belongs_to :user - belongs_to :muted_user, class_name: 'User' + belongs_to :muted_user, class_name: "User" end # == Schema Information diff --git a/app/models/navigation_menu_site_setting.rb b/app/models/navigation_menu_site_setting.rb index d71cac2fdd2..bc7b38540de 100644 --- a/app/models/navigation_menu_site_setting.rb +++ b/app/models/navigation_menu_site_setting.rb @@ -13,7 +13,7 @@ class NavigationMenuSiteSetting < EnumSiteSetting @values ||= [ { name: "admin.navigation_menu.sidebar", value: SIDEBAR }, { name: "admin.navigation_menu.header_dropdown", value: HEADER_DROPDOWN }, - { name: "admin.navigation_menu.legacy", value: LEGACY } + { name: "admin.navigation_menu.legacy", value: LEGACY }, ] end diff --git a/app/models/new_topic_duration_site_setting.rb b/app/models/new_topic_duration_site_setting.rb index 3ed58210ae8..e1f5b1fdae6 100644 --- a/app/models/new_topic_duration_site_setting.rb +++ b/app/models/new_topic_duration_site_setting.rb @@ -1,25 +1,22 @@ # frozen_string_literal: true class NewTopicDurationSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.new_topic_duration.not_viewed', value: -1 }, - { name: 'user.new_topic_duration.after_1_day', value: 60 * 24 }, - { name: 'user.new_topic_duration.after_2_days', value: 60 * 24 * 2 }, - { name: 'user.new_topic_duration.after_1_week', value: 60 * 24 * 7 }, - { name: 'user.new_topic_duration.after_2_weeks', value: 60 * 24 * 7 * 2 }, - { name: 'user.new_topic_duration.last_here', value: -2 }, + { name: "user.new_topic_duration.not_viewed", value: -1 }, + { name: "user.new_topic_duration.after_1_day", value: 60 * 24 }, + { name: "user.new_topic_duration.after_2_days", value: 60 * 24 * 2 }, + { name: "user.new_topic_duration.after_1_week", value: 60 * 24 * 7 }, + { name: "user.new_topic_duration.after_2_weeks", value: 60 * 24 * 7 * 2 }, + { name: "user.new_topic_duration.last_here", value: -2 }, ] end def self.translate_names? true end - end diff --git a/app/models/notification.rb b/app/models/notification.rb index c4843a70e6f..f8aeab2b73d 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -12,48 +12,58 @@ class Notification < ActiveRecord::Base validates_presence_of :notification_type scope :unread, lambda { where(read: false) } - scope :recent, lambda { |n = nil| n ||= 10; order('notifications.created_at desc').limit(n) } - scope :visible , lambda { joins('LEFT JOIN topics ON notifications.topic_id = topics.id') - .where('topics.id IS NULL OR topics.deleted_at IS NULL') } - scope :unread_type, ->(user, type, limit = 30) do - unread_types(user, [type], limit) - end - scope :unread_types, ->(user, types, limit = 30) do - where(user_id: user.id, read: false, notification_type: types) - .visible - .includes(:topic) - .limit(limit) - end - scope :prioritized, ->(deprioritized_types = []) do - scope = order("notifications.high_priority AND NOT notifications.read DESC") - if deprioritized_types.present? - scope = scope.order(DB.sql_fragment("NOT notifications.read AND notifications.notification_type NOT IN (?) DESC", deprioritized_types)) - else - scope = scope.order("NOT notifications.read DESC") - end - scope.order("notifications.created_at DESC") - end - scope :for_user_menu, ->(user_id, limit: 30) do - where(user_id: user_id) - .visible - .prioritized - .includes(:topic) - .limit(limit) - end + scope :recent, + lambda { |n = nil| + n ||= 10 + order("notifications.created_at desc").limit(n) + } + scope :visible, + lambda { + joins("LEFT JOIN topics ON notifications.topic_id = topics.id").where( + "topics.id IS NULL OR topics.deleted_at IS NULL", + ) + } + scope :unread_type, ->(user, type, limit = 30) { unread_types(user, [type], limit) } + scope :unread_types, + ->(user, types, limit = 30) { + where(user_id: user.id, read: false, notification_type: types) + .visible + .includes(:topic) + .limit(limit) + } + scope :prioritized, + ->(deprioritized_types = []) { + scope = order("notifications.high_priority AND NOT notifications.read DESC") + if deprioritized_types.present? + scope = + scope.order( + DB.sql_fragment( + "NOT notifications.read AND notifications.notification_type NOT IN (?) DESC", + deprioritized_types, + ), + ) + else + scope = scope.order("NOT notifications.read DESC") + end + scope.order("notifications.created_at DESC") + } + scope :for_user_menu, + ->(user_id, limit: 30) { + where(user_id: user_id).visible.prioritized.includes(:topic).limit(limit) + } attr_accessor :skip_send_email - after_commit :refresh_notification_count, on: [:create, :update, :destroy] + after_commit :refresh_notification_count, on: %i[create update destroy] after_commit :send_email, on: :create - after_commit(on: :create) do - DiscourseEvent.trigger(:notification_created, self) - end + after_commit(on: :create) { DiscourseEvent.trigger(:notification_created, self) } before_create do # if we have manually set the notification to high_priority on create then # make sure that is respected - self.high_priority = self.high_priority || Notification.high_priority_types.include?(self.notification_type) + self.high_priority = + self.high_priority || Notification.high_priority_types.include?(self.notification_type) end def self.consolidate_or_create!(notification_params) @@ -103,54 +113,53 @@ class Notification < ActiveRecord::Base end def self.types - @types ||= Enum.new(mentioned: 1, - replied: 2, - quoted: 3, - edited: 4, - liked: 5, - private_message: 6, - invited_to_private_message: 7, - invitee_accepted: 8, - posted: 9, - moved_post: 10, - linked: 11, - granted_badge: 12, - invited_to_topic: 13, - custom: 14, - group_mentioned: 15, - group_message_summary: 16, - watching_first_post: 17, - topic_reminder: 18, - liked_consolidated: 19, - post_approved: 20, - code_review_commit_approved: 21, - membership_request_accepted: 22, - membership_request_consolidated: 23, - bookmark_reminder: 24, - reaction: 25, - votes_released: 26, - event_reminder: 27, - event_invitation: 28, - chat_mention: 29, - chat_message: 30, - chat_invitation: 31, - chat_group_mention: 32, # March 2022 - This is obsolete, as all chat_mentions use `chat_mention` type - chat_quoted: 33, - assigned: 34, - question_answer_user_commented: 35, # Used by https://github.com/discourse/discourse-question-answer - watching_category_or_tag: 36, - new_features: 37, - following: 800, # Used by https://github.com/discourse/discourse-follow - following_created_topic: 801, # Used by https://github.com/discourse/discourse-follow - following_replied: 802, # Used by https://github.com/discourse/discourse-follow - ) + @types ||= + Enum.new( + mentioned: 1, + replied: 2, + quoted: 3, + edited: 4, + liked: 5, + private_message: 6, + invited_to_private_message: 7, + invitee_accepted: 8, + posted: 9, + moved_post: 10, + linked: 11, + granted_badge: 12, + invited_to_topic: 13, + custom: 14, + group_mentioned: 15, + group_message_summary: 16, + watching_first_post: 17, + topic_reminder: 18, + liked_consolidated: 19, + post_approved: 20, + code_review_commit_approved: 21, + membership_request_accepted: 22, + membership_request_consolidated: 23, + bookmark_reminder: 24, + reaction: 25, + votes_released: 26, + event_reminder: 27, + event_invitation: 28, + chat_mention: 29, + chat_message: 30, + chat_invitation: 31, + chat_group_mention: 32, # March 2022 - This is obsolete, as all chat_mentions use `chat_mention` type + chat_quoted: 33, + assigned: 34, + question_answer_user_commented: 35, # Used by https://github.com/discourse/discourse-question-answer + watching_category_or_tag: 36, + new_features: 37, + following: 800, # Used by https://github.com/discourse/discourse-follow + following_created_topic: 801, # Used by https://github.com/discourse/discourse-follow + following_replied: 802, # Used by https://github.com/discourse/discourse-follow + ) end def self.high_priority_types - @high_priority_types ||= [ - types[:private_message], - types[:bookmark_reminder] - ] + @high_priority_types ||= [types[:private_message], types[:bookmark_reminder]] end def self.normal_priority_types @@ -158,24 +167,16 @@ class Notification < ActiveRecord::Base end def self.mark_posts_read(user, topic_id, post_numbers) - Notification - .where( - user_id: user.id, - topic_id: topic_id, - post_number: post_numbers, - read: false - ) - .update_all(read: true) + Notification.where( + user_id: user.id, + topic_id: topic_id, + post_number: post_numbers, + read: false, + ).update_all(read: true) end def self.read(user, notification_ids) - Notification - .where( - id: notification_ids, - user_id: user.id, - read: false - ) - .update_all(read: true) + Notification.where(id: notification_ids, user_id: user.id, read: false).update_all(read: true) end def self.read_types(user, types = nil) @@ -185,15 +186,19 @@ class Notification < ActiveRecord::Base end def self.interesting_after(min_date) - result = where("created_at > ?", min_date) - .includes(:topic) - .visible - .unread - .limit(20) - .order("CASE WHEN notification_type = #{Notification.types[:replied]} THEN 1 + result = + where("created_at > ?", min_date) + .includes(:topic) + .visible + .unread + .limit(20) + .order( + "CASE WHEN notification_type = #{Notification.types[:replied]} THEN 1 WHEN notification_type = #{Notification.types[:mentioned]} THEN 2 ELSE 3 - END, created_at DESC").to_a + END, created_at DESC", + ) + .to_a # Remove any duplicates by type and topic if result.present? @@ -222,14 +227,15 @@ class Notification < ActiveRecord::Base # Be wary of calling this frequently. O(n) JSON parsing can suck. def data_hash - @data_hash ||= begin - return {} if data.blank? + @data_hash ||= + begin + return {} if data.blank? - parsed = JSON.parse(data) - return {} if parsed.blank? + parsed = JSON.parse(data) + return {} if parsed.blank? - parsed.with_indifferent_access - end + parsed.with_indifferent_access + end end def url @@ -245,26 +251,27 @@ class Notification < ActiveRecord::Base [ Notification.types[:liked], Notification.types[:liked_consolidated], - Notification.types[:reaction] + Notification.types[:reaction], ] end def self.prioritized_list(user, count: 30, types: []) return [] if !user&.user_option - notifications = user.notifications - .includes(:topic) - .visible - .prioritized(types.present? ? [] : like_types) - .limit(count) + notifications = + user + .notifications + .includes(:topic) + .visible + .prioritized(types.present? ? [] : like_types) + .limit(count) if types.present? notifications = notifications.where(notification_type: types) - elsif user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never] + elsif user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:never] like_types.each do |notification_type| - notifications = notifications.where( - 'notification_type <> ?', notification_type - ) + notifications = notifications.where("notification_type <> ?", notification_type) end end notifications.to_a @@ -275,20 +282,16 @@ class Notification < ActiveRecord::Base return unless user && user.user_option count ||= 10 - notifications = user.notifications - .visible - .recent(count) - .includes(:topic) + notifications = user.notifications.visible.recent(count).includes(:topic) notifications = notifications.where(notification_type: types) if types.present? - if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never] + if user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:never] [ Notification.types[:liked], - Notification.types[:liked_consolidated] + Notification.types[:liked_consolidated], ].each do |notification_type| - notifications = notifications.where( - 'notification_type <> ?', notification_type - ) + notifications = notifications.where("notification_type <> ?", notification_type) end end @@ -313,27 +316,30 @@ class Notification < ActiveRecord::Base ids = builder.query_single if ids.length > 0 - notifications += user - .notifications - .order('notifications.created_at DESC') - .where(id: ids) - .joins(:topic) - .limit(count) + notifications += + user + .notifications + .order("notifications.created_at DESC") + .where(id: ids) + .joins(:topic) + .limit(count) end - notifications.uniq(&:id).sort do |x, y| - if x.unread_high_priority? && !y.unread_high_priority? - -1 - elsif y.unread_high_priority? && !x.unread_high_priority? - 1 - else - y.created_at <=> x.created_at + notifications + .uniq(&:id) + .sort do |x, y| + if x.unread_high_priority? && !y.unread_high_priority? + -1 + elsif y.unread_high_priority? && !x.unread_high_priority? + 1 + else + y.created_at <=> x.created_at + end end - end.take(count) + .take(count) else [] end - end def unread_high_priority? @@ -347,19 +353,18 @@ class Notification < ActiveRecord::Base protected def refresh_notification_count - if user_id - User.find_by(id: user_id)&.publish_notifications_state - end + User.find_by(id: user_id)&.publish_notifications_state if user_id end def send_email return if skip_send_email - user.do_not_disturb? ? - ShelvedNotification.create(notification_id: self.id) : + if user.do_not_disturb? + ShelvedNotification.create(notification_id: self.id) + else NotificationEmailer.process_notification(self) + end end - end # == Schema Information diff --git a/app/models/notification_level_when_replying_site_setting.rb b/app/models/notification_level_when_replying_site_setting.rb index c71927815fe..e12b97b6351 100644 --- a/app/models/notification_level_when_replying_site_setting.rb +++ b/app/models/notification_level_when_replying_site_setting.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true class NotificationLevelWhenReplyingSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.notification_levels @@ -13,14 +11,13 @@ class NotificationLevelWhenReplyingSiteSetting < EnumSiteSetting def self.values @values ||= [ - { name: 'topic.notifications.watching.title', value: notification_levels[:watching] }, - { name: 'topic.notifications.tracking.title', value: notification_levels[:tracking] }, - { name: 'topic.notifications.regular.title', value: notification_levels[:regular] } + { name: "topic.notifications.watching.title", value: notification_levels[:watching] }, + { name: "topic.notifications.tracking.title", value: notification_levels[:tracking] }, + { name: "topic.notifications.regular.title", value: notification_levels[:regular] }, ] end def self.translate_names? true end - end diff --git a/app/models/oauth2_user_info.rb b/app/models/oauth2_user_info.rb index bf758ee9118..34a9e62f564 100644 --- a/app/models/oauth2_user_info.rb +++ b/app/models/oauth2_user_info.rb @@ -4,7 +4,11 @@ class Oauth2UserInfo < ActiveRecord::Base belongs_to :user before_save do - Discourse.deprecate("Oauth2UserInfo is deprecated. Use `ManagedAuthenticator` and `UserAssociatedAccount` instead. For more information, see https://meta.discourse.org/t/106695", drop_from: '2.9.0', output_in_test: true) + Discourse.deprecate( + "Oauth2UserInfo is deprecated. Use `ManagedAuthenticator` and `UserAssociatedAccount` instead. For more information, see https://meta.discourse.org/t/106695", + drop_from: "2.9.0", + output_in_test: true, + ) end end diff --git a/app/models/optimized_image.rb b/app/models/optimized_image.rb index 901e762328f..dbfa6d10c66 100644 --- a/app/models/optimized_image.rb +++ b/app/models/optimized_image.rb @@ -6,7 +6,7 @@ class OptimizedImage < ActiveRecord::Base # BUMP UP if optimized image algorithm changes VERSION = 2 - URL_REGEX ||= /(\/optimized\/\dX[\/\.\w]*\/([a-zA-Z0-9]+)[\.\w]*)/ + URL_REGEX ||= %r{(/optimized/\dX[/\.\w]*/([a-zA-Z0-9]+)[\.\w]*)} def self.lock(upload_id, width, height) @hostname ||= Discourse.os_hostname @@ -15,14 +15,10 @@ class OptimizedImage < ActiveRecord::Base # # we can not afford this blocking in Sidekiq cause it can lead to starvation if Sidekiq.server? - DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") do - yield - end + DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") { yield } else DistributedMutex.synchronize("optimized_image_host_#{@hostname}") do - DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") do - yield - end + DistributedMutex.synchronize("optimized_image_#{upload_id}_#{width}_#{height}") { yield } end end end @@ -32,9 +28,7 @@ class OptimizedImage < ActiveRecord::Base return if upload.try(:sha1).blank? # no extension so try to guess it - if (!upload.extension) - upload.fix_image_extension - end + upload.fix_image_extension if (!upload.extension) if !upload.extension.match?(IM_DECODERS) && upload.extension != "svg" if !opts[:raise_on_error] @@ -61,7 +55,12 @@ class OptimizedImage < ActiveRecord::Base if original_path.blank? # download is protected with a DistributedMutex - external_copy = Discourse.store.download(upload) rescue nil + external_copy = + begin + Discourse.store.download(upload) + rescue StandardError + nil + end original_path = external_copy.try(:path) end @@ -78,14 +77,13 @@ class OptimizedImage < ActiveRecord::Base # create a temp file with the same extension as the original extension = ".#{opts[:format] || upload.extension}" - if extension.length == 1 - return nil - end + return nil if extension.length == 1 temp_file = Tempfile.new(["discourse-thumbnail", extension]) temp_path = temp_file.path - target_quality = upload.target_image_quality(original_path, SiteSetting.image_preview_jpg_quality) + target_quality = + upload.target_image_quality(original_path, SiteSetting.image_preview_jpg_quality) opts = opts.merge(quality: target_quality) if target_quality if upload.extension == "svg" @@ -98,25 +96,29 @@ class OptimizedImage < ActiveRecord::Base end if resized - thumbnail = OptimizedImage.create!( - upload_id: upload.id, - sha1: Upload.generate_digest(temp_path), - extension: extension, - width: width, - height: height, - url: "", - filesize: File.size(temp_path), - version: VERSION - ) + thumbnail = + OptimizedImage.create!( + upload_id: upload.id, + sha1: Upload.generate_digest(temp_path), + extension: extension, + width: width, + height: height, + url: "", + filesize: File.size(temp_path), + version: VERSION, + ) # store the optimized image and update its url File.open(temp_path) do |file| - url = Discourse.store.store_optimized_image(file, thumbnail, nil, secure: upload.secure?) + url = + Discourse.store.store_optimized_image(file, thumbnail, nil, secure: upload.secure?) if url.present? thumbnail.url = url thumbnail.save else - Rails.logger.error("Failed to store optimized image of size #{width}x#{height} from url: #{upload.url}\nTemp image path: #{temp_path}") + Rails.logger.error( + "Failed to store optimized image of size #{width}x#{height} from url: #{upload.url}\nTemp image path: #{temp_path}", + ) end end end @@ -126,9 +128,7 @@ class OptimizedImage < ActiveRecord::Base end # make sure we remove the cached copy from external stores - if Discourse.store.external? - external_copy&.close - end + external_copy&.close if Discourse.store.external? thumbnail end @@ -142,7 +142,7 @@ class OptimizedImage < ActiveRecord::Base end def local? - !(url =~ /^(https?:)?\/\//) + !(url =~ %r{^(https?:)?//}) end def calculate_filesize @@ -162,9 +162,7 @@ class OptimizedImage < ActiveRecord::Base size = calculate_filesize write_attribute(:filesize, size) - if !new_record? - update_columns(filesize: size) - end + update_columns(filesize: size) if !new_record? size end end @@ -173,14 +171,12 @@ class OptimizedImage < ActiveRecord::Base # this matches instructions which call #to_s path = path.to_s return false if path != File.expand_path(path) - return false if path !~ /\A[\w\-\.\/]+\z/m + return false if path !~ %r{\A[\w\-\./]+\z}m true end def self.ensure_safe_paths!(*paths) - paths.each do |path| - raise Discourse::InvalidAccess unless safe_path?(path) - end + paths.each { |path| raise Discourse::InvalidAccess unless safe_path?(path) } end IM_DECODERS ||= /\A(jpe?g|png|ico|gif|webp)\z/i @@ -215,29 +211,35 @@ class OptimizedImage < ActiveRecord::Base from = prepend_decoder!(from, to, opts) to = prepend_decoder!(to, to, opts) - instructions = ['convert', "#{from}[0]"] + instructions = ["convert", "#{from}[0]"] - if opts[:colors] - instructions << "-colors" << opts[:colors].to_s - end + instructions << "-colors" << opts[:colors].to_s if opts[:colors] - if opts[:quality] - instructions << "-quality" << opts[:quality].to_s - end + instructions << "-quality" << opts[:quality].to_s if opts[:quality] # NOTE: ORDER is important! - instructions.concat(%W{ - -auto-orient - -gravity center - -background transparent - -#{thumbnail_or_resize} #{dimensions}^ - -extent #{dimensions} - -interpolate catrom - -unsharp 2x0.5+0.7+0 - -interlace none - -profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')} - #{to} - }) + instructions.concat( + %W[ + -auto-orient + -gravity + center + -background + transparent + -#{thumbnail_or_resize} + #{dimensions}^ + -extent + #{dimensions} + -interpolate + catrom + -unsharp + 2x0.5+0.7+0 + -interlace + none + -profile + #{File.join(Rails.root, "vendor", "data", "RT_sRGB.icm")} + #{to} + ], + ) end def self.crop_instructions(from, to, dimensions, opts = {}) @@ -250,18 +252,23 @@ class OptimizedImage < ActiveRecord::Base convert #{from}[0] -auto-orient - -gravity north - -background transparent - -#{thumbnail_or_resize} #{dimensions}^ - -crop #{dimensions}+0+0 - -unsharp 2x0.5+0.7+0 - -interlace none - -profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')} + -gravity + north + -background + transparent + -#{thumbnail_or_resize} + #{dimensions}^ + -crop + #{dimensions}+0+0 + -unsharp + 2x0.5+0.7+0 + -interlace + none + -profile + #{File.join(Rails.root, "vendor", "data", "RT_sRGB.icm")} } - if opts[:quality] - instructions << "-quality" << opts[:quality].to_s - end + instructions << "-quality" << opts[:quality].to_s if opts[:quality] instructions << to end @@ -276,11 +283,16 @@ class OptimizedImage < ActiveRecord::Base convert #{from}[0] -auto-orient - -gravity center - -background transparent - -interlace none - -resize #{dimensions} - -profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')} + -gravity + center + -background + transparent + -interlace + none + -resize + #{dimensions} + -profile + #{File.join(Rails.root, "vendor", "data", "RT_sRGB.icm")} #{to} } end @@ -308,7 +320,13 @@ class OptimizedImage < ActiveRecord::Base MAX_CONVERT_SECONDS = 20 def self.convert_with(instructions, to, opts = {}) - Discourse::Utils.execute_command("nice", "-n", "10", *instructions, timeout: MAX_CONVERT_SECONDS) + Discourse::Utils.execute_command( + "nice", + "-n", + "10", + *instructions, + timeout: MAX_CONVERT_SECONDS, + ) allow_pngquant = to.downcase.ends_with?(".png") && File.size(to) < MAX_PNGQUANT_SIZE FileHelper.optimize_image!(to, allow_pngquant: allow_pngquant) diff --git a/app/models/permalink.rb b/app/models/permalink.rb index 11ea908d9f6..028313fcae2 100644 --- a/app/models/permalink.rb +++ b/app/models/permalink.rb @@ -15,15 +15,11 @@ class Permalink < ActiveRecord::Base def initialize(source) @source = source - if source.present? - @rules = source.split("|").map do |rule| - parse_rule(rule) - end.compact - end + @rules = source.split("|").map { |rule| parse_rule(rule) }.compact if source.present? end def parse_rule(rule) - return unless rule =~ /\/.*\// + return unless rule =~ %r{/.*/} escaping = false regex = +"" @@ -41,32 +37,27 @@ class Permalink < ActiveRecord::Base end end - if regex.length > 1 - [Regexp.new(regex[1..-1]), sub[1..-1] || ""] - end - + [Regexp.new(regex[1..-1]), sub[1..-1] || ""] if regex.length > 1 end def normalize(url) return url unless @rules - @rules.each do |(regex, sub)| - url = url.sub(regex, sub) - end + @rules.each { |(regex, sub)| url = url.sub(regex, sub) } url end - end def self.normalize_url(url) if url url = url.strip - url = url[1..-1] if url[0, 1] == '/' + url = url[1..-1] if url[0, 1] == "/" end normalizations = SiteSetting.permalink_normalizations - @normalizer = Normalizer.new(normalizations) unless @normalizer && @normalizer.source == normalizations + @normalizer = Normalizer.new(normalizations) unless @normalizer && + @normalizer.source == normalizations @normalizer.normalize(url) end @@ -88,11 +79,10 @@ class Permalink < ActiveRecord::Base end def self.filter_by(url = nil) - permalinks = Permalink - .includes(:topic, :post, :category, :tag) - .order('permalinks.created_at desc') + permalinks = + Permalink.includes(:topic, :post, :category, :tag).order("permalinks.created_at desc") - permalinks.where!('url ILIKE :url OR external_url ILIKE :url', url: "%#{url}%") if url.present? + permalinks.where!("url ILIKE :url OR external_url ILIKE :url", url: "%#{url}%") if url.present? permalinks.limit!(100) permalinks.to_a end diff --git a/app/models/plugin_store.rb b/app/models/plugin_store.rb index ed8dd6dc000..15504cc857b 100644 --- a/app/models/plugin_store.rb +++ b/app/models/plugin_store.rb @@ -31,7 +31,7 @@ class PluginStore end def self.get_all(plugin_name, keys) - rows = PluginStoreRow.where('plugin_name = ? AND key IN (?)', plugin_name, keys).to_a + rows = PluginStoreRow.where("plugin_name = ? AND key IN (?)", plugin_name, keys).to_a Hash[rows.map { |row| [row.key, cast_value(row.type_name, row.value)] }] end @@ -72,10 +72,14 @@ class PluginStore def self.cast_value(type, value) case type - when "Integer", "Fixnum" then value.to_i - when "TrueClass", "FalseClass" then value == "true" - when "JSON" then map_json(::JSON.parse(value)) - else value + when "Integer", "Fixnum" + value.to_i + when "TrueClass", "FalseClass" + value == "true" + when "JSON" + map_json(::JSON.parse(value)) + else + value end end end diff --git a/app/models/post.rb b/app/models/post.rb index 7d1f5514145..7aa9155264a 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'archetype' -require 'digest/sha1' +require "archetype" +require "digest/sha1" class Post < ActiveRecord::Base include RateLimiter::OnCreateRecord @@ -12,7 +12,7 @@ class Post < ActiveRecord::Base self.ignored_columns = [ "avg_time", # TODO(2021-01-04): remove - "image_url" # TODO(2021-06-01): remove + "image_url", # TODO(2021-06-01): remove ] cattr_accessor :plugin_permitted_create_params, :plugin_permitted_update_params @@ -54,7 +54,7 @@ class Post < ActiveRecord::Base has_many :post_details has_many :post_revisions - has_many :revisions, -> { order(:number) }, foreign_key: :post_id, class_name: 'PostRevision' + has_many :revisions, -> { order(:number) }, foreign_key: :post_id, class_name: "PostRevision" has_many :user_actions, foreign_key: :target_post_id @@ -67,11 +67,17 @@ class Post < ActiveRecord::Base after_commit :index_search # We can pass several creating options to a post via attributes - attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes, :cooking_options, :skip_unique_check, :skip_validation + attr_accessor :image_sizes, + :quoted_post_numbers, + :no_bump, + :invalidate_oneboxes, + :cooking_options, + :skip_unique_check, + :skip_validation - MISSING_UPLOADS ||= "missing uploads" + MISSING_UPLOADS ||= "missing uploads" MISSING_UPLOADS_IGNORED ||= "missing uploads ignored" - NOTICE ||= "notice" + NOTICE ||= "notice" SHORT_POST_CHARS ||= 1200 @@ -80,46 +86,56 @@ class Post < ActiveRecord::Base register_custom_field_type(NOTICE, :json) - scope :private_posts_for_user, ->(user) do - where( - "topics.id IN (#{Topic::PRIVATE_MESSAGES_SQL_USER}) + scope :private_posts_for_user, + ->(user) { + where( + "topics.id IN (#{Topic::PRIVATE_MESSAGES_SQL_USER}) OR topics.id IN (#{Topic::PRIVATE_MESSAGES_SQL_GROUP})", - user_id: user.id - ) - end + user_id: user.id, + ) + } - scope :by_newest, -> { order('created_at DESC, id DESC') } - scope :by_post_number, -> { order('post_number ASC') } + scope :by_newest, -> { order("created_at DESC, id DESC") } + scope :by_post_number, -> { order("post_number ASC") } scope :with_user, -> { includes(:user) } - scope :created_since, -> (time_ago) { where('posts.created_at > ?', time_ago) } - scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) } - scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) } - scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) } - scope :visible, -> { joins(:topic).where('topics.visible = true').where(hidden: false) } - scope :secured, -> (guardian) { where('posts.post_type IN (?)', Topic.visible_post_types(guardian&.user)) } + scope :created_since, ->(time_ago) { where("posts.created_at > ?", time_ago) } + scope :public_posts, + -> { joins(:topic).where("topics.archetype <> ?", Archetype.private_message) } + scope :private_posts, + -> { joins(:topic).where("topics.archetype = ?", Archetype.private_message) } + scope :with_topic_subtype, ->(subtype) { joins(:topic).where("topics.subtype = ?", subtype) } + scope :visible, -> { joins(:topic).where("topics.visible = true").where(hidden: false) } + scope :secured, + ->(guardian) { where("posts.post_type IN (?)", Topic.visible_post_types(guardian&.user)) } - scope :for_mailing_list, ->(user, since) { - q = created_since(since) - .joins("INNER JOIN (#{Topic.for_digest(user, Time.at(0)).select(:id).to_sql}) AS digest_topics ON digest_topics.id = posts.topic_id") # we want all topics with new content, regardless when they were created - .order('posts.created_at ASC') + scope :for_mailing_list, + ->(user, since) { + q = + created_since(since).joins( + "INNER JOIN (#{Topic.for_digest(user, Time.at(0)).select(:id).to_sql}) AS digest_topics ON digest_topics.id = posts.topic_id", + ) # we want all topics with new content, regardless when they were created + .order("posts.created_at ASC") - q = q.where.not(post_type: Post.types[:whisper]) unless user.staff? - q - } + q = q.where.not(post_type: Post.types[:whisper]) unless user.staff? + q + } - scope :raw_match, -> (pattern, type = 'string') { - type = type&.downcase + scope :raw_match, + ->(pattern, type = "string") { + type = type&.downcase - case type - when 'string' - where('raw ILIKE ?', "%#{pattern}%") - when 'regex' - where('raw ~* ?', "(?n)#{pattern}") - end - } + case type + when "string" + where("raw ILIKE ?", "%#{pattern}%") + when "regex" + where("raw ~* ?", "(?n)#{pattern}") + end + } - scope :have_uploads, -> { - where(" + scope :have_uploads, + -> { + where( + " ( posts.cooked LIKE '% 1 - RateLimiter.new(user, "first-day-replies-per-day", SiteSetting.max_replies_in_first_day, 1.day.to_i) + RateLimiter.new( + user, + "first-day-replies-per-day", + SiteSetting.max_replies_in_first_day, + 1.day.to_i, + ) end end @@ -207,7 +225,7 @@ class Post < ActiveRecord::Base user_id: user_id, last_editor_id: last_editor_id, type: type, - version: version + version: version, }.merge(opts) publish_message!("/topic/#{topic_id}", message) @@ -220,14 +238,10 @@ class Post < ActiveRecord::Base if Topic.visible_post_types.include?(post_type) opts.merge!(topic.secure_audience_publish_messages) else - opts[:user_ids] = User.human_users - .where("admin OR moderator OR id = ?", user_id) - .pluck(:id) + opts[:user_ids] = User.human_users.where("admin OR moderator OR id = ?", user_id).pluck(:id) end - if opts[:user_ids] != [] && opts[:group_ids] != [] - MessageBus.publish(channel, message, opts) - end + MessageBus.publish(channel, message, opts) if opts[:user_ids] != [] && opts[:group_ids] != [] end def trash!(trashed_by = nil) @@ -241,9 +255,7 @@ class Post < ActiveRecord::Base recover_public_post_actions TopicLink.extract_from(self) QuotedPost.extract_from(self) - if topic && topic.category_id && topic.category - topic.category.update_latest - end + topic.category.update_latest if topic && topic.category_id && topic.category end # The key we use in redis to ensure unique posts @@ -268,7 +280,7 @@ class Post < ActiveRecord::Base end def self.allowed_image_classes - @allowed_image_classes ||= ['avatar', 'favicon', 'thumbnail', 'emoji', 'ytp-thumbnail-image'] + @allowed_image_classes ||= %w[avatar favicon thumbnail emoji ytp-thumbnail-image] end def post_analyzer @@ -276,17 +288,15 @@ class Post < ActiveRecord::Base @post_analyzers[raw_hash] ||= PostAnalyzer.new(raw, topic_id) end - %w{raw_mentions + %w[ + raw_mentions linked_hosts embedded_media_count attachment_count link_count raw_links - has_oneboxes?}.each do |attr| - define_method(attr) do - post_analyzer.public_send(attr) - end - end + has_oneboxes? + ].each { |attr| define_method(attr) { post_analyzer.public_send(attr) } } def add_nofollow? return false if user&.staff? @@ -318,11 +328,16 @@ class Post < ActiveRecord::Base each_upload_url do |url| uri = URI.parse(url) if FileHelper.is_supported_media?(File.basename(uri.path)) - raw = raw.sub( - url, Rails.application.routes.url_for( - controller: "uploads", action: "show_secure", path: uri.path[1..-1], host: Discourse.current_hostname + raw = + raw.sub( + url, + Rails.application.routes.url_for( + controller: "uploads", + action: "show_secure", + path: uri.path[1..-1], + host: Discourse.current_hostname, + ), ) - ) end end end @@ -358,43 +373,46 @@ class Post < ActiveRecord::Base end def allowed_spam_hosts - hosts = SiteSetting - .allowed_spam_host_domains - .split('|') - .map { |h| h.strip } - .reject { |h| !h.include?('.') } + hosts = + SiteSetting + .allowed_spam_host_domains + .split("|") + .map { |h| h.strip } + .reject { |h| !h.include?(".") } hosts << GlobalSetting.hostname hosts << RailsMultisite::ConnectionManagement.current_hostname - end def total_hosts_usage hosts = linked_hosts.clone allowlisted = allowed_spam_hosts - hosts.reject! do |h| - allowlisted.any? do |w| - h.end_with?(w) - end - end + hosts.reject! { |h| allowlisted.any? { |w| h.end_with?(w) } } return hosts if hosts.length == 0 - TopicLink.where(domain: hosts.keys, user_id: acting_user.id) + TopicLink + .where(domain: hosts.keys, user_id: acting_user.id) .group(:domain, :post_id) .count .each_key do |tuple| - domain = tuple[0] - hosts[domain] = (hosts[domain] || 0) + 1 - end + domain = tuple[0] + hosts[domain] = (hosts[domain] || 0) + 1 + end hosts end # Prevent new users from posting the same hosts too many times. def has_host_spam? - return false if acting_user.present? && (acting_user.staged? || acting_user.mature_staged? || acting_user.has_trust_level?(TrustLevel[1])) + if acting_user.present? && + ( + acting_user.staged? || acting_user.mature_staged? || + acting_user.has_trust_level?(TrustLevel[1]) + ) + return false + end return false if topic&.private_message? total_hosts_usage.values.any? { |count| count >= SiteSetting.newuser_spam_host_threshold } @@ -409,15 +427,15 @@ class Post < ActiveRecord::Base end def self.reverse_order - order('sort_order desc, post_number desc') + order("sort_order desc, post_number desc") end def self.summary(topic_id) topic_id = topic_id.to_i # percent rank has tons of ties - where(topic_id: topic_id) - .where([ + where(topic_id: topic_id).where( + [ "id = ANY( ( SELECT posts.id @@ -435,8 +453,9 @@ class Post < ActiveRecord::Base ) )", SiteSetting.summary_percent_filter.to_f / 100.0, - SiteSetting.summary_max_results - ]) + SiteSetting.summary_max_results, + ], + ) end def delete_post_notices @@ -445,7 +464,8 @@ class Post < ActiveRecord::Base end def recover_public_post_actions - PostAction.publics + PostAction + .publics .with_deleted .where(post_id: self.id, id: self.custom_fields["deleted_public_actions"]) .find_each do |post_action| @@ -463,10 +483,10 @@ class Post < ActiveRecord::Base # We only filter quotes when there is exactly 1 return cooked unless (quote_count == 1) - parent_raw = parent_post.raw.sub(/\[quote.+\/quote\]/m, '') + parent_raw = parent_post.raw.sub(%r{\[quote.+/quote\]}m, "") if raw[parent_raw] || (parent_raw.size < SHORT_POST_CHARS) - return cooked.sub(/\/m, '') + return cooked.sub(%r{\}m, "") end cooked @@ -478,12 +498,22 @@ class Post < ActiveRecord::Base def reply_to_post return if reply_to_post_number.blank? - @reply_to_post ||= Post.find_by("topic_id = :topic_id AND post_number = :post_number", topic_id: topic_id, post_number: reply_to_post_number) + @reply_to_post ||= + Post.find_by( + "topic_id = :topic_id AND post_number = :post_number", + topic_id: topic_id, + post_number: reply_to_post_number, + ) end def reply_notification_target return if reply_to_post_number.blank? - Post.find_by("topic_id = :topic_id AND post_number = :post_number AND user_id <> :user_id", topic_id: topic_id, post_number: reply_to_post_number, user_id: user_id).try(:user) + Post.find_by( + "topic_id = :topic_id AND post_number = :post_number AND user_id <> :user_id", + topic_id: topic_id, + post_number: reply_to_post_number, + user_id: user_id, + ).try(:user) end def self.excerpt(cooked, maxlength = nil, options = {}) @@ -497,13 +527,17 @@ class Post < ActiveRecord::Base end def excerpt_for_topic - Post.excerpt(cooked, SiteSetting.topic_excerpt_maxlength, strip_links: true, strip_images: true, post: self) + Post.excerpt( + cooked, + SiteSetting.topic_excerpt_maxlength, + strip_links: true, + strip_images: true, + post: self, + ) end def is_first_post? - post_number.blank? ? - topic.try(:highest_post_number) == 0 : - post_number == 1 + post_number.blank? ? topic.try(:highest_post_number) == 0 : post_number == 1 end def is_category_description? @@ -515,7 +549,10 @@ class Post < ActiveRecord::Base end def is_flagged? - post_actions.where(post_action_type_id: PostActionType.flag_types_without_custom.values, deleted_at: nil).count != 0 + post_actions.where( + post_action_type_id: PostActionType.flag_types_without_custom.values, + deleted_at: nil, + ).count != 0 end def reviewable_flag @@ -524,16 +561,21 @@ class Post < ActiveRecord::Base def with_secure_uploads? return false if !SiteSetting.secure_uploads? - SiteSetting.login_required? || \ + SiteSetting.login_required? || (topic.present? && (topic.private_message? || topic.category&.read_restricted)) end def hide!(post_action_type_id, reason = nil, custom_message: nil) return if hidden? - reason ||= hidden_at ? - Post.hidden_reasons[:flag_threshold_reached_again] : - Post.hidden_reasons[:flag_threshold_reached] + reason ||= + ( + if hidden_at + Post.hidden_reasons[:flag_threshold_reached_again] + else + Post.hidden_reasons[:flag_threshold_reached] + end + ) hiding_again = hidden_at.present? @@ -547,7 +589,7 @@ class Post < ActiveRecord::Base Topic.where( "id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)", - topic_id: topic_id + topic_id: topic_id, ).update_all(visible: false) UserStatCountUpdater.decrement!(self) @@ -558,24 +600,23 @@ class Post < ActiveRecord::Base options = { url: url, edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts, - flag_reason: I18n.t( - "flag_reasons.#{PostActionType.types[post_action_type_id]}", - locale: SiteSetting.default_locale, - base_path: Discourse.base_path - ) + flag_reason: + I18n.t( + "flag_reasons.#{PostActionType.types[post_action_type_id]}", + locale: SiteSetting.default_locale, + base_path: Discourse.base_path, + ), } message = custom_message - if message.nil? - message = hiding_again ? :post_hidden_again : :post_hidden - end + message = hiding_again ? :post_hidden_again : :post_hidden if message.nil? Jobs.enqueue_in( 5.seconds, :send_system_message, user_id: user.id, message_type: message.to_s, - message_options: options + message_options: options, ) end end @@ -610,9 +651,7 @@ class Post < ActiveRecord::Base page = "" - if topic_view.page > 1 - page = "?page=#{topic_view.page}" - end + page = "?page=#{topic_view.page}" if topic_view.page > 1 "#{topic.url}#{page}#post_#{post_number}" end @@ -636,11 +675,11 @@ class Post < ActiveRecord::Base ids = post_ids.map { |u| u } if ids.length > 0 urls = {} - Topic.joins(:posts).where('posts.id' => ids). - select(['posts.id as post_id', 'post_number', 'topics.slug', 'topics.title', 'topics.id']). - each do |t| - urls[t.post_id.to_i] = url(t.slug, t.id, t.post_number) - end + Topic + .joins(:posts) + .where("posts.id" => ids) + .select(["posts.id as post_id", "post_number", "topics.slug", "topics.title", "topics.id"]) + .each { |t| urls[t.post_id.to_i] = url(t.slug, t.id, t.post_number) } urls else {} @@ -652,47 +691,50 @@ class Post < ActiveRecord::Base end def self.rebake_old(limit, priority: :normal, rate_limiter: true) - - limiter = RateLimiter.new( - nil, - "global_periodical_rebake_limit", - GlobalSetting.max_old_rebakes_per_15_minutes, - 900, - global: true - ) + limiter = + RateLimiter.new( + nil, + "global_periodical_rebake_limit", + GlobalSetting.max_old_rebakes_per_15_minutes, + 900, + global: true, + ) problems = [] - Post.where('baked_version IS NULL OR baked_version < ?', BAKED_VERSION) - .order('id desc') - .limit(limit).pluck(:id).each do |id| - begin - - break if !limiter.can_perform? - - post = Post.find(id) - post.rebake!(priority: priority) - + Post + .where("baked_version IS NULL OR baked_version < ?", BAKED_VERSION) + .order("id desc") + .limit(limit) + .pluck(:id) + .each do |id| begin - limiter.performed! if rate_limiter - rescue RateLimiter::LimitExceeded - break + break if !limiter.can_perform? + + post = Post.find(id) + post.rebake!(priority: priority) + + begin + limiter.performed! if rate_limiter + rescue RateLimiter::LimitExceeded + break + end + rescue => e + problems << { post: post, ex: e } + + attempts = post.custom_fields["rebake_attempts"].to_i + + if attempts > 3 + post.update_columns(baked_version: BAKED_VERSION) + Discourse.warn_exception( + e, + message: "Can not rebake post# #{post.id} after 3 attempts, giving up", + ) + else + post.custom_fields["rebake_attempts"] = attempts + 1 + post.save_custom_fields + end end - - rescue => e - problems << { post: post, ex: e } - - attempts = post.custom_fields["rebake_attempts"].to_i - - if attempts > 3 - post.update_columns(baked_version: BAKED_VERSION) - Discourse.warn_exception(e, message: "Can not rebake post# #{post.id} after 3 attempts, giving up") - else - post.custom_fields["rebake_attempts"] = attempts + 1 - post.save_custom_fields - end - end - end problems end @@ -700,15 +742,9 @@ class Post < ActiveRecord::Base new_cooked = cook(raw, topic_id: topic_id, invalidate_oneboxes: invalidate_oneboxes) old_cooked = cooked - update_columns( - cooked: new_cooked, - baked_at: Time.zone.now, - baked_version: BAKED_VERSION - ) + update_columns(cooked: new_cooked, baked_at: Time.zone.now, baked_version: BAKED_VERSION) - if is_first_post? - topic&.update_excerpt(excerpt_for_topic) - end + topic&.update_excerpt(excerpt_for_topic) if is_first_post? if invalidate_broken_images post_hotlinked_media.download_failed.destroy_all @@ -730,31 +766,29 @@ class Post < ActiveRecord::Base def set_owner(new_user, actor, skip_revision = false) return if user_id == new_user.id - edit_reason = I18n.t('change_owner.post_revision_text', locale: SiteSetting.default_locale) + edit_reason = I18n.t("change_owner.post_revision_text", locale: SiteSetting.default_locale) revise( actor, { raw: self.raw, user_id: new_user.id, edit_reason: edit_reason }, - bypass_bump: true, skip_revision: skip_revision, skip_validations: true + bypass_bump: true, + skip_revision: skip_revision, + skip_validations: true, ) - if post_number == topic.highest_post_number - topic.update_columns(last_post_user_id: new_user.id) - end + topic.update_columns(last_post_user_id: new_user.id) if post_number == topic.highest_post_number end - before_create do - PostCreator.before_create_tasks(self) - end + before_create { PostCreator.before_create_tasks(self) } def self.estimate_posts_per_day val = Discourse.redis.get("estimated_posts_per_day") return val.to_i if val - posts_per_day = Topic.listable_topics.secured.joins(:posts).merge(Post.created_since(30.days.ago)).count / 30 + posts_per_day = + Topic.listable_topics.secured.joins(:posts).merge(Post.created_since(30.days.ago)).count / 30 Discourse.redis.setex("estimated_posts_per_day", 1.day.to_i, posts_per_day.to_s) posts_per_day - end before_save do @@ -778,13 +812,15 @@ class Post < ActiveRecord::Base temp_collector = [] # Create relationships for the quotes - raw.scan(/\[quote=\"([^"]+)"\]/).each do |quote| - args = parse_quote_into_arguments(quote) - # If the topic attribute is present, ensure it's the same topic - if !(args[:topic].present? && topic_id != args[:topic]) && args[:post] != post_number - temp_collector << args[:post] + raw + .scan(/\[quote=\"([^"]+)"\]/) + .each do |quote| + args = parse_quote_into_arguments(quote) + # If the topic attribute is present, ensure it's the same topic + if !(args[:topic].present? && topic_id != args[:topic]) && args[:post] != post_number + temp_collector << args[:post] + end end - end temp_collector.uniq! self.quoted_post_numbers = temp_collector @@ -803,7 +839,12 @@ class Post < ActiveRecord::Base end # Enqueue post processing for this post - def trigger_post_process(bypass_bump: false, priority: :normal, new_post: false, skip_pull_hotlinked_images: false) + def trigger_post_process( + bypass_bump: false, + priority: :normal, + new_post: false, + skip_pull_hotlinked_images: false + ) args = { bypass_bump: bypass_bump, cooking_options: self.cooking_options, @@ -820,30 +861,36 @@ class Post < ActiveRecord::Base DiscourseEvent.trigger(:after_trigger_post_process, self) end - def self.public_posts_count_per_day(start_date, end_date, category_id = nil, include_subcategories = false) - result = public_posts - .where('posts.created_at >= ? AND posts.created_at <= ?', start_date, end_date) - .where(post_type: Post.types[:regular]) + def self.public_posts_count_per_day( + start_date, + end_date, + category_id = nil, + include_subcategories = false + ) + result = + public_posts.where( + "posts.created_at >= ? AND posts.created_at <= ?", + start_date, + end_date, + ).where(post_type: Post.types[:regular]) if category_id if include_subcategories - result = result.where('topics.category_id IN (?)', Category.subcategory_ids(category_id)) + result = result.where("topics.category_id IN (?)", Category.subcategory_ids(category_id)) else - result = result.where('topics.category_id = ?', category_id) + result = result.where("topics.category_id = ?", category_id) end end - result - .group('date(posts.created_at)') - .order('date(posts.created_at)') - .count + result.group("date(posts.created_at)").order("date(posts.created_at)").count end def self.private_messages_count_per_day(start_date, end_date, topic_subtype) - private_posts.with_topic_subtype(topic_subtype) - .where('posts.created_at >= ? AND posts.created_at <= ?', start_date, end_date) - .group('date(posts.created_at)') - .order('date(posts.created_at)') + private_posts + .with_topic_subtype(topic_subtype) + .where("posts.created_at >= ? AND posts.created_at <= ?", start_date, end_date) + .group("date(posts.created_at)") + .order("date(posts.created_at)") .count end @@ -962,28 +1009,26 @@ class Post < ActiveRecord::Base upload_ids << upload.id if upload.present? end - upload_references = upload_ids.map do |upload_id| - { - target_id: self.id, - target_type: self.class.name, - upload_id: upload_id, - created_at: Time.zone.now, - updated_at: Time.zone.now - } - end + upload_references = + upload_ids.map do |upload_id| + { + target_id: self.id, + target_type: self.class.name, + upload_id: upload_id, + created_at: Time.zone.now, + updated_at: Time.zone.now, + } + end UploadReference.transaction do UploadReference.where(target: self).delete_all UploadReference.insert_all(upload_references) if upload_references.size > 0 if SiteSetting.secure_uploads? - Upload.where( - id: upload_ids, access_control_post_id: nil - ).where( - 'id NOT IN (SELECT upload_id FROM custom_emojis)' - ).update_all( - access_control_post_id: self.id - ) + Upload + .where(id: upload_ids, access_control_post_id: nil) + .where("id NOT IN (SELECT upload_id FROM custom_emojis)") + .update_all(access_control_post_id: self.id) end end end @@ -997,25 +1042,30 @@ class Post < ActiveRecord::Base def each_upload_url(fragments: nil, include_local_upload: true) current_db = RailsMultisite::ConnectionManagement.current_db upload_patterns = [ - /\/uploads\/#{current_db}\//, - /\/original\//, - /\/optimized\//, - /\/uploads\/short-url\/[a-zA-Z0-9]+(\.[a-z0-9]+)?/ + %r{/uploads/#{current_db}/}, + %r{/original/}, + %r{/optimized/}, + %r{/uploads/short-url/[a-zA-Z0-9]+(\.[a-z0-9]+)?}, ] - fragments ||= Nokogiri::HTML5::fragment(self.cooked) + fragments ||= Nokogiri::HTML5.fragment(self.cooked) selectors = fragments.css("a/@href", "img/@src", "source/@src", "track/@src", "video/@poster") - links = selectors.map do |media| - src = media.value - next if src.blank? + links = + selectors + .map do |media| + src = media.value + next if src.blank? - if src.end_with?("/images/transparent.png") && (parent = media.parent)["data-orig-src"].present? - parent["data-orig-src"] - else - src - end - end.compact.uniq + if src.end_with?("/images/transparent.png") && + (parent = media.parent)["data-orig-src"].present? + parent["data-orig-src"] + else + src + end + end + .compact + .uniq links.each do |src| src = src.split("?")[0] @@ -1034,13 +1084,19 @@ class Post < ActiveRecord::Base next if Rails.configuration.multisite && src.exclude?(current_db) src = "#{SiteSetting.force_https ? "https" : "http"}:#{src}" if src.start_with?("//") - next unless Discourse.store.has_been_uploaded?(src) || Upload.secure_uploads_url?(src) || (include_local_upload && src =~ /\A\/[^\/]/i) - - path = begin - URI(UrlHelper.unencode(GlobalSetting.cdn_url ? src.sub(GlobalSetting.cdn_url, "") : src))&.path - rescue URI::Error + unless Discourse.store.has_been_uploaded?(src) || Upload.secure_uploads_url?(src) || + (include_local_upload && src =~ %r{\A/[^/]}i) + next end + path = + begin + URI( + UrlHelper.unencode(GlobalSetting.cdn_url ? src.sub(GlobalSetting.cdn_url, "") : src), + )&.path + rescue URI::Error + end + next if path.blank? sha1 = @@ -1061,20 +1117,24 @@ class Post < ActiveRecord::Base DistributedMutex.synchronize("find_missing_uploads", validity: 30.minutes) do PostCustomField.where(name: Post::MISSING_UPLOADS).delete_all - query = Post - .have_uploads - .joins(:topic) - .joins("LEFT JOIN post_custom_fields ON posts.id = post_custom_fields.post_id AND post_custom_fields.name = '#{Post::MISSING_UPLOADS_IGNORED}'") - .where("post_custom_fields.id IS NULL") - .select(:id, :cooked) + query = + Post + .have_uploads + .joins(:topic) + .joins( + "LEFT JOIN post_custom_fields ON posts.id = post_custom_fields.post_id AND post_custom_fields.name = '#{Post::MISSING_UPLOADS_IGNORED}'", + ) + .where("post_custom_fields.id IS NULL") + .select(:id, :cooked) query.find_in_batches do |posts| ids = posts.pluck(:id) - sha1s = Upload - .joins(:upload_references) - .where(upload_references: { target_type: "Post" }) - .where("upload_references.target_id BETWEEN ? AND ?", ids.min, ids.max) - .pluck(:sha1) + sha1s = + Upload + .joins(:upload_references) + .where(upload_references: { target_type: "Post" }) + .where("upload_references.target_id BETWEEN ? AND ?", ids.min, ids.max) + .pluck(:sha1) posts.each do |post| post.each_upload_url do |src, path, sha1| @@ -1099,14 +1159,19 @@ class Post < ActiveRecord::Base end end - missing_post_uploads = missing_post_uploads.reject do |post_id, uploads| - if uploads.present? - PostCustomField.create!(post_id: post_id, name: Post::MISSING_UPLOADS, value: uploads.to_json) - count += uploads.count - end + missing_post_uploads = + missing_post_uploads.reject do |post_id, uploads| + if uploads.present? + PostCustomField.create!( + post_id: post_id, + name: Post::MISSING_UPLOADS, + value: uploads.to_json, + ) + count += uploads.count + end - uploads.empty? - end + uploads.empty? + end end { uploads: missing_uploads, post_uploads: missing_post_uploads, count: count } @@ -1123,8 +1188,11 @@ class Post < ActiveRecord::Base def cannot_permanently_delete_reason(user) if self.deleted_by_id == user&.id && self.deleted_at >= Post::PERMANENT_DELETE_TIMER.ago - time_left = RateLimiter.time_left(Post::PERMANENT_DELETE_TIMER.to_i - Time.zone.now.to_i + self.deleted_at.to_i) - I18n.t('post.cannot_permanently_delete.wait_or_different_admin', time_left: time_left) + time_left = + RateLimiter.time_left( + Post::PERMANENT_DELETE_TIMER.to_i - Time.zone.now.to_i + self.deleted_at.to_i, + ) + I18n.t("post.cannot_permanently_delete.wait_or_different_admin", time_left: time_left) end end @@ -1137,9 +1205,7 @@ class Post < ActiveRecord::Base def parse_quote_into_arguments(quote) return {} unless quote.present? args = HashWithIndifferentAccess.new - quote.first.scan(/([a-z]+)\:(\d+)/).each do |arg| - args[arg[0]] = arg[1].to_i - end + quote.first.scan(/([a-z]+)\:(\d+)/).each { |arg| args[arg[0]] = arg[1].to_i } args end @@ -1154,7 +1220,7 @@ class Post < ActiveRecord::Base post_reply = post.post_replies.new(reply_post_id: id) if post_reply.save if Topic.visible_post_types.include?(self.post_type) - Post.where(id: post.id).update_all ['reply_count = reply_count + 1'] + Post.where(id: post.id).update_all ["reply_count = reply_count + 1"] end end end diff --git a/app/models/post_action.rb b/app/models/post_action.rb index e2fa99e45ff..a02c8908cf6 100644 --- a/app/models/post_action.rb +++ b/app/models/post_action.rb @@ -7,8 +7,8 @@ class PostAction < ActiveRecord::Base belongs_to :post belongs_to :user belongs_to :post_action_type - belongs_to :related_post, class_name: 'Post' - belongs_to :target_user, class_name: 'User' + belongs_to :related_post, class_name: "Post" + belongs_to :target_user, class_name: "User" rate_limit :post_action_rate_limiter @@ -55,9 +55,9 @@ class PostAction < ActiveRecord::Base ORDER BY p.topic_id, p.post_number SQL - builder.query(user_id: user.id, post_action_type_id: post_action_type_id, topic_ids: topic_ids).each do |row| - (map[row.topic_id] ||= []) << row.post_number - end + builder + .query(user_id: user.id, post_action_type_id: post_action_type_id, topic_ids: topic_ids) + .each { |row| (map[row.topic_id] ||= []) << row.post_number } map end @@ -65,18 +65,24 @@ class PostAction < ActiveRecord::Base def self.count_per_day_for_type(post_action_type, opts = nil) opts ||= {} result = unscoped.where(post_action_type_id: post_action_type) - result = result.where('post_actions.created_at >= ?', opts[:start_date] || (opts[:since_days_ago] || 30).days.ago) - result = result.where('post_actions.created_at <= ?', opts[:end_date]) if opts[:end_date] + result = + result.where( + "post_actions.created_at >= ?", + opts[:start_date] || (opts[:since_days_ago] || 30).days.ago, + ) + result = result.where("post_actions.created_at <= ?", opts[:end_date]) if opts[:end_date] if opts[:category_id] if opts[:include_subcategories] - result = result.joins(post: :topic).where('topics.category_id IN (?)', Category.subcategory_ids(opts[:category_id])) + result = + result.joins(post: :topic).where( + "topics.category_id IN (?)", + Category.subcategory_ids(opts[:category_id]), + ) else - result = result.joins(post: :topic).where('topics.category_id = ?', opts[:category_id]) + result = result.joins(post: :topic).where("topics.category_id = ?", opts[:category_id]) end end - result.group('date(post_actions.created_at)') - .order('date(post_actions.created_at)') - .count + result.group("date(post_actions.created_at)").order("date(post_actions.created_at)").count end def add_moderator_post_if_needed(moderator, disposition, delete_post = false) @@ -95,7 +101,13 @@ class PostAction < ActiveRecord::Base end def staff_already_replied?(topic) - topic.posts.where("user_id IN (SELECT id FROM users WHERE moderator OR admin) OR (post_type != :regular_post_type)", regular_post_type: Post.types[:regular]).exists? + topic + .posts + .where( + "user_id IN (SELECT id FROM users WHERE moderator OR admin) OR (post_type != :regular_post_type)", + regular_post_type: Post.types[:regular], + ) + .exists? end def self.limit_action!(user, post, post_action_type_id) @@ -106,21 +118,17 @@ class PostAction < ActiveRecord::Base Discourse.deprecate( "PostAction.act is deprecated. Use `PostActionCreator` instead.", output_in_test: true, - drop_from: '2.9.0', + drop_from: "2.9.0", ) - result = PostActionCreator.new( - created_by, - post, - post_action_type_id, - message: opts[:message] - ).perform + result = + PostActionCreator.new(created_by, post, post_action_type_id, message: opts[:message]).perform result.success? ? result.post_action : nil end def self.copy(original_post, target_post) - cols_to_copy = (column_names - %w{id post_id}).join(', ') + cols_to_copy = (column_names - %w[id post_id]).join(", ") DB.exec <<~SQL INSERT INTO post_actions(post_id, #{cols_to_copy}) @@ -136,7 +144,7 @@ class PostAction < ActiveRecord::Base Discourse.deprecate( "PostAction.remove_act is deprecated. Use `PostActionDestroyer` instead.", output_in_test: true, - drop_from: '2.9.0', + drop_from: "2.9.0", ) PostActionDestroyer.new(user, post, post_action_type_id).perform @@ -160,7 +168,7 @@ class PostAction < ActiveRecord::Base def is_private_message? post_action_type_id == PostActionType.types[:notify_user] || - post_action_type_id == PostActionType.types[:notify_moderators] + post_action_type_id == PostActionType.types[:notify_moderators] end # A custom rate limiter for this model @@ -169,12 +177,13 @@ class PostAction < ActiveRecord::Base return @rate_limiter if @rate_limiter.present? - %w(like flag).each do |type| + %w[like flag].each do |type| if public_send("is_#{type}?") limit = SiteSetting.get("max_#{type}s_per_day") if (is_flag? || is_like?) && user && user.trust_level >= 2 - multiplier = SiteSetting.get("tl#{user.trust_level}_additional_#{type}s_per_day_multiplier").to_f + multiplier = + SiteSetting.get("tl#{user.trust_level}_additional_#{type}s_per_day_multiplier").to_f multiplier = 1.0 if multiplier < 1.0 limit = (limit * multiplier).to_i @@ -189,13 +198,15 @@ class PostAction < ActiveRecord::Base def ensure_unique_actions post_action_type_ids = is_flag? ? PostActionType.notify_flag_types.values : post_action_type_id - acted = PostAction.where(user_id: user_id) - .where(post_id: post_id) - .where(post_action_type_id: post_action_type_ids) - .where(deleted_at: nil) - .where(disagreed_at: nil) - .where(targets_topic: targets_topic) - .exists? + acted = + PostAction + .where(user_id: user_id) + .where(post_id: post_id) + .where(post_action_type_id: post_action_type_ids) + .where(deleted_at: nil) + .where(disagreed_at: nil) + .where(targets_topic: targets_topic) + .exists? errors.add(:post_action_type_id) if acted end @@ -207,18 +218,24 @@ class PostAction < ActiveRecord::Base def update_counters # Update denormalized counts column = "#{post_action_type_key}_count" - count = PostAction.where(post_id: post_id) - .where(post_action_type_id: post_action_type_id) - .count + count = PostAction.where(post_id: post_id).where(post_action_type_id: post_action_type_id).count # We probably want to refactor this method to something cleaner. case post_action_type_key when :like # 'like_score' is weighted higher for staff accounts - score = PostAction.joins(:user) - .where(post_id: post_id) - .sum("CASE WHEN users.moderator OR users.admin THEN #{SiteSetting.staff_like_weight} ELSE 1 END") - Post.where(id: post_id).update_all ["like_count = :count, like_score = :score", count: count, score: score] + score = + PostAction + .joins(:user) + .where(post_id: post_id) + .sum( + "CASE WHEN users.moderator OR users.admin THEN #{SiteSetting.staff_like_weight} ELSE 1 END", + ) + Post.where(id: post_id).update_all [ + "like_count = :count, like_score = :score", + count: count, + score: score, + ] else if ActiveRecord::Base.connection.column_exists?(:posts, column) Post.where(id: post_id).update_all ["#{column} = ?", count] @@ -229,15 +246,14 @@ class PostAction < ActiveRecord::Base # topic_user if post_action_type_key == :like - TopicUser.update_post_action_cache(user_id: user_id, - topic_id: topic_id, - post_action_type: post_action_type_key) - end - - if column == "like_count" - Topic.find_by(id: topic_id)&.update_action_counts + TopicUser.update_post_action_cache( + user_id: user_id, + topic_id: topic_id, + post_action_type: post_action_type_key, + ) end + Topic.find_by(id: topic_id)&.update_action_counts if column == "like_count" end end diff --git a/app/models/post_action_type.rb b/app/models/post_action_type.rb index a209a79b6b0..db785c307fe 100644 --- a/app/models/post_action_type.rb +++ b/app/models/post_action_type.rb @@ -24,16 +24,14 @@ class PostActionType < ActiveRecord::Base end def ordered - order('position asc') + order("position asc") end def types unless @types # NOTE: Previously bookmark was type 1 but that has been superseded # by the separate Bookmark model and functionality - @types = Enum.new( - like: 2 - ) + @types = Enum.new(like: 2) @types.merge!(flag_settings.flag_types) end @@ -85,39 +83,22 @@ class PostActionType < ActiveRecord::Base def initialize_flag_settings @flag_settings = FlagSettings.new - @flag_settings.add( - 3, - :off_topic, - notify_type: true, - auto_action_type: true, - ) + @flag_settings.add(3, :off_topic, notify_type: true, auto_action_type: true) @flag_settings.add( 4, :inappropriate, topic_type: true, notify_type: true, auto_action_type: true, - ) - @flag_settings.add( - 8, - :spam, - topic_type: true, - notify_type: true, - auto_action_type: true, - ) - @flag_settings.add( - 6, - :notify_user, - topic_type: false, - notify_type: false, - custom_type: true ) + @flag_settings.add(8, :spam, topic_type: true, notify_type: true, auto_action_type: true) + @flag_settings.add(6, :notify_user, topic_type: false, notify_type: false, custom_type: true) @flag_settings.add( 7, :notify_moderators, topic_type: true, notify_type: true, - custom_type: true + custom_type: true, ) end end diff --git a/app/models/post_analyzer.rb b/app/models/post_analyzer.rb index bbd5bdd7f3e..2d2622c2070 100644 --- a/app/models/post_analyzer.rb +++ b/app/models/post_analyzer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PostAnalyzer - def initialize(raw, topic_id) @raw = raw @topic_id = topic_id @@ -32,19 +31,20 @@ class PostAnalyzer end limit = SiteSetting.max_oneboxes_per_post - result = Oneboxer.apply(cooked, extra_paths: ".inline-onebox-loading") do |url, element| - if opts[:invalidate_oneboxes] - Oneboxer.invalidate(url) - InlineOneboxer.invalidate(url) + result = + Oneboxer.apply(cooked, extra_paths: ".inline-onebox-loading") do |url, element| + if opts[:invalidate_oneboxes] + Oneboxer.invalidate(url) + InlineOneboxer.invalidate(url) + end + next if element["class"] != Oneboxer::ONEBOX_CSS_CLASS + next if limit <= 0 + limit -= 1 + @onebox_urls << url + onebox = Oneboxer.cached_onebox(url) + @found_oneboxes = true if onebox.present? + onebox end - next if element["class"] != Oneboxer::ONEBOX_CSS_CLASS - next if limit <= 0 - limit -= 1 - @onebox_urls << url - onebox = Oneboxer.cached_onebox(url) - @found_oneboxes = true if onebox.present? - onebox - end if result.changed? PrettyText.sanitize_hotlinked_media(result.doc) @@ -59,19 +59,26 @@ class PostAnalyzer return 0 unless @raw.present? # TODO - do we need to look for tags other than img, video and audio? - cooked_stripped.css("img", "video", "audio").reject do |t| - if dom_class = t["class"] - (Post.allowed_image_classes & dom_class.split).count > 0 + cooked_stripped + .css("img", "video", "audio") + .reject do |t| + if dom_class = t["class"] + (Post.allowed_image_classes & dom_class.split).count > 0 + end end - end.count + .count end # How many attachments are present in the post def attachment_count return 0 unless @raw.present? - attachments = cooked_stripped.css("a.attachment[href^=\"#{Discourse.store.absolute_base_url}\"]") - attachments += cooked_stripped.css("a.attachment[href^=\"#{Discourse.store.relative_base_url}\"]") if Discourse.store.internal? + attachments = + cooked_stripped.css("a.attachment[href^=\"#{Discourse.store.absolute_base_url}\"]") + attachments += + cooked_stripped.css( + "a.attachment[href^=\"#{Discourse.store.relative_base_url}\"]", + ) if Discourse.store.internal? attachments.count end @@ -116,15 +123,17 @@ class PostAnalyzer return @raw_links if @raw_links.present? @raw_links = [] - cooked_stripped.css("a").each do |l| - # Don't include @mentions in the link count - next if link_is_a_mention?(l) - # Don't include heading anchor in the link count - next if link_is_an_anchor?(l) - # Don't include hashtags in the link count - next if link_is_a_hashtag?(l) - @raw_links << l['href'].to_s - end + cooked_stripped + .css("a") + .each do |l| + # Don't include @mentions in the link count + next if link_is_a_mention?(l) + # Don't include heading anchor in the link count + next if link_is_an_anchor?(l) + # Don't include hashtags in the link count + next if link_is_a_hashtag?(l) + @raw_links << l["href"].to_s + end @raw_links end @@ -135,27 +144,37 @@ class PostAnalyzer end def cooked_stripped - @cooked_stripped ||= begin - doc = Nokogiri::HTML5.fragment(cook(@raw, topic_id: @topic_id)) - doc.css("pre .mention, aside.quote > .title, aside.quote .mention, aside.quote .mention-group, .onebox, .elided").remove - doc - end + @cooked_stripped ||= + begin + doc = Nokogiri::HTML5.fragment(cook(@raw, topic_id: @topic_id)) + doc.css( + "pre .mention, aside.quote > .title, aside.quote .mention, aside.quote .mention-group, .onebox, .elided", + ).remove + doc + end end private def link_is_a_mention?(l) - href = l['href'].to_s - l['class'].to_s['mention'] && (href.start_with?("#{Discourse.base_path}/u/") || href.start_with?("#{Discourse.base_path}/users/")) + href = l["href"].to_s + l["class"].to_s["mention"] && + ( + href.start_with?("#{Discourse.base_path}/u/") || + href.start_with?("#{Discourse.base_path}/users/") + ) end def link_is_an_anchor?(l) - l['class'].to_s['anchor'] && l['href'].to_s.start_with?('#') + l["class"].to_s["anchor"] && l["href"].to_s.start_with?("#") end def link_is_a_hashtag?(l) - href = l['href'].to_s - l['class'].to_s['hashtag'] && (href.start_with?("#{Discourse.base_path}/c/") || href.start_with?("#{Discourse.base_path}/tag/")) + href = l["href"].to_s + l["class"].to_s["hashtag"] && + ( + href.start_with?("#{Discourse.base_path}/c/") || + href.start_with?("#{Discourse.base_path}/tag/") + ) end - end diff --git a/app/models/post_detail.rb b/app/models/post_detail.rb index 56fbed2ac5f..c28d501d8ec 100644 --- a/app/models/post_detail.rb +++ b/app/models/post_detail.rb @@ -3,7 +3,7 @@ class PostDetail < ActiveRecord::Base belongs_to :post - validates_presence_of :key, :value + validates_presence_of :key, :value validates_uniqueness_of :key, scope: :post_id end diff --git a/app/models/post_hotlinked_media.rb b/app/models/post_hotlinked_media.rb index fc8e5814dfb..de7d6aca7a5 100644 --- a/app/models/post_hotlinked_media.rb +++ b/app/models/post_hotlinked_media.rb @@ -4,11 +4,11 @@ class PostHotlinkedMedia < ActiveRecord::Base belongs_to :post belongs_to :upload enum status: { - downloaded: "downloaded", - too_large: "too_large", - download_failed: "download_failed", - upload_create_failed: "upload_create_failed" - } + downloaded: "downloaded", + too_large: "too_large", + download_failed: "download_failed", + upload_create_failed: "upload_create_failed", + } def self.normalize_src(src) uri = Addressable::URI.heuristic_parse(src) diff --git a/app/models/post_mover.rb b/app/models/post_mover.rb index f8e77c7cd6d..ac6e90471ae 100644 --- a/app/models/post_mover.rb +++ b/app/models/post_mover.rb @@ -19,13 +19,11 @@ class PostMover topic = Topic.find_by_id(id) if topic.archetype != @original_topic.archetype && - [@original_topic.archetype, topic.archetype].include?(Archetype.private_message) + [@original_topic.archetype, topic.archetype].include?(Archetype.private_message) raise Discourse::InvalidParameters end - Topic.transaction do - move_posts_to topic - end + Topic.transaction { move_posts_to topic } add_allowed_users(participants) if participants.present? && @move_to_pm enqueue_jobs(topic) topic @@ -38,20 +36,22 @@ class PostMover raise Discourse::InvalidParameters unless post archetype = @move_to_pm ? Archetype.private_message : Archetype.default - topic = Topic.transaction do - new_topic = Topic.create!( - user: post.user, - title: title, - category_id: category_id, - created_at: post.created_at, - archetype: archetype - ) - DiscourseTagging.tag_topic_by_names(new_topic, Guardian.new(user), tags) - move_posts_to new_topic - watch_new_topic - update_topic_excerpt new_topic - new_topic - end + topic = + Topic.transaction do + new_topic = + Topic.create!( + user: post.user, + title: title, + category_id: category_id, + created_at: post.created_at, + archetype: archetype, + ) + DiscourseTagging.tag_topic_by_names(new_topic, Guardian.new(user), tags) + move_posts_to new_topic + watch_new_topic + update_topic_excerpt new_topic + new_topic + end enqueue_jobs(topic) topic end @@ -73,9 +73,15 @@ class PostMover # we should only exclude whispers with action_code: 'split_topic' # because we use such whispers as a small-action posts when moving posts to the secret message # (in this case we don't want everyone to see that posts were moved, that's why we use whispers) - original_topic_posts_count = @original_topic.posts - .where("post_type = ? or (post_type = ? and action_code != 'split_topic')", Post.types[:regular], Post.types[:whisper]) - .count + original_topic_posts_count = + @original_topic + .posts + .where( + "post_type = ? or (post_type = ? and action_code != 'split_topic')", + Post.types[:regular], + Post.types[:whisper], + ) + .count moving_all_posts = original_topic_posts_count == posts.length create_temp_table @@ -87,9 +93,7 @@ class PostMover update_upload_security_status update_bookmarks - if moving_all_posts - close_topic_and_schedule_deletion - end + close_topic_and_schedule_deletion if moving_all_posts destination_topic.reload destination_topic @@ -152,19 +156,20 @@ class PostMover end def create_first_post(post) - @post_creator = PostCreator.new( - post.user, - raw: post.raw, - topic_id: destination_topic.id, - acting_user: user, - cook_method: post.cook_method, - via_email: post.via_email, - raw_email: post.raw_email, - skip_validations: true, - created_at: post.created_at, - guardian: Guardian.new(user), - skip_jobs: true - ) + @post_creator = + PostCreator.new( + post.user, + raw: post.raw, + topic_id: destination_topic.id, + acting_user: user, + cook_method: post.cook_method, + via_email: post.via_email, + raw_email: post.raw_email, + skip_validations: true, + created_at: post.created_at, + guardian: Guardian.new(user), + skip_jobs: true, + ) new_post = @post_creator.create! move_email_logs(post, new_post) @@ -192,12 +197,10 @@ class PostMover reply_to_post_number: @move_map[post.reply_to_post_number], topic_id: destination_topic.id, sort_order: @move_map[post.post_number], - baked_version: nil + baked_version: nil, } - unless @move_map[post.reply_to_post_number] - update[:reply_to_user_id] = nil - end + update[:reply_to_user_id] = nil unless @move_map[post.reply_to_post_number] post.attributes = update post.save(validate: false) @@ -217,7 +220,7 @@ class PostMover old_post_number: post.post_number, new_topic_id: destination_topic.id, new_post_number: @move_map[post.post_number], - new_topic_title: destination_topic.title + new_topic_title: destination_topic.title, } end @@ -241,9 +244,7 @@ class PostMover end def move_email_logs(old_post, new_post) - EmailLog - .where(post_id: old_post.id) - .update_all(post_id: new_post.id) + EmailLog.where(post_id: old_post.id).update_all(post_id: new_post.id) end def move_notifications @@ -349,7 +350,7 @@ class PostMover old_topic_id: original_topic.id, new_topic_id: destination_topic.id, old_highest_post_number: destination_topic.highest_post_number, - old_highest_staff_post_number: destination_topic.highest_staff_post_number + old_highest_staff_post_number: destination_topic.highest_staff_post_number, } DB.exec(<<~SQL, params) @@ -423,7 +424,10 @@ class PostMover def update_statistics destination_topic.update_statistics original_topic.update_statistics - TopicUser.update_post_action_cache(topic_id: [original_topic.id, destination_topic.id], post_id: @post_ids) + TopicUser.update_post_action_cache( + topic_id: [original_topic.id, destination_topic.id], + post_id: @post_ids, + ) end def update_user_actions @@ -434,35 +438,42 @@ class PostMover move_type_str = PostMover.move_types[@move_type].to_s move_type_str.sub!("topic", "message") if @move_to_pm - message = I18n.with_locale(SiteSetting.default_locale) do - I18n.t( - "move_posts.#{move_type_str}_moderator_post", - count: posts.length, - topic_link: posts.first.is_first_post? ? - "[#{destination_topic.title}](#{destination_topic.relative_url})" : - "[#{destination_topic.title}](#{posts.first.url})" - ) - end + message = + I18n.with_locale(SiteSetting.default_locale) do + I18n.t( + "move_posts.#{move_type_str}_moderator_post", + count: posts.length, + topic_link: + ( + if posts.first.is_first_post? + "[#{destination_topic.title}](#{destination_topic.relative_url})" + else + "[#{destination_topic.title}](#{posts.first.url})" + end + ), + ) + end post_type = @move_to_pm ? Post.types[:whisper] : Post.types[:small_action] original_topic.add_moderator_post( - user, message, + user, + message, post_type: post_type, action_code: "split_topic", - post_number: @first_post_number_moved + post_number: @first_post_number_moved, ) end def posts - @posts ||= begin - Post.where(topic: @original_topic, id: post_ids) - .where.not(post_type: Post.types[:small_action]) - .where.not(raw: '') - .order(:created_at).tap do |posts| - - raise Discourse::InvalidParameters.new(:post_ids) if posts.empty? + @posts ||= + begin + Post + .where(topic: @original_topic, id: post_ids) + .where.not(post_type: Post.types[:small_action]) + .where.not(raw: "") + .order(:created_at) + .tap { |posts| raise Discourse::InvalidParameters.new(:post_ids) if posts.empty? } end - end end def update_last_post_stats @@ -478,9 +489,7 @@ class PostMover end def update_upload_security_status - DB.after_commit do - Jobs.enqueue(:update_topic_upload_security, topic_id: @destination_topic.id) - end + DB.after_commit { Jobs.enqueue(:update_topic_upload_security, topic_id: @destination_topic.id) } end def update_bookmarks @@ -493,9 +502,18 @@ class PostMover def watch_new_topic if @destination_topic.archetype == Archetype.private_message if @original_topic.archetype == Archetype.private_message - notification_levels = TopicUser.where(topic_id: @original_topic.id, user_id: posts.pluck(:user_id)).pluck(:user_id, :notification_level).to_h + notification_levels = + TopicUser + .where(topic_id: @original_topic.id, user_id: posts.pluck(:user_id)) + .pluck(:user_id, :notification_level) + .to_h else - notification_levels = posts.pluck(:user_id).uniq.map { |user_id| [user_id, TopicUser.notification_levels[:watching]] }.to_h + notification_levels = + posts + .pluck(:user_id) + .uniq + .map { |user_id| [user_id, TopicUser.notification_levels[:watching]] } + .to_h end else notification_levels = [[@destination_topic.user_id, TopicUser.notification_levels[:watching]]] @@ -506,7 +524,10 @@ class PostMover user_id, @destination_topic.id, notification_level: notification_level, - notifications_reason_id: TopicUser.notification_reasons[destination_topic.user_id == user_id ? :created_topic : :created_post] + notifications_reason_id: + TopicUser.notification_reasons[ + destination_topic.user_id == user_id ? :created_topic : :created_post + ], ) end end @@ -514,37 +535,34 @@ class PostMover def add_allowed_users(usernames) return unless usernames.present? - names = usernames.split(',').flatten - User.where(username: names).find_each do |user| - destination_topic.topic_allowed_users.build(user_id: user.id) unless destination_topic.topic_allowed_users.where(user_id: user.id).exists? - end + names = usernames.split(",").flatten + User + .where(username: names) + .find_each do |user| + unless destination_topic.topic_allowed_users.where(user_id: user.id).exists? + destination_topic.topic_allowed_users.build(user_id: user.id) + end + end destination_topic.save! end def enqueue_jobs(topic) @post_creator.enqueue_jobs if @post_creator - Jobs.enqueue( - :notify_moved_posts, - post_ids: post_ids, - moved_by_id: user.id - ) + Jobs.enqueue(:notify_moved_posts, post_ids: post_ids, moved_by_id: user.id) - Jobs.enqueue( - :delete_inaccessible_notifications, - topic_id: topic.id - ) + Jobs.enqueue(:delete_inaccessible_notifications, topic_id: topic.id) end def close_topic_and_schedule_deletion - @original_topic.update_status('closed', true, @user) + @original_topic.update_status("closed", true, @user) days_to_deleting = SiteSetting.delete_merged_stub_topics_after_days if days_to_deleting > 0 @original_topic.set_or_create_timer( TopicTimer.types[:delete], days_to_deleting * 24, - by_user: @user + by_user: @user, ) end end diff --git a/app/models/post_reply.rb b/app/models/post_reply.rb index 1cedb847a94..d2cce1de360 100644 --- a/app/models/post_reply.rb +++ b/app/models/post_reply.rb @@ -2,7 +2,7 @@ class PostReply < ActiveRecord::Base belongs_to :post - belongs_to :reply, foreign_key: :reply_post_id, class_name: 'Post' + belongs_to :reply, foreign_key: :reply_post_id, class_name: "Post" validates_uniqueness_of :reply_post_id, scope: :post_id validate :ensure_same_topic @@ -11,10 +11,7 @@ class PostReply < ActiveRecord::Base def ensure_same_topic if post.topic_id != reply.topic_id - self.errors.add( - :base, - I18n.t("activerecord.errors.models.post_reply.base.different_topic") - ) + self.errors.add(:base, I18n.t("activerecord.errors.models.post_reply.base.different_topic")) end end end diff --git a/app/models/post_reply_key.rb b/app/models/post_reply_key.rb index 83686df18ba..1084996ec81 100644 --- a/app/models/post_reply_key.rb +++ b/app/models/post_reply_key.rb @@ -11,7 +11,7 @@ class PostReplyKey < ActiveRecord::Base validates :reply_key, presence: true def reply_key - super&.delete('-') + super&.delete("-") end def self.generate_reply_key diff --git a/app/models/post_revision.rb b/app/models/post_revision.rb index 9d9d48c3195..91554852383 100644 --- a/app/models/post_revision.rb +++ b/app/models/post_revision.rb @@ -39,7 +39,6 @@ class PostRevision < ActiveRecord::Base def create_notification PostActionNotifier.after_create_post_revision(self) end - end # == Schema Information diff --git a/app/models/post_timing.rb b/app/models/post_timing.rb index 9efb3e59f2a..1a3863672f5 100644 --- a/app/models/post_timing.rb +++ b/app/models/post_timing.rb @@ -10,7 +10,8 @@ class PostTiming < ActiveRecord::Base def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number) # This is done in SQL cause the logic is quite tricky and we want to do this in one db hit # - DB.exec("INSERT INTO post_timings(topic_id, user_id, post_number, msecs) + DB.exec( + "INSERT INTO post_timings(topic_id, user_id, post_number, msecs) SELECT :topic_id, user_id, :pretend_read_post_number, 1 FROM post_timings pt WHERE topic_id = :topic_id AND @@ -22,27 +23,32 @@ class PostTiming < ActiveRecord::Base pt1.user_id = pt.user_id ) ", - pretend_read_post_number: pretend_read_post_number, - topic_id: topic_id, - actual_read_post_number: actual_read_post_number - ) + pretend_read_post_number: pretend_read_post_number, + topic_id: topic_id, + actual_read_post_number: actual_read_post_number, + ) TopicUser.ensure_consistency!(topic_id) end def self.record_new_timing(args) - row_count = DB.exec("INSERT INTO post_timings (topic_id, user_id, post_number, msecs) + row_count = + DB.exec( + "INSERT INTO post_timings (topic_id, user_id, post_number, msecs) SELECT :topic_id, :user_id, :post_number, :msecs ON CONFLICT DO NOTHING", - args) + args, + ) # concurrency is hard, we are not running serialized so this can possibly # still happen, if it happens we just don't care, its an invalid record anyway return if row_count == 0 - Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1' + Post.where( + ["topic_id = :topic_id and post_number = :post_number", args], + ).update_all "reads = reads + 1" return if Topic.exists?(id: args[:topic_id], archetype: Archetype.private_message) - UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1' + UserStat.where(user_id: args[:user_id]).update_all "posts_read_count = posts_read_count + 1" end # Increases a timer if a row exists, otherwise create it @@ -65,13 +71,16 @@ class PostTiming < ActiveRecord::Base last_read = post_number - 1 PostTiming.transaction do - PostTiming.where("topic_id = ? AND user_id = ? AND post_number > ?", topic.id, user.id, last_read).delete_all - if last_read < 1 - last_read = nil - end + PostTiming.where( + "topic_id = ? AND user_id = ? AND post_number > ?", + topic.id, + user.id, + last_read, + ).delete_all + last_read = nil if last_read < 1 TopicUser.where(user_id: user.id, topic_id: topic.id).update_all( - last_read_post_number: last_read + last_read_post_number: last_read, ) topic.posts.find_by(post_number: post_number).decrement!(:reads) @@ -86,28 +95,23 @@ class PostTiming < ActiveRecord::Base def self.destroy_for(user_id, topic_ids) PostTiming.transaction do - PostTiming - .where('user_id = ? and topic_id in (?)', user_id, topic_ids) - .delete_all + PostTiming.where("user_id = ? and topic_id in (?)", user_id, topic_ids).delete_all - TopicUser - .where('user_id = ? and topic_id in (?)', user_id, topic_ids) - .delete_all + TopicUser.where("user_id = ? and topic_id in (?)", user_id, topic_ids).delete_all - Post.where(topic_id: topic_ids).update_all('reads = reads - 1') + Post.where(topic_id: topic_ids).update_all("reads = reads - 1") date = Topic.listable_topics.where(id: topic_ids).minimum(:updated_at) - if date - set_minimum_first_unread!(user_id: user_id, date: date) - end + set_minimum_first_unread!(user_id: user_id, date: date) if date end end def self.set_minimum_first_unread_pm!(topic:, user_id:, date:) if topic.topic_allowed_users.exists?(user_id: user_id) - UserStat.where("first_unread_pm_at > ? AND user_id = ?", date, user_id) - .update_all(first_unread_pm_at: date) + UserStat.where("first_unread_pm_at > ? AND user_id = ?", date, user_id).update_all( + first_unread_pm_at: date, + ) else DB.exec(<<~SQL, date: date, user_id: user_id, topic_id: topic.id) UPDATE group_users gu @@ -155,12 +159,10 @@ class PostTiming < ActiveRecord::Base end timings.each_with_index do |(post_number, time), index| - join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number, #{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx" - highest_seen = post_number.to_i > highest_seen ? - post_number.to_i : highest_seen + highest_seen = post_number.to_i > highest_seen ? post_number.to_i : highest_seen end if join_table.length > 0 @@ -188,10 +190,12 @@ SQL timings.each_with_index do |(post_number, time), index| unless existing.include?(index) - PostTiming.record_new_timing(topic_id: topic_id, - post_number: post_number, - user_id: current_user.id, - msecs: time) + PostTiming.record_new_timing( + topic_id: topic_id, + post_number: post_number, + user_id: current_user.id, + msecs: time, + ) end end end @@ -203,7 +207,14 @@ SQL topic_time = max_time_per_post if topic_time > max_time_per_post - TopicUser.update_last_read(current_user, topic_id, highest_seen, new_posts_read, topic_time, opts) + TopicUser.update_last_read( + current_user, + topic_id, + highest_seen, + new_posts_read, + topic_time, + opts, + ) TopicGroup.update_last_read(current_user, topic_id, highest_seen) if total_changed > 0 diff --git a/app/models/previous_replies_site_setting.rb b/app/models/previous_replies_site_setting.rb index 59b7c2d246e..ff1ed64b9a0 100644 --- a/app/models/previous_replies_site_setting.rb +++ b/app/models/previous_replies_site_setting.rb @@ -1,22 +1,19 @@ # frozen_string_literal: true class PreviousRepliesSiteSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - values.any? { |v| v[:value] == val.to_i } + val.to_i.to_s == val.to_s && values.any? { |v| v[:value] == val.to_i } end def self.values @values ||= [ - { name: 'user.email_previous_replies.always', value: 0 }, - { name: 'user.email_previous_replies.unless_emailed', value: 1 }, - { name: 'user.email_previous_replies.never', value: 2 }, + { name: "user.email_previous_replies.always", value: 0 }, + { name: "user.email_previous_replies.unless_emailed", value: 1 }, + { name: "user.email_previous_replies.never", value: 2 }, ] end def self.translate_names? true end - end diff --git a/app/models/private_message_topic_tracking_state.rb b/app/models/private_message_topic_tracking_state.rb index cd03054c6f1..48db452375d 100644 --- a/app/models/private_message_topic_tracking_state.rb +++ b/app/models/private_message_topic_tracking_state.rb @@ -30,8 +30,8 @@ class PrivateMessageTopicTrackingState sql + "\n\n LIMIT :max_topics", { max_topics: TopicTrackingState::MAX_TOPICS, - min_new_topic_date: Time.at(SiteSetting.min_new_topics_time).to_datetime - } + min_new_topic_date: Time.at(SiteSetting.min_new_topics_time).to_datetime, + }, ) end @@ -41,9 +41,7 @@ class PrivateMessageTopicTrackingState sql << report_raw_sql(user, skip_new: true) end - def self.report_raw_sql(user, skip_unread: false, - skip_new: false) - + def self.report_raw_sql(user, skip_unread: false, skip_new: false) unread = if skip_unread "1=0" @@ -101,9 +99,7 @@ class PrivateMessageTopicTrackingState topic = post.topic return unless topic.private_message? - scope = TopicUser - .tracking(post.topic_id) - .includes(user: [:user_stat, :user_option]) + scope = TopicUser.tracking(post.topic_id).includes(user: %i[user_stat user_option]) allowed_group_ids = topic.allowed_groups.pluck(:id) @@ -115,9 +111,11 @@ class PrivateMessageTopicTrackingState end if group_ids.present? - scope = scope - .joins("INNER JOIN group_users gu ON gu.user_id = topic_users.user_id") - .where("gu.group_id IN (?)", group_ids) + scope = + scope.joins("INNER JOIN group_users gu ON gu.user_id = topic_users.user_id").where( + "gu.group_id IN (?)", + group_ids, + ) end # Note: At some point we may want to make the same peformance optimisation @@ -127,31 +125,27 @@ class PrivateMessageTopicTrackingState # # cf. f6c852bf8e7f4dea519425ba87a114f22f52a8f4 scope - .select([:user_id, :last_read_post_number, :notification_level]) + .select(%i[user_id last_read_post_number notification_level]) .each do |tu| + if tu.last_read_post_number.nil? && + topic.created_at < tu.user.user_option.treat_as_new_topic_start_date + next + end - if tu.last_read_post_number.nil? && - topic.created_at < tu.user.user_option.treat_as_new_topic_start_date - - next - end - - message = { - topic_id: post.topic_id, - message_type: UNREAD_MESSAGE_TYPE, - payload: { - last_read_post_number: tu.last_read_post_number, - highest_post_number: post.post_number, - notification_level: tu.notification_level, - group_ids: allowed_group_ids, - created_by_user_id: post.user_id + message = { + topic_id: post.topic_id, + message_type: UNREAD_MESSAGE_TYPE, + payload: { + last_read_post_number: tu.last_read_post_number, + highest_post_number: post.post_number, + notification_level: tu.notification_level, + group_ids: allowed_group_ids, + created_by_user_id: post.user_id, + }, } - } - MessageBus.publish(self.user_channel(tu.user_id), message.as_json, - user_ids: [tu.user_id] - ) - end + MessageBus.publish(self.user_channel(tu.user_id), message.as_json, user_ids: [tu.user_id]) + end end def self.publish_new(topic) @@ -165,16 +159,22 @@ class PrivateMessageTopicTrackingState highest_post_number: 1, group_ids: topic.allowed_groups.pluck(:id), created_by_user_id: topic.user_id, - } + }, }.as_json - topic.allowed_users.pluck(:id).each do |user_id| - MessageBus.publish(self.user_channel(user_id), message, user_ids: [user_id]) - end + topic + .allowed_users + .pluck(:id) + .each do |user_id| + MessageBus.publish(self.user_channel(user_id), message, user_ids: [user_id]) + end - topic.allowed_groups.pluck(:id).each do |group_id| - MessageBus.publish(self.group_channel(group_id), message, group_ids: [group_id]) - end + topic + .allowed_groups + .pluck(:id) + .each do |group_id| + MessageBus.publish(self.group_channel(group_id), message, group_ids: [group_id]) + end end def self.publish_group_archived(topic:, group_id:, acting_user_id: nil) @@ -185,15 +185,11 @@ class PrivateMessageTopicTrackingState topic_id: topic.id, payload: { group_ids: [group_id], - acting_user_id: acting_user_id - } + acting_user_id: acting_user_id, + }, }.as_json - MessageBus.publish( - self.group_channel(group_id), - message, - group_ids: [group_id] - ) + MessageBus.publish(self.group_channel(group_id), message, group_ids: [group_id]) end def self.publish_read(topic_id, last_read_post_number, user, notification_level = nil) @@ -203,7 +199,7 @@ class PrivateMessageTopicTrackingState topic_id: topic_id, user: user, last_read_post_number: last_read_post_number, - notification_level: notification_level + notification_level: notification_level, ) end diff --git a/app/models/published_page.rb b/app/models/published_page.rb index 8345ae05bb6..937ba59310e 100644 --- a/app/models/published_page.rb +++ b/app/models/published_page.rb @@ -10,7 +10,7 @@ class PublishedPage < ActiveRecord::Base def slug_format if slug !~ /^[a-zA-Z\-\_0-9]+$/ errors.add(:slug, I18n.t("publish_page.slug_errors.invalid")) - elsif ["check-slug", "by-topic"].include?(slug) + elsif %w[check-slug by-topic].include?(slug) errors.add(:slug, I18n.t("publish_page.slug_errors.unavailable")) end end @@ -26,16 +26,17 @@ class PublishedPage < ActiveRecord::Base def self.publish!(publisher, topic, slug, options = {}) pp = nil - results = transaction do - pp = find_or_initialize_by(topic: topic) - pp.slug = slug.strip - pp.public = options[:public] || false + results = + transaction do + pp = find_or_initialize_by(topic: topic) + pp.slug = slug.strip + pp.public = options[:public] || false - if pp.save - StaffActionLogger.new(publisher).log_published_page(topic.id, slug) - [true, pp] + if pp.save + StaffActionLogger.new(publisher).log_published_page(topic.id, slug) + [true, pp] + end end - end results || [false, pp] end diff --git a/app/models/quoted_post.rb b/app/models/quoted_post.rb index 9a6a96e9ebf..54d68c9a926 100644 --- a/app/models/quoted_post.rb +++ b/app/models/quoted_post.rb @@ -2,36 +2,36 @@ class QuotedPost < ActiveRecord::Base belongs_to :post - belongs_to :quoted_post, class_name: 'Post' + belongs_to :quoted_post, class_name: "Post" # NOTE we already have a path that does this for topic links, # however topic links exclude quotes and links within a topic # we are double parsing this fragment, this may be worth optimising later def self.extract_from(post) - doc = Nokogiri::HTML5.fragment(post.cooked) uniq = {} - doc.css("aside.quote[data-topic]").each do |a| - topic_id = a['data-topic'].to_i - post_number = a['data-post'].to_i + doc + .css("aside.quote[data-topic]") + .each do |a| + topic_id = a["data-topic"].to_i + post_number = a["data-post"].to_i - next if topic_id == 0 || post_number == 0 - next if uniq[[topic_id, post_number]] - next if post.topic_id == topic_id && post.post_number == post_number + next if topic_id == 0 || post_number == 0 + next if uniq[[topic_id, post_number]] + next if post.topic_id == topic_id && post.post_number == post_number - uniq[[topic_id, post_number]] = true - end + uniq[[topic_id, post_number]] = true + end if uniq.length == 0 DB.exec("DELETE FROM quoted_posts WHERE post_id = :post_id", post_id: post.id) else - args = { post_id: post.id, topic_ids: uniq.keys.map(&:first), - post_numbers: uniq.keys.map(&:second) + post_numbers: uniq.keys.map(&:second), } DB.exec(<<~SQL, args) @@ -67,14 +67,14 @@ class QuotedPost < ActiveRecord::Base reply_quoted = false if post.reply_to_post_number - reply_post_id = Post.where(topic_id: post.topic_id, post_number: post.reply_to_post_number).pluck_first(:id) - reply_quoted = reply_post_id.present? && QuotedPost.where(post_id: post.id, quoted_post_id: reply_post_id).count > 0 - end - - if reply_quoted != post.reply_quoted - post.update_columns(reply_quoted: reply_quoted) + reply_post_id = + Post.where(topic_id: post.topic_id, post_number: post.reply_to_post_number).pluck_first(:id) + reply_quoted = + reply_post_id.present? && + QuotedPost.where(post_id: post.id, quoted_post_id: reply_post_id).count > 0 end + post.update_columns(reply_quoted: reply_quoted) if reply_quoted != post.reply_quoted end end diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb index 2c7f4e1ed13..baed6456548 100644 --- a/app/models/remote_theme.rb +++ b/app/models/remote_theme.rb @@ -1,28 +1,35 @@ # frozen_string_literal: true class RemoteTheme < ActiveRecord::Base - METADATA_PROPERTIES = %i{ - license_url - about_url - authors - theme_version - minimum_discourse_version - maximum_discourse_version - } + METADATA_PROPERTIES = %i[ + license_url + about_url + authors + theme_version + minimum_discourse_version + maximum_discourse_version + ] - class ImportError < StandardError; end + class ImportError < StandardError + end - ALLOWED_FIELDS = %w{scss embedded_scss head_tag header after_header body_tag footer} + ALLOWED_FIELDS = %w[scss embedded_scss head_tag header after_header body_tag footer] - GITHUB_REGEXP = /^https?:\/\/github\.com\// - GITHUB_SSH_REGEXP = /^ssh:\/\/git@github\.com:/ + GITHUB_REGEXP = %r{^https?://github\.com/} + GITHUB_SSH_REGEXP = %r{^ssh://git@github\.com:} has_one :theme, autosave: false - scope :joined_remotes, -> { - joins("JOIN themes ON themes.remote_theme_id = remote_themes.id").where.not(remote_url: "") - } + scope :joined_remotes, + -> { + joins("JOIN themes ON themes.remote_theme_id = remote_themes.id").where.not( + remote_url: "", + ) + } - validates_format_of :minimum_discourse_version, :maximum_discourse_version, with: Discourse::VERSION_REGEXP, allow_nil: true + validates_format_of :minimum_discourse_version, + :maximum_discourse_version, + with: Discourse::VERSION_REGEXP, + allow_nil: true def self.extract_theme_info(importer) json = JSON.parse(importer["about.json"]) @@ -32,7 +39,14 @@ class RemoteTheme < ActiveRecord::Base raise ImportError.new I18n.t("themes.import_error.about_json") end - def self.update_zipped_theme(filename, original_filename, match_theme: false, user: Discourse.system_user, theme_id: nil, update_components: nil) + def self.update_zipped_theme( + filename, + original_filename, + match_theme: false, + user: Discourse.system_user, + theme_id: nil, + update_components: nil + ) importer = ThemeStore::ZipImporter.new(filename, original_filename) importer.import! @@ -60,10 +74,18 @@ class RemoteTheme < ActiveRecord::Base child_components = child_components.map { |url| ThemeStore::GitImporter.new(url.strip).url } if update_components == "sync" - ChildTheme.joins(child_theme: :remote_theme).where("remote_themes.remote_url NOT IN (?)", child_components).delete_all + ChildTheme + .joins(child_theme: :remote_theme) + .where("remote_themes.remote_url NOT IN (?)", child_components) + .delete_all end - child_components -= theme.child_themes.joins(:remote_theme).where("remote_themes.remote_url IN (?)", child_components).pluck("remote_themes.remote_url") + child_components -= + theme + .child_themes + .joins(:remote_theme) + .where("remote_themes.remote_url IN (?)", child_components) + .pluck("remote_themes.remote_url") theme.child_components = child_components theme.update_child_components end @@ -106,7 +128,9 @@ class RemoteTheme < ActiveRecord::Base end def self.out_of_date_themes - self.joined_remotes.where("commits_behind > 0 OR remote_version <> local_version") + self + .joined_remotes + .where("commits_behind > 0 OR remote_version <> local_version") .where(themes: { enabled: true }) .pluck("themes.name", "themes.id") end @@ -164,13 +188,28 @@ class RemoteTheme < ActiveRecord::Base if path = importer.real_path(relative_path) new_path = "#{File.dirname(path)}/#{SecureRandom.hex}#{File.extname(path)}" File.rename(path, new_path) # OptimizedImage has strict file name restrictions, so rename temporarily - upload = UploadCreator.new(File.open(new_path), File.basename(relative_path), for_theme: true).create_for(theme.user_id) + upload = + UploadCreator.new( + File.open(new_path), + File.basename(relative_path), + for_theme: true, + ).create_for(theme.user_id) if !upload.errors.empty? - raise ImportError, I18n.t("themes.import_error.upload", name: name, errors: upload.errors.full_messages.join(",")) + raise ImportError, + I18n.t( + "themes.import_error.upload", + name: name, + errors: upload.errors.full_messages.join(","), + ) end - updated_fields << theme.set_field(target: :common, name: name, type: :theme_upload_var, upload_id: upload.id) + updated_fields << theme.set_field( + target: :common, + name: name, + type: :theme_upload_var, + upload_id: upload.id, + ) end end @@ -185,14 +224,25 @@ class RemoteTheme < ActiveRecord::Base self.public_send(:"#{property}=", theme_info[property.to_s]) end if !self.valid? - raise ImportError, I18n.t("themes.import_error.about_json_values", errors: self.errors.full_messages.join(",")) + raise ImportError, + I18n.t( + "themes.import_error.about_json_values", + errors: self.errors.full_messages.join(","), + ) end ThemeModifierSet.modifiers.keys.each do |modifier_name| - theme.theme_modifier_set.public_send(:"#{modifier_name}=", theme_info.dig("modifiers", modifier_name.to_s)) + theme.theme_modifier_set.public_send( + :"#{modifier_name}=", + theme_info.dig("modifiers", modifier_name.to_s), + ) end if !theme.theme_modifier_set.valid? - raise ImportError, I18n.t("themes.import_error.modifier_values", errors: theme.theme_modifier_set.errors.full_messages.join(",")) + raise ImportError, + I18n.t( + "themes.import_error.modifier_values", + errors: theme.theme_modifier_set.errors.full_messages.join(","), + ) end importer.all_files.each do |filename| @@ -230,9 +280,7 @@ class RemoteTheme < ActiveRecord::Base return unless hex override = hex.downcase - if override !~ /\A[0-9a-f]{6}\z/ - override = nil - end + override = nil if override !~ /\A[0-9a-f]{6}\z/ override end @@ -247,8 +295,9 @@ class RemoteTheme < ActiveRecord::Base # Update main colors ColorScheme.base.colors_hashes.each do |color| override = normalize_override(colors[color[:name]]) - color_scheme_color = scheme.color_scheme_colors.to_a.find { |c| c.name == color[:name] } || - scheme.color_scheme_colors.build(name: color[:name]) + color_scheme_color = + scheme.color_scheme_colors.to_a.find { |c| c.name == color[:name] } || + scheme.color_scheme_colors.build(name: color[:name]) color_scheme_color.hex = override || color[:hex] theme.notify_color_change(color_scheme_color) if color_scheme_color.hex_changed? end @@ -275,9 +324,7 @@ class RemoteTheme < ActiveRecord::Base # we may have stuff pointed at the incorrect scheme? end - if theme.new_record? - theme.color_scheme = ordered_schemes.first - end + theme.color_scheme = ordered_schemes.first if theme.new_record? end def github_diff_link diff --git a/app/models/remove_muted_tags_from_latest_site_setting.rb b/app/models/remove_muted_tags_from_latest_site_setting.rb index 9ca328e328f..fb07d94b7a9 100644 --- a/app/models/remove_muted_tags_from_latest_site_setting.rb +++ b/app/models/remove_muted_tags_from_latest_site_setting.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class RemoveMutedTagsFromLatestSiteSetting < EnumSiteSetting - ALWAYS ||= "always" ONLY_MUTED ||= "only_muted" NEVER ||= "never" @@ -14,7 +13,7 @@ class RemoveMutedTagsFromLatestSiteSetting < EnumSiteSetting @values ||= [ { name: "admin.tags.remove_muted_tags_from_latest.always", value: ALWAYS }, { name: "admin.tags.remove_muted_tags_from_latest.only_muted", value: ONLY_MUTED }, - { name: "admin.tags.remove_muted_tags_from_latest.never", value: NEVER } + { name: "admin.tags.remove_muted_tags_from_latest.never", value: NEVER }, ] end diff --git a/app/models/report.rb b/app/models/report.rb index fa893edfcb4..cd9b7623ac1 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -5,7 +5,16 @@ class Report # and you want to ensure cache is reset SCHEMA_VERSION = 4 - FILTERS = [:name, :start_date, :end_date, :category, :group, :trust_level, :file_extension, :include_subcategories] + FILTERS = %i[ + name + start_date + end_date + category + group + trust_level + file_extension + include_subcategories + ] include Reports::PostEdits include Reports::TopTrafficSources @@ -51,11 +60,30 @@ class Report include Reports::TopUsersByLikesReceivedFromInferiorTrustLevel include Reports::TopUsersByLikesReceivedFromAVarietyOfPeople - attr_accessor :type, :data, :total, :prev30Days, :start_date, - :end_date, :labels, :prev_period, :facets, :limit, :average, - :percent, :higher_is_better, :icon, :modes, :prev_data, - :prev_start_date, :prev_end_date, :dates_filtering, :error, - :primary_color, :secondary_color, :filters, :available_filters + attr_accessor :type, + :data, + :total, + :prev30Days, + :start_date, + :end_date, + :labels, + :prev_period, + :facets, + :limit, + :average, + :percent, + :higher_is_better, + :icon, + :modes, + :prev_data, + :prev_start_date, + :prev_end_date, + :dates_filtering, + :error, + :primary_color, + :secondary_color, + :filters, + :available_filters def self.default_days 30 @@ -63,16 +91,8 @@ class Report def self.default_labels [ - { - type: :date, - property: :x, - title: I18n.t("reports.default.labels.day") - }, - { - type: :number, - property: :y, - title: I18n.t("reports.default.labels.count") - }, + { type: :date, property: :x, title: I18n.t("reports.default.labels.day") }, + { type: :number, property: :y, title: I18n.t("reports.default.labels.count") }, ] end @@ -84,13 +104,13 @@ class Report @average = false @percent = false @higher_is_better = true - @modes = [:table, :chart] + @modes = %i[table chart] @prev_data = nil @dates_filtering = true @available_filters = {} @filters = {} - tertiary = ColorScheme.hex_for_name('tertiary') || '0088cc' + tertiary = ColorScheme.hex_for_name("tertiary") || "0088cc" @primary_color = rgba_color(tertiary) @secondary_color = rgba_color(tertiary, 0.1) end @@ -105,13 +125,16 @@ class Report report.limit, report.filters.blank? ? nil : MultiJson.dump(report.filters), SCHEMA_VERSION, - ].compact.map(&:to_s).join(':') + ].compact.map(&:to_s).join(":") end def add_filter(name, options = {}) if options[:type].blank? options[:type] = name - Discourse.deprecate("#{name} filter should define a `:type` option. Temporarily setting type to #{name}.", drop_from: '2.9.0') + Discourse.deprecate( + "#{name} filter should define a `:type` option. Temporarily setting type to #{name}.", + drop_from: "2.9.0", + ) end available_filters[name] = options @@ -123,12 +146,12 @@ class Report def add_category_filter category_id = filters[:category].to_i if filters[:category].present? - add_filter('category', type: 'category', default: category_id) + add_filter("category", type: "category", default: category_id) return if category_id.blank? include_subcategories = filters[:include_subcategories] include_subcategories = !!ActiveRecord::Type::Boolean.new.cast(include_subcategories) - add_filter('include_subcategories', type: 'bool', default: include_subcategories) + add_filter("include_subcategories", type: "bool", default: include_subcategories) [category_id, include_subcategories] end @@ -136,12 +159,10 @@ class Report def self.clear_cache(type = nil) pattern = type ? "reports:#{type}:*" : "reports:*" - Discourse.cache.keys(pattern).each do |key| - Discourse.cache.redis.del(key) - end + Discourse.cache.keys(pattern).each { |key| Discourse.cache.redis.del(key) } end - def self.wrap_slow_query(timeout = 20000) + def self.wrap_slow_query(timeout = 20_000) ActiveRecord::Base.connection.transaction do # Allows only read only transactions DB.exec "SET TRANSACTION READ ONLY" @@ -195,8 +216,12 @@ class Report json[:prev30Days] = self.prev30Days if self.prev30Days json[:limit] = self.limit if self.limit - if type == 'page_view_crawler_reqs' - json[:related_report] = Report.find('web_crawlers', start_date: start_date, end_date: end_date)&.as_json + if type == "page_view_crawler_reqs" + json[:related_report] = Report.find( + "web_crawlers", + start_date: start_date, + end_date: end_date, + )&.as_json end end end @@ -212,7 +237,7 @@ class Report report = Report.new(type) report.start_date = opts[:start_date] if opts[:start_date] report.end_date = opts[:end_date] if opts[:end_date] - report.facets = opts[:facets] || [:total, :prev30Days] + report.facets = opts[:facets] || %i[total prev30Days] report.limit = opts[:limit] if opts[:limit] report.average = opts[:average] if opts[:average] report.percent = opts[:percent] if opts[:percent] @@ -268,7 +293,9 @@ class Report # given reports can be added by plugins we don’t want dashboard failures # on report computation, however we do want to log which report is provoking # an error - Rails.logger.error("Error while computing report `#{report.type}`: #{e.message}\n#{e.backtrace.join("\n")}") + Rails.logger.error( + "Error while computing report `#{report.type}`: #{e.message}\n#{e.backtrace.join("\n")}", + ) end report @@ -277,32 +304,35 @@ class Report def self.req_report(report, filter = nil) data = if filter == :page_view_total - ApplicationRequest.where(req_type: [ - ApplicationRequest.req_types.reject { |k, v| k =~ /mobile/ }.map { |k, v| v if k =~ /page_view/ }.compact - ].flatten) + ApplicationRequest.where( + req_type: [ + ApplicationRequest + .req_types + .reject { |k, v| k =~ /mobile/ } + .map { |k, v| v if k =~ /page_view/ } + .compact, + ].flatten, + ) else ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter]) end - if filter == :page_view_total - report.icon = 'file' - end + report.icon = "file" if filter == :page_view_total report.data = [] - data.where('date >= ? AND date <= ?', report.start_date, report.end_date) + data + .where("date >= ? AND date <= ?", report.start_date, report.end_date) .order(date: :asc) .group(:date) .sum(:count) - .each do |date, count| - report.data << { x: date, y: count } - end + .each { |date, count| report.data << { x: date, y: count } } report.total = data.sum(:count) - report.prev30Days = data.where( - 'date >= ? AND date < ?', - (report.start_date - 31.days), report.start_date - ).sum(:count) + report.prev30Days = + data.where("date >= ? AND date < ?", (report.start_date - 31.days), report.start_date).sum( + :count, + ) end def self.report_about(report, subject_class, report_method = :count_per_day) @@ -313,9 +343,9 @@ class Report def self.basic_report_about(report, subject_class, report_method, *args) report.data = [] - subject_class.public_send(report_method, *args).each do |date, count| - report.data << { x: date, y: count } - end + subject_class + .public_send(report_method, *args) + .each { |date, count| report.data << { x: date, y: count } } end def self.add_prev_data(report, subject_class, report_method, *args) @@ -325,25 +355,27 @@ class Report end end - def self.add_counts(report, subject_class, query_column = 'created_at') + def self.add_counts(report, subject_class, query_column = "created_at") if report.facets.include?(:prev_period) - prev_data = subject_class - .where("#{query_column} >= ? and #{query_column} < ?", + prev_data = + subject_class.where( + "#{query_column} >= ? and #{query_column} < ?", report.prev_start_date, - report.prev_end_date) + report.prev_end_date, + ) report.prev_period = prev_data.count end - if report.facets.include?(:total) - report.total = subject_class.count - end + report.total = subject_class.count if report.facets.include?(:total) if report.facets.include?(:prev30Days) - report.prev30Days = subject_class - .where("#{query_column} >= ? and #{query_column} < ?", + report.prev30Days = + subject_class.where( + "#{query_column} >= ? and #{query_column} < ?", report.start_date - 30.days, - report.start_date).count + report.start_date, + ).count end end @@ -351,28 +383,43 @@ class Report category_id, include_subcategories = report.add_category_filter report.data = [] - PostAction.count_per_day_for_type(post_action_type, category_id: category_id, include_subcategories: include_subcategories, start_date: report.start_date, end_date: report.end_date).each do |date, count| - report.data << { x: date, y: count } - end + PostAction + .count_per_day_for_type( + post_action_type, + category_id: category_id, + include_subcategories: include_subcategories, + start_date: report.start_date, + end_date: report.end_date, + ) + .each { |date, count| report.data << { x: date, y: count } } countable = PostAction.unscoped.where(post_action_type_id: post_action_type) if category_id if include_subcategories - countable = countable.joins(post: :topic).where('topics.category_id IN (?)', Category.subcategory_ids(category_id)) + countable = + countable.joins(post: :topic).where( + "topics.category_id IN (?)", + Category.subcategory_ids(category_id), + ) else - countable = countable.joins(post: :topic).where('topics.category_id = ?', category_id) + countable = countable.joins(post: :topic).where("topics.category_id = ?", category_id) end end - add_counts report, countable, 'post_actions.created_at' + add_counts report, countable, "post_actions.created_at" end def self.private_messages_report(report, topic_subtype) - report.icon = 'envelope' - subject = Topic.where('topics.user_id > 0') - basic_report_about report, subject, :private_message_topics_count_per_day, report.start_date, report.end_date, topic_subtype - subject = Topic.private_messages.where('topics.user_id > 0').with_subtype(topic_subtype) - add_counts report, subject, 'topics.created_at' + report.icon = "envelope" + subject = Topic.where("topics.user_id > 0") + basic_report_about report, + subject, + :private_message_topics_count_per_day, + report.start_date, + report.end_date, + topic_subtype + subject = Topic.private_messages.where("topics.user_id > 0").with_subtype(topic_subtype) + add_counts report, subject, "topics.created_at" end def lighten_color(hex, amount) @@ -386,31 +433,27 @@ class Report def rgba_color(hex, opacity = 1) rgbs = hex_to_rgbs(adjust_hex(hex)) - "rgba(#{rgbs.join(',')},#{opacity})" + "rgba(#{rgbs.join(",")},#{opacity})" end private def adjust_hex(hex) - hex = hex.gsub('#', '') + hex = hex.gsub("#", "") if hex.size == 3 chars = hex.scan(/\w/) hex = chars.zip(chars).flatten.join end - if hex.size < 3 - hex = hex.ljust(6, hex.last) - end + hex = hex.ljust(6, hex.last) if hex.size < 3 hex end def hex_to_rgbs(hex_color) - hex_color = hex_color.gsub('#', '') + hex_color = hex_color.gsub("#", "") rgbs = hex_color.scan(/../) - rgbs - .map! { |color| color.hex } - .map! { |rgb| rgb.to_i } + rgbs.map! { |color| color.hex }.map! { |rgb| rgb.to_i } end end diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb index 1d010d0cf61..8f389cb6700 100644 --- a/app/models/reviewable.rb +++ b/app/models/reviewable.rb @@ -4,10 +4,11 @@ class Reviewable < ActiveRecord::Base TYPE_TO_BASIC_SERIALIZER = { ReviewableFlaggedPost: BasicReviewableFlaggedPostSerializer, ReviewableQueuedPost: BasicReviewableQueuedPostSerializer, - ReviewableUser: BasicReviewableUserSerializer + ReviewableUser: BasicReviewableUserSerializer, } - class UpdateConflict < StandardError; end + class UpdateConflict < StandardError + end class InvalidAction < StandardError def initialize(action_id, klass) @@ -20,9 +21,9 @@ class Reviewable < ActiveRecord::Base attr_accessor :created_new validates_presence_of :type, :status, :created_by_id belongs_to :target, polymorphic: true - belongs_to :created_by, class_name: 'User' - belongs_to :target_created_by, class_name: 'User' - belongs_to :reviewable_by_group, class_name: 'Group' + belongs_to :created_by, class_name: "User" + belongs_to :target_created_by, class_name: "User" + belongs_to :reviewable_by_group, class_name: "Group" # Optional, for filtering belongs_to :topic @@ -31,34 +32,15 @@ class Reviewable < ActiveRecord::Base has_many :reviewable_histories, dependent: :destroy has_many :reviewable_scores, -> { order(created_at: :desc) }, dependent: :destroy - enum :status, { - pending: 0, - approved: 1, - rejected: 2, - ignored: 3, - deleted: 4 - } - enum :priority, { - low: 0, - medium: 5, - high: 10 - }, scopes: false, suffix: true - enum :sensitivity, { - disabled: 0, - low: 9, - medium: 6, - high: 3 - }, scopes: false, suffix: true + enum :status, { pending: 0, approved: 1, rejected: 2, ignored: 3, deleted: 4 } + enum :priority, { low: 0, medium: 5, high: 10 }, scopes: false, suffix: true + enum :sensitivity, { disabled: 0, low: 9, medium: 6, high: 3 }, scopes: false, suffix: true - after_create do - log_history(:created, created_by) - end + after_create { log_history(:created, created_by) } - after_commit(on: :create) do - DiscourseEvent.trigger(:reviewable_created, self) - end + after_commit(on: :create) { DiscourseEvent.trigger(:reviewable_created, self) } - after_commit(on: [:create, :update]) do + after_commit(on: %i[create update]) do Jobs.enqueue(:notify_reviewable, reviewable_id: self.id) if pending? end @@ -119,14 +101,15 @@ class Reviewable < ActiveRecord::Base reviewable_by_moderator: false, potential_spam: true ) - reviewable = new( - target: target, - topic: topic, - created_by: created_by, - reviewable_by_moderator: reviewable_by_moderator, - payload: payload, - potential_spam: potential_spam - ) + reviewable = + new( + target: target, + topic: topic, + created_by: created_by, + reviewable_by_moderator: reviewable_by_moderator, + payload: payload, + potential_spam: potential_spam, + ) reviewable.created_new! if target.blank? || !Reviewable.where(target: target, type: reviewable.type).exists? @@ -147,7 +130,7 @@ class Reviewable < ActiveRecord::Base status: statuses[:pending], id: target.id, type: target.class.name, - potential_spam: potential_spam == true ? true : nil + potential_spam: potential_spam == true ? true : nil, } row = DB.query_single(<<~SQL, update_args) @@ -187,22 +170,22 @@ class Reviewable < ActiveRecord::Base meta_topic_id: nil, force_review: false ) - type_bonus = PostActionType.where(id: reviewable_score_type).pluck(:score_bonus)[0] || 0 take_action_bonus = take_action ? 5.0 : 0.0 user_accuracy_bonus = ReviewableScore.user_accuracy_bonus(user) sub_total = ReviewableScore.calculate_score(user, type_bonus, take_action_bonus) - rs = reviewable_scores.new( - user: user, - status: :pending, - reviewable_score_type: reviewable_score_type, - score: sub_total, - user_accuracy_bonus: user_accuracy_bonus, - meta_topic_id: meta_topic_id, - take_action_bonus: take_action_bonus, - created_at: created_at || Time.zone.now - ) + rs = + reviewable_scores.new( + user: user, + status: :pending, + reviewable_score_type: reviewable_score_type, + score: sub_total, + user_accuracy_bonus: user_accuracy_bonus, + meta_topic_id: meta_topic_id, + take_action_bonus: take_action_bonus, + created_at: created_at || Time.zone.now, + ) rs.reason = reason.to_s if reason rs.save! @@ -217,7 +200,7 @@ class Reviewable < ActiveRecord::Base def self.set_priorities(values) values.each do |k, v| id = priorities[k] - PluginStore.set('reviewables', "priority_#{id}", v) unless id.nil? + PluginStore.set("reviewables", "priority_#{id}", v) unless id.nil? end end @@ -225,10 +208,8 @@ class Reviewable < ActiveRecord::Base return Float::MAX if sensitivity == 0 ratio = sensitivity / sensitivities[:low].to_f - high = ( - PluginStore.get('reviewables', "priority_#{priorities[:high]}") || - typical_sensitivity - ).to_f + high = + (PluginStore.get("reviewables", "priority_#{priorities[:high]}") || typical_sensitivity).to_f # We want this to be hard to reach ((high.to_f * ratio) * scale).truncate(2) @@ -257,7 +238,7 @@ class Reviewable < ActiveRecord::Base priority ||= SiteSetting.reviewable_default_visibility id = priorities[priority] return 0.0 if id.nil? - PluginStore.get('reviewables', "priority_#{id}").to_f + PluginStore.get("reviewables", "priority_#{id}").to_f end def history @@ -269,14 +250,15 @@ class Reviewable < ActiveRecord::Base reviewable_history_type: reviewable_history_type, status: status, created_by: performed_by, - edited: edited + edited: edited, ) end def apply_review_group - return unless SiteSetting.enable_category_group_moderation? && - category.present? && - category.reviewable_by_group_id + unless SiteSetting.enable_category_group_moderation? && category.present? && + category.reviewable_by_group_id + return + end self.reviewable_by_group_id = category.reviewable_by_group_id end @@ -284,16 +266,14 @@ class Reviewable < ActiveRecord::Base def actions_for(guardian, args = nil) args ||= {} - Actions.new(self, guardian).tap do |actions| - build_actions(actions, guardian, args) - end + Actions.new(self, guardian).tap { |actions| build_actions(actions, guardian, args) } end def editable_for(guardian, args = nil) args ||= {} - EditableFields.new(self, guardian, args).tap do |fields| - build_editable_fields(fields, guardian, args) - end + EditableFields + .new(self, guardian, args) + .tap { |fields| build_editable_fields(fields, guardian, args) } end # subclasses must implement "build_actions" to list the actions they're capable of @@ -316,7 +296,7 @@ class Reviewable < ActiveRecord::Base Reviewable.transaction do increment_version!(version) changes_json = changes.as_json - changes_json.delete('version') + changes_json.delete("version") result = save log_history(:edited, performed_by, edited: changes_json) if result @@ -331,7 +311,7 @@ class Reviewable < ActiveRecord::Base args ||= {} # Support this action or any aliases aliases = self.class.action_aliases - valid = [ action_id, aliases.to_a.select { |k, v| v == action_id }.map(&:first) ].flatten + valid = [action_id, aliases.to_a.select { |k, v| v == action_id }.map(&:first)].flatten # Ensure the user has access to the action actions = actions_for(Guardian.new(performed_by), args) @@ -353,16 +333,14 @@ class Reviewable < ActiveRecord::Base recalculate_score if result.recalculate_score end - if result && result.after_commit - result.after_commit.call - end + result.after_commit.call if result && result.after_commit if update_count || result.remove_reviewable_ids.present? Jobs.enqueue( :notify_reviewable, reviewable_id: self.id, performing_username: performed_by.username, - updated_reviewable_ids: result.remove_reviewable_ids + updated_reviewable_ids: result.remove_reviewable_ids, ) end @@ -380,7 +358,7 @@ class Reviewable < ActiveRecord::Base reviewable_scores.pending.update_all( status: score_status, reviewed_by_id: performed_by.id, - reviewed_at: Time.zone.now + reviewed_at: Time.zone.now, ) end @@ -391,40 +369,45 @@ class Reviewable < ActiveRecord::Base Discourse.deprecate( "Reviewable#post_options is deprecated. Please use #payload instead.", output_in_test: true, - drop_from: '2.9.0', + drop_from: "2.9.0", ) end def self.bulk_perform_targets(performed_by, action, type, target_ids, args = nil) args ||= {} - viewable_by(performed_by).where(type: type, target_id: target_ids).each do |r| - r.perform(performed_by, action, args) - end + viewable_by(performed_by) + .where(type: type, target_id: target_ids) + .each { |r| r.perform(performed_by, action, args) } end def self.viewable_by(user, order: nil, preload: true) return none unless user.present? - result = self.order(order || 'reviewables.score desc, reviewables.created_at desc') + result = self.order(order || "reviewables.score desc, reviewables.created_at desc") if preload - result = result.includes( - { created_by: :user_stat }, - :topic, - :target, - :target_created_by, - :reviewable_histories - ).includes(reviewable_scores: { user: :user_stat, meta_topic: :posts }) + result = + result.includes( + { created_by: :user_stat }, + :topic, + :target, + :target_created_by, + :reviewable_histories, + ).includes(reviewable_scores: { user: :user_stat, meta_topic: :posts }) end return result if user.admin? - group_ids = SiteSetting.enable_category_group_moderation? ? user.group_users.pluck(:group_id) : [] + group_ids = + SiteSetting.enable_category_group_moderation? ? user.group_users.pluck(:group_id) : [] result.where( - '(reviewables.reviewable_by_moderator AND :staff) OR (reviewables.reviewable_by_group_id IN (:group_ids))', + "(reviewables.reviewable_by_moderator AND :staff) OR (reviewables.reviewable_by_group_id IN (:group_ids))", staff: user.staff?, - group_ids: group_ids - ).where("reviewables.category_id IS NULL OR reviewables.category_id IN (?)", Guardian.new(user).allowed_category_ids) + group_ids: group_ids, + ).where( + "reviewables.category_id IS NULL OR reviewables.category_id IN (?)", + Guardian.new(user).allowed_category_ids, + ) end def self.pending_count(user) @@ -454,16 +437,17 @@ class Reviewable < ActiveRecord::Base preload: true, include_claimed_by_others: true ) - order = case sort_order - when 'score_asc' - 'reviewables.score ASC, reviewables.created_at DESC' - when 'created_at' - 'reviewables.created_at DESC, reviewables.score DESC' - when 'created_at_asc' - 'reviewables.created_at ASC, reviewables.score DESC' - else - 'reviewables.score DESC, reviewables.created_at DESC' - end + order = + case sort_order + when "score_asc" + "reviewables.score ASC, reviewables.created_at DESC" + when "created_at" + "reviewables.created_at DESC, reviewables.score DESC" + when "created_at_asc" + "reviewables.created_at ASC, reviewables.score DESC" + else + "reviewables.score DESC, reviewables.created_at DESC" + end if username.present? user_id = User.find_by_username(username)&.id @@ -475,9 +459,9 @@ class Reviewable < ActiveRecord::Base result = by_status(result, status) result = result.where(id: ids) if ids - result = result.where('reviewables.type = ?', type) if type - result = result.where('reviewables.category_id = ?', category_id) if category_id - result = result.where('reviewables.topic_id = ?', topic_id) if topic_id + result = result.where("reviewables.type = ?", type) if type + result = result.where("reviewables.category_id = ?", category_id) if category_id + result = result.where("reviewables.topic_id = ?", topic_id) if topic_id result = result.where("reviewables.created_at >= ?", from_date) if from_date result = result.where("reviewables.created_at <= ?", to_date) if to_date @@ -485,7 +469,7 @@ class Reviewable < ActiveRecord::Base reviewed_by_id = User.find_by_username(reviewed_by)&.id return none if reviewed_by_id.nil? - result = result.joins(<<~SQL + result = result.joins(<<~SQL) INNER JOIN( SELECT reviewable_id FROM reviewable_histories @@ -493,7 +477,6 @@ class Reviewable < ActiveRecord::Base status <> #{statuses[:pending]} AND created_by_id = #{reviewed_by_id} ) AS rh ON rh.reviewable_id = reviewables.id SQL - ) end min_score = min_score_for_priority(priority) @@ -505,28 +488,31 @@ class Reviewable < ActiveRecord::Base end if !custom_filters.empty? - result = custom_filters.reduce(result) do |memo, filter| - key = filter.first - filter_query = filter.last + result = + custom_filters.reduce(result) do |memo, filter| + key = filter.first + filter_query = filter.last - next(memo) unless additional_filters[key] - filter_query.call(result, additional_filters[key]) - end + next(memo) unless additional_filters[key] + filter_query.call(result, additional_filters[key]) + end end # If a reviewable doesn't have a target, allow us to filter on who created that reviewable. if user_id - result = result.where( - "(reviewables.target_created_by_id IS NULL AND reviewables.created_by_id = :user_id) + result = + result.where( + "(reviewables.target_created_by_id IS NULL AND reviewables.created_by_id = :user_id) OR (reviewables.target_created_by_id = :user_id)", - user_id: user_id - ) + user_id: user_id, + ) end if !include_claimed_by_others - result = result - .joins("LEFT JOIN reviewable_claimed_topics rct ON reviewables.topic_id = rct.topic_id") - .where("rct.user_id IS NULL OR rct.user_id = ?", user.id) + result = + result.joins( + "LEFT JOIN reviewable_claimed_topics rct ON reviewables.topic_id = rct.topic_id", + ).where("rct.user_id IS NULL OR rct.user_id = ?", user.id) end result = result.limit(limit) if limit result = result.offset(offset) if offset @@ -536,10 +522,7 @@ class Reviewable < ActiveRecord::Base def self.unseen_list_for(user, preload: true, limit: nil) results = list_for(user, preload: preload, limit: limit, include_claimed_by_others: false) if user.last_seen_reviewable_id - results = results.where( - "reviewables.id > ?", - user.last_seen_reviewable_id - ) + results = results.where("reviewables.id > ?", user.last_seen_reviewable_id) end results end @@ -584,7 +567,8 @@ class Reviewable < ActiveRecord::Base end def self.count_by_date(start_date, end_date, category_id = nil, include_subcategories = false) - query = scores_with_topics.where('reviewable_scores.created_at BETWEEN ? AND ?', start_date, end_date) + query = + scores_with_topics.where("reviewable_scores.created_at BETWEEN ? AND ?", start_date, end_date) if category_id if include_subcategories @@ -596,7 +580,7 @@ class Reviewable < ActiveRecord::Base query .group("date(reviewable_scores.created_at)") - .order('date(reviewable_scores.created_at)') + .order("date(reviewable_scores.created_at)") .count end @@ -634,12 +618,13 @@ class Reviewable < ActiveRecord::Base RETURNING score SQL - result = DB.query( - sql, - id: self.id, - pending: ReviewableScore.statuses[:pending], - agreed: ReviewableScore.statuses[:agreed] - ) + result = + DB.query( + sql, + id: self.id, + pending: ReviewableScore.statuses[:pending], + agreed: ReviewableScore.statuses[:agreed], + ) # Update topic score sql = <<~SQL @@ -657,7 +642,7 @@ class Reviewable < ActiveRecord::Base sql, topic_id: topic_id, pending: self.class.statuses[:pending], - approved: self.class.statuses[:approved] + approved: self.class.statuses[:approved], ) self.score = result[0].score @@ -668,44 +653,47 @@ class Reviewable < ActiveRecord::Base end def delete_user_actions(actions, require_reject_reason: false) - reject = actions.add_bundle( - 'reject_user', - icon: 'user-times', - label: 'reviewables.actions.reject_user.title' - ) + reject = + actions.add_bundle( + "reject_user", + icon: "user-times", + label: "reviewables.actions.reject_user.title", + ) actions.add(:delete_user, bundle: reject) do |a| - a.icon = 'user-times' + a.icon = "user-times" a.label = "reviewables.actions.reject_user.delete.title" a.require_reject_reason = require_reject_reason a.description = "reviewables.actions.reject_user.delete.description" end actions.add(:delete_user_block, bundle: reject) do |a| - a.icon = 'ban' + a.icon = "ban" a.label = "reviewables.actions.reject_user.block.title" a.require_reject_reason = require_reject_reason a.description = "reviewables.actions.reject_user.block.description" end end -protected + protected def increment_version!(version = nil) version_result = nil if version - version_result = DB.query_single( - "UPDATE reviewables SET version = version + 1 WHERE id = :id AND version = :version RETURNING version", - version: version, - id: self.id - ) + version_result = + DB.query_single( + "UPDATE reviewables SET version = version + 1 WHERE id = :id AND version = :version RETURNING version", + version: version, + id: self.id, + ) else # We didn't supply a version to update safely, so just increase it - version_result = DB.query_single( - "UPDATE reviewables SET version = version + 1 WHERE id = :id RETURNING version", - id: self.id - ) + version_result = + DB.query_single( + "UPDATE reviewables SET version = version + 1 WHERE id = :id RETURNING version", + id: self.id, + ) end if version_result && version_result[0] @@ -725,10 +713,10 @@ protected end end -private + private def update_flag_stats(status:, user_ids:) - return unless [:agreed, :disagreed, :ignored].include?(status) + return unless %i[agreed disagreed ignored].include?(status) # Don't count self-flags user_ids -= [post&.user_id] @@ -741,7 +729,8 @@ private RETURNING user_id, flags_agreed + flags_disagreed + flags_ignored AS total SQL - user_ids = result.select { |r| r.total > Jobs::TruncateUserFlagStats.truncate_to }.map(&:user_id) + user_ids = + result.select { |r| r.total > Jobs::TruncateUserFlagStats.truncate_to }.map(&:user_id) return if user_ids.blank? Jobs.enqueue(:truncate_user_flag_stats, user_ids: user_ids) diff --git a/app/models/reviewable_claimed_topic.rb b/app/models/reviewable_claimed_topic.rb index dc54c4abf14..a4b81864c70 100644 --- a/app/models/reviewable_claimed_topic.rb +++ b/app/models/reviewable_claimed_topic.rb @@ -7,10 +7,11 @@ class ReviewableClaimedTopic < ActiveRecord::Base def self.claimed_hash(topic_ids) result = {} - if SiteSetting.reviewable_claiming != 'disabled' - ReviewableClaimedTopic.where(topic_id: topic_ids).includes(:user).each do |rct| - result[rct.topic_id] = rct.user - end + if SiteSetting.reviewable_claiming != "disabled" + ReviewableClaimedTopic + .where(topic_id: topic_ids) + .includes(:user) + .each { |rct| result[rct.topic_id] = rct.user } end result end diff --git a/app/models/reviewable_flagged_post.rb b/app/models/reviewable_flagged_post.rb index 44011bf6fbf..8015cd6e127 100644 --- a/app/models/reviewable_flagged_post.rb +++ b/app/models/reviewable_flagged_post.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true class ReviewableFlaggedPost < Reviewable - scope :pending_and_default_visible, -> { - pending.default_visible - } + scope :pending_and_default_visible, -> { pending.default_visible } # Penalties are handled by the modal after the action is performed def self.action_aliases - { agree_and_keep_hidden: :agree_and_keep, + { + agree_and_keep_hidden: :agree_and_keep, agree_and_silence: :agree_and_keep, agree_and_suspend: :agree_and_keep, - disagree_and_restore: :disagree } + disagree_and_restore: :disagree, + } end def self.counts_for(posts) @@ -43,68 +43,84 @@ class ReviewableFlaggedPost < Reviewable return unless pending? return if post.blank? - agree = actions.add_bundle("#{id}-agree", icon: 'thumbs-up', label: 'reviewables.actions.agree.title') + agree = + actions.add_bundle("#{id}-agree", icon: "thumbs-up", label: "reviewables.actions.agree.title") if !post.user_deleted? && !post.hidden? - build_action(actions, :agree_and_hide, icon: 'far-eye-slash', bundle: agree) + build_action(actions, :agree_and_hide, icon: "far-eye-slash", bundle: agree) end if post.hidden? - build_action(actions, :agree_and_keep_hidden, icon: 'thumbs-up', bundle: agree) + build_action(actions, :agree_and_keep_hidden, icon: "thumbs-up", bundle: agree) else - build_action(actions, :agree_and_keep, icon: 'thumbs-up', bundle: agree) + build_action(actions, :agree_and_keep, icon: "thumbs-up", bundle: agree) end if guardian.can_suspend?(target_created_by) - build_action(actions, :agree_and_suspend, icon: 'ban', bundle: agree, client_action: 'suspend') - build_action(actions, :agree_and_silence, icon: 'microphone-slash', bundle: agree, client_action: 'silence') + build_action( + actions, + :agree_and_suspend, + icon: "ban", + bundle: agree, + client_action: "suspend", + ) + build_action( + actions, + :agree_and_silence, + icon: "microphone-slash", + bundle: agree, + client_action: "silence", + ) end - if post.user_deleted? - build_action(actions, :agree_and_restore, icon: 'far-eye', bundle: agree) - end + build_action(actions, :agree_and_restore, icon: "far-eye", bundle: agree) if post.user_deleted? if post.hidden? - build_action(actions, :disagree_and_restore, icon: 'thumbs-down') + build_action(actions, :disagree_and_restore, icon: "thumbs-down") else - build_action(actions, :disagree, icon: 'thumbs-down') + build_action(actions, :disagree, icon: "thumbs-down") end - build_action(actions, :ignore, icon: 'external-link-alt') + build_action(actions, :ignore, icon: "external-link-alt") - if potential_spam? && guardian.can_delete_user?(target_created_by) - delete_user_actions(actions) - end + delete_user_actions(actions) if potential_spam? && guardian.can_delete_user?(target_created_by) if guardian.can_delete_post_or_topic?(post) - delete = actions.add_bundle("#{id}-delete", icon: "far-trash-alt", label: "reviewables.actions.delete.title") - build_action(actions, :delete_and_ignore, icon: 'external-link-alt', bundle: delete) + delete = + actions.add_bundle( + "#{id}-delete", + icon: "far-trash-alt", + label: "reviewables.actions.delete.title", + ) + build_action(actions, :delete_and_ignore, icon: "external-link-alt", bundle: delete) if post.reply_count > 0 build_action( actions, :delete_and_ignore_replies, - icon: 'external-link-alt', + icon: "external-link-alt", confirm: true, - bundle: delete + bundle: delete, ) end - build_action(actions, :delete_and_agree, icon: 'thumbs-up', bundle: delete) + build_action(actions, :delete_and_agree, icon: "thumbs-up", bundle: delete) if post.reply_count > 0 build_action( actions, :delete_and_agree_replies, - icon: 'external-link-alt', + icon: "external-link-alt", bundle: delete, - confirm: true + confirm: true, ) end end end def perform_ignore(performed_by, args) - actions = PostAction.active - .where(post_id: target_id) - .where(post_action_type_id: PostActionType.notify_flag_type_ids) + actions = + PostAction + .active + .where(post_id: target_id) + .where(post_action_type_id: PostActionType.notify_flag_type_ids) actions.each do |action| action.deferred_at = Time.zone.now @@ -142,9 +158,7 @@ class ReviewableFlaggedPost < Reviewable def perform_delete_user_block(performed_by, args) delete_options = delete_opts - if Rails.env.production? - delete_options.merge!(block_email: true, block_ip: true) - end + delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production? UserDestroyer.new(performed_by).destroy(post.user, delete_options) @@ -152,15 +166,11 @@ class ReviewableFlaggedPost < Reviewable end def perform_agree_and_hide(performed_by, args) - agree(performed_by, args) do |pa| - post.hide!(pa.post_action_type_id) - end + agree(performed_by, args) { |pa| post.hide!(pa.post_action_type_id) } end def perform_agree_and_restore(performed_by, args) - agree(performed_by, args) do - PostDestroyer.new(performed_by, post).recover - end + agree(performed_by, args) { PostDestroyer.new(performed_by, post).recover } end def perform_disagree(performed_by, args) @@ -172,7 +182,8 @@ class ReviewableFlaggedPost < Reviewable PostActionType.notify_flag_type_ids end - actions = PostAction.active.where(post_id: target_id).where(post_action_type_id: action_type_ids) + actions = + PostAction.active.where(post_id: target_id).where(post_action_type_id: action_type_ids) actions.each do |action| action.disagreed_at = Time.zone.now @@ -234,12 +245,14 @@ class ReviewableFlaggedPost < Reviewable result end -protected + protected def agree(performed_by, args) - actions = PostAction.active - .where(post_id: target_id) - .where(post_action_type_id: PostActionType.notify_flag_types.values) + actions = + PostAction + .active + .where(post_id: target_id) + .where(post_action_type_id: PostActionType.notify_flag_types.values) trigger_spam = false actions.each do |action| @@ -270,7 +283,15 @@ protected end end - def build_action(actions, id, icon:, button_class: nil, bundle: nil, client_action: nil, confirm: false) + def build_action( + actions, + id, + icon:, + button_class: nil, + bundle: nil, + client_action: nil, + confirm: false + ) actions.add(id, bundle: bundle) do |action| prefix = "reviewables.actions.#{id}" action.icon = icon @@ -284,15 +305,14 @@ protected def unassign_topic(performed_by, post) topic = post.topic - return unless topic && performed_by && SiteSetting.reviewable_claiming != 'disabled' + return unless topic && performed_by && SiteSetting.reviewable_claiming != "disabled" ReviewableClaimedTopic.where(topic_id: topic.id).delete_all - topic.reviewables.find_each do |reviewable| - reviewable.log_history(:unclaimed, performed_by) - end + topic.reviewables.find_each { |reviewable| reviewable.log_history(:unclaimed, performed_by) } user_ids = User.staff.pluck(:id) - if SiteSetting.enable_category_group_moderation? && group_id = topic.category&.reviewable_by_group_id.presence + if SiteSetting.enable_category_group_moderation? && + group_id = topic.category&.reviewable_by_group_id.presence user_ids.concat(GroupUser.where(group_id: group_id).pluck(:user_id)) user_ids.uniq! end @@ -302,7 +322,7 @@ protected MessageBus.publish("/reviewable_claimed", data, user_ids: user_ids) end -private + private def delete_opts { @@ -310,7 +330,7 @@ private prepare_for_destroy: true, block_urls: true, delete_as_spammer: true, - context: "review" + context: "review", } end @@ -327,8 +347,8 @@ private message_type: "flags_disagreed", message_options: { flagged_post_raw_content: post.raw, - url: post.url - } + url: post.url, + }, ) end end diff --git a/app/models/reviewable_history.rb b/app/models/reviewable_history.rb index c01d41a9357..b5df88cb421 100644 --- a/app/models/reviewable_history.rb +++ b/app/models/reviewable_history.rb @@ -2,24 +2,12 @@ class ReviewableHistory < ActiveRecord::Base belongs_to :reviewable - belongs_to :created_by, class_name: 'User' + belongs_to :created_by, class_name: "User" - enum status: { - pending: 0, - approved: 1, - rejected: 2, - ignored: 3, - deleted: 4 - } + enum status: { pending: 0, approved: 1, rejected: 2, ignored: 3, deleted: 4 } alias_attribute :type, :reviewable_history_type - enum type: { - created: 0, - transitioned: 1, - edited: 2, - claimed: 3, - unclaimed: 4 - } + enum type: { created: 0, transitioned: 1, edited: 2, claimed: 3, unclaimed: 4 } end # == Schema Information diff --git a/app/models/reviewable_post.rb b/app/models/reviewable_post.rb index 983a96c0549..f9fb8afb37c 100644 --- a/app/models/reviewable_post.rb +++ b/app/models/reviewable_post.rb @@ -9,7 +9,10 @@ class ReviewablePost < Reviewable return unless SiteSetting.review_every_post return if post.post_type != Post.types[:regular] || post.topic.private_message? return if Reviewable.pending.where(target: post).exists? - return if created_or_edited_by.bot? || created_or_edited_by.staff? || created_or_edited_by.has_trust_level?(TrustLevel[4]) + if created_or_edited_by.bot? || created_or_edited_by.staff? || + created_or_edited_by.has_trust_level?(TrustLevel[4]) + return + end system_user = Discourse.system_user needs_review!( @@ -17,13 +20,9 @@ class ReviewablePost < Reviewable topic: post.topic, created_by: system_user, reviewable_by_moderator: true, - potential_spam: false + potential_spam: false, ).tap do |reviewable| - reviewable.add_score( - system_user, - ReviewableScore.types[:needs_approval], - force_review: true - ) + reviewable.add_score(system_user, ReviewableScore.types[:needs_approval], force_review: true) end end @@ -31,26 +30,41 @@ class ReviewablePost < Reviewable return unless pending? if post.trashed? && guardian.can_recover_post?(post) - build_action(actions, :approve_and_restore, icon: 'check') + build_action(actions, :approve_and_restore, icon: "check") elsif post.hidden? - build_action(actions, :approve_and_unhide, icon: 'check') + build_action(actions, :approve_and_unhide, icon: "check") else - build_action(actions, :approve, icon: 'check') + build_action(actions, :approve, icon: "check") end - reject = actions.add_bundle( - "#{id}-reject", icon: 'times', label: 'reviewables.actions.reject.bundle_title' - ) + reject = + actions.add_bundle( + "#{id}-reject", + icon: "times", + label: "reviewables.actions.reject.bundle_title", + ) if post.trashed? - build_action(actions, :reject_and_keep_deleted, icon: 'trash-alt', bundle: reject) + build_action(actions, :reject_and_keep_deleted, icon: "trash-alt", bundle: reject) elsif guardian.can_delete_post_or_topic?(post) - build_action(actions, :reject_and_delete, icon: 'trash-alt', bundle: reject) + build_action(actions, :reject_and_delete, icon: "trash-alt", bundle: reject) end if guardian.can_suspend?(target_created_by) - build_action(actions, :reject_and_suspend, icon: 'ban', bundle: reject, client_action: 'suspend') - build_action(actions, :reject_and_silence, icon: 'microphone-slash', bundle: reject, client_action: 'silence') + build_action( + actions, + :reject_and_suspend, + icon: "ban", + bundle: reject, + client_action: "suspend", + ) + build_action( + actions, + :reject_and_silence, + icon: "microphone-slash", + bundle: reject, + client_action: "silence", + ) end end @@ -90,7 +104,15 @@ class ReviewablePost < Reviewable @post ||= (target || Post.with_deleted.find_by(id: target_id)) end - def build_action(actions, id, icon:, button_class: nil, bundle: nil, client_action: nil, confirm: false) + def build_action( + actions, + id, + icon:, + button_class: nil, + bundle: nil, + client_action: nil, + confirm: false + ) actions.add(id, bundle: bundle) do |action| prefix = "reviewables.actions.#{id}" action.icon = icon @@ -103,7 +125,7 @@ class ReviewablePost < Reviewable end def successful_transition(to_state, recalculate_score: true) - create_result(:success, to_state) do |result| + create_result(:success, to_state) do |result| result.recalculate_score = recalculate_score result.update_flag_stats = { status: to_state, user_ids: [created_by_id] } end diff --git a/app/models/reviewable_priority_setting.rb b/app/models/reviewable_priority_setting.rb index 42afd497758..04547837a45 100644 --- a/app/models/reviewable_priority_setting.rb +++ b/app/models/reviewable_priority_setting.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ReviewablePrioritySetting < EnumSiteSetting - def self.valid_value?(val) values.any? { |v| v[:value].to_s == val.to_s } end @@ -15,5 +14,4 @@ class ReviewablePrioritySetting < EnumSiteSetting def self.translate_names? false end - end diff --git a/app/models/reviewable_queued_post.rb b/app/models/reviewable_queued_post.rb index 928b841060c..ecf6297cdcd 100644 --- a/app/models/reviewable_queued_post.rb +++ b/app/models/reviewable_queued_post.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true class ReviewableQueuedPost < Reviewable - after_create do # Backwards compatibility, new code should listen for `reviewable_created` DiscourseEvent.trigger(:queued_post_created, self) end after_save do - if saved_change_to_payload? && self.status == Reviewable.statuses[:pending] && self.payload&.[]('raw').present? - upload_ids = Upload.extract_upload_ids(self.payload['raw']) + if saved_change_to_payload? && self.status == Reviewable.statuses[:pending] && + self.payload&.[]("raw").present? + upload_ids = Upload.extract_upload_ids(self.payload["raw"]) UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) end end @@ -17,18 +17,16 @@ class ReviewableQueuedPost < Reviewable after_commit :compute_user_stats, only: %i[create update] def build_actions(actions, guardian, args) - unless approved? - if topic&.closed? actions.add(:approve_post_closed) do |a| - a.icon = 'check' + a.icon = "check" a.label = "reviewables.actions.approve_post.title" a.confirm_message = "reviewables.actions.approve_post.confirm_closed" end else actions.add(:approve_post) do |a| - a.icon = 'check' + a.icon = "check" a.label = "reviewables.actions.approve_post.title" end end @@ -36,32 +34,28 @@ class ReviewableQueuedPost < Reviewable unless rejected? actions.add(:reject_post) do |a| - a.icon = 'times' + a.icon = "times" a.label = "reviewables.actions.reject_post.title" end end - if pending? && guardian.can_delete_user?(created_by) - delete_user_actions(actions) - end + delete_user_actions(actions) if pending? && guardian.can_delete_user?(created_by) actions.add(:delete) if guardian.can_delete?(self) end def build_editable_fields(fields, guardian, args) - # We can edit category / title if it's a new topic if topic_id.blank? - # Only staff can edit category for now, since in theory a category group reviewer could # post in a category they don't have access to. - fields.add('category_id', :category) if guardian.is_staff? + fields.add("category_id", :category) if guardian.is_staff? - fields.add('payload.title', :text) - fields.add('payload.tags', :tags) + fields.add("payload.title", :text) + fields.add("payload.tags", :tags) end - fields.add('payload.raw', :editor) + fields.add("payload.raw", :editor) end def create_options @@ -74,12 +68,13 @@ class ReviewableQueuedPost < Reviewable def perform_approve_post(performed_by, args) created_post = nil - opts = create_options.merge( - skip_validations: true, - skip_jobs: true, - skip_events: true, - skip_guardian: true - ) + opts = + create_options.merge( + skip_validations: true, + skip_jobs: true, + skip_events: true, + skip_guardian: true, + ) opts.merge!(guardian: Guardian.new(performed_by)) if performed_by.staff? creator = PostCreator.new(created_by, opts) @@ -90,9 +85,7 @@ class ReviewableQueuedPost < Reviewable end self.target = created_post - if topic_id.nil? - self.topic_id = created_post.topic_id - end + self.topic_id = created_post.topic_id if topic_id.nil? save UserSilencer.unsilence(created_by, performed_by) if created_by.silenced? @@ -107,17 +100,17 @@ class ReviewableQueuedPost < Reviewable user_id: created_by.id, data: { post_url: created_post.url }.to_json, topic_id: created_post.topic_id, - post_number: created_post.post_number + post_number: created_post.post_number, ) create_result(:success, :approved) do |result| result.created_post = created_post # Do sidekiq work outside of the transaction - result.after_commit = -> { + result.after_commit = -> do creator.enqueue_jobs creator.trigger_after_events - } + end end end @@ -145,9 +138,7 @@ class ReviewableQueuedPost < Reviewable def perform_delete_user_block(performed_by, args) delete_options = delete_opts - if Rails.env.production? - delete_options.merge!(block_email: true, block_ip: true) - end + delete_options.merge!(block_email: true, block_ip: true) if Rails.env.production? delete_user(performed_by, delete_options) end @@ -162,10 +153,10 @@ class ReviewableQueuedPost < Reviewable def delete_opts { - context: I18n.t('reviewables.actions.delete_user.reason'), + context: I18n.t("reviewables.actions.delete_user.reason"), delete_posts: true, block_urls: true, - delete_as_spammer: true + delete_as_spammer: true, } end @@ -175,8 +166,7 @@ class ReviewableQueuedPost < Reviewable end def status_changed_from_or_to_pending? - saved_change_to_id?(from: nil) && pending? || - saved_change_to_status?(from: "pending") + saved_change_to_id?(from: nil) && pending? || saved_change_to_status?(from: "pending") end end diff --git a/app/models/reviewable_score.rb b/app/models/reviewable_score.rb index 2d650b41317..774a5385333 100644 --- a/app/models/reviewable_score.rb +++ b/app/models/reviewable_score.rb @@ -3,22 +3,15 @@ class ReviewableScore < ActiveRecord::Base belongs_to :reviewable belongs_to :user - belongs_to :reviewed_by, class_name: 'User' - belongs_to :meta_topic, class_name: 'Topic' + belongs_to :reviewed_by, class_name: "User" + belongs_to :meta_topic, class_name: "Topic" - enum status: { - pending: 0, - agreed: 1, - disagreed: 2, - ignored: 3 - } + enum status: { pending: 0, agreed: 1, disagreed: 2, ignored: 3 } # To keep things simple the types correspond to `PostActionType` for backwards # compatibility, but we can add extra reasons for scores. def self.types - @types ||= PostActionType.flag_types.merge( - needs_approval: 9 - ) + @types ||= PostActionType.flag_types.merge(needs_approval: 9) end # When extending post action flags, we need to call this method in order to @@ -31,17 +24,11 @@ class ReviewableScore < ActiveRecord::Base def self.add_new_types(type_names) next_id = types.values.max + 1 - type_names.each_with_index do |name, idx| - @types[name] = next_id + idx - end + type_names.each_with_index { |name, idx| @types[name] = next_id + idx } end def self.score_transitions - { - approved: statuses[:agreed], - rejected: statuses[:disagreed], - ignored: statuses[:ignored] - } + { approved: statuses[:agreed], rejected: statuses[:disagreed], ignored: statuses[:ignored] } end def score_type @@ -88,22 +75,21 @@ class ReviewableScore < ActiveRecord::Base bottom = positive_accuracy ? accuracy_axis : 0.0 top = positive_accuracy ? 1.0 : accuracy_axis - absolute_distance = positive_accuracy ? - percent_correct - bottom : - top - percent_correct + absolute_distance = positive_accuracy ? percent_correct - bottom : top - percent_correct axis_distance_multiplier = 1.0 / (top - bottom) positivity_multiplier = positive_accuracy ? 1.0 : -1.0 - (absolute_distance * axis_distance_multiplier * positivity_multiplier * (Math.log(total, 4) * 5.0)) - .round(2) + ( + absolute_distance * axis_distance_multiplier * positivity_multiplier * + (Math.log(total, 4) * 5.0) + ).round(2) end def reviewable_conversation return if meta_topic.blank? Reviewable::Conversation.new(meta_topic) end - end # == Schema Information diff --git a/app/models/reviewable_sensitivity_setting.rb b/app/models/reviewable_sensitivity_setting.rb index c6fa405c3d6..5eb60bed4ef 100644 --- a/app/models/reviewable_sensitivity_setting.rb +++ b/app/models/reviewable_sensitivity_setting.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ReviewableSensitivitySetting < EnumSiteSetting - def self.valid_value?(val) values.any? { |v| v[:value].to_s == val.to_s } end @@ -15,5 +14,4 @@ class ReviewableSensitivitySetting < EnumSiteSetting def self.translate_names? false end - end diff --git a/app/models/reviewable_user.rb b/app/models/reviewable_user.rb index c885c8718c6..d7d2d8da098 100644 --- a/app/models/reviewable_user.rb +++ b/app/models/reviewable_user.rb @@ -1,12 +1,8 @@ # frozen_string_literal: true class ReviewableUser < Reviewable - def self.create_for(user) - create( - created_by_id: Discourse.system_user.id, - target: user - ) + create(created_by_id: Discourse.system_user.id, target: user) end def build_actions(actions, guardian, args) @@ -14,7 +10,7 @@ class ReviewableUser < Reviewable if guardian.can_approve?(target) actions.add(:approve_user) do |a| - a.icon = 'user-plus' + a.icon = "user-plus" a.label = "reviewables.actions.approve_user.title" end end @@ -29,11 +25,7 @@ class ReviewableUser < Reviewable DiscourseEvent.trigger(:user_approved, target) if args[:send_email] != false && SiteSetting.must_approve_users? - Jobs.enqueue( - :critical_user_email, - type: "signup_after_approval", - user_id: target.id - ) + Jobs.enqueue(:critical_user_email, type: "signup_after_approval", user_id: target.id) end StaffActionLogger.new(performed_by).log_user_approve(target) @@ -52,11 +44,9 @@ class ReviewableUser < Reviewable if args[:send_email] && SiteSetting.must_approve_users? # Execute job instead of enqueue because user has to exists to send email - Jobs::CriticalUserEmail.new.execute({ - type: :signup_after_reject, - user_id: target.id, - reject_reason: self.reject_reason - }) + Jobs::CriticalUserEmail.new.execute( + { type: :signup_after_reject, user_id: target.id, reject_reason: self.reject_reason }, + ) end delete_args = {} @@ -95,7 +85,7 @@ class ReviewableUser < Reviewable end def is_a_suspect_user? - reviewable_scores.any? { |rs| rs.reason == 'suspect_user' } + reviewable_scores.any? { |rs| rs.reason == "suspect_user" } end end diff --git a/app/models/s3_region_site_setting.rb b/app/models/s3_region_site_setting.rb index bf7d36740e7..e7b0c2756cd 100644 --- a/app/models/s3_region_site_setting.rb +++ b/app/models/s3_region_site_setting.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class S3RegionSiteSetting < EnumSiteSetting - def self.valid_value?(val) valid_values.include? val end @@ -11,29 +10,29 @@ class S3RegionSiteSetting < EnumSiteSetting end def self.valid_values - [ - 'ap-northeast-1', - 'ap-northeast-2', - 'ap-east-1', - 'ap-south-1', - 'ap-southeast-1', - 'ap-southeast-2', - 'ca-central-1', - 'cn-north-1', - 'cn-northwest-1', - 'eu-central-1', - 'eu-north-1', - 'eu-south-1', - 'eu-west-1', - 'eu-west-2', - 'eu-west-3', - 'sa-east-1', - 'us-east-1', - 'us-east-2', - 'us-gov-east-1', - 'us-gov-west-1', - 'us-west-1', - 'us-west-2', + %w[ + ap-northeast-1 + ap-northeast-2 + ap-east-1 + ap-south-1 + ap-southeast-1 + ap-southeast-2 + ca-central-1 + cn-north-1 + cn-northwest-1 + eu-central-1 + eu-north-1 + eu-south-1 + eu-west-1 + eu-west-2 + eu-west-3 + sa-east-1 + us-east-1 + us-east-2 + us-gov-east-1 + us-gov-west-1 + us-west-1 + us-west-2 ] end diff --git a/app/models/screened_email.rb b/app/models/screened_email.rb index 3f8a604d9b1..d86fa0a0c58 100644 --- a/app/models/screened_email.rb +++ b/app/models/screened_email.rb @@ -5,7 +5,6 @@ # (or some other form) matches a ScreenedEmail record, an action can be # performed based on the action_type. class ScreenedEmail < ActiveRecord::Base - include ScreeningModel default_action :block @@ -19,11 +18,9 @@ class ScreenedEmail < ActiveRecord::Base end def self.canonical(email) - name, domain = email.split('@', 2) - name = name.gsub(/\+.*/, '') - if ['gmail.com', 'googlemail.com'].include?(domain.downcase) - name = name.gsub('.', '') - end + name, domain = email.split("@", 2) + name = name.gsub(/\+.*/, "") + name = name.gsub(".", "") if %w[gmail.com googlemail.com].include?(domain.downcase) "#{name}@#{domain}".downcase end @@ -33,18 +30,21 @@ class ScreenedEmail < ActiveRecord::Base end def self.should_block?(email) - email = canonical(email) screened_emails = ScreenedEmail.order(created_at: :desc).limit(100) distances = {} - screened_emails.each { |se| distances[se.email] = levenshtein(se.email.downcase, email.downcase) } + screened_emails.each do |se| + distances[se.email] = levenshtein(se.email.downcase, email.downcase) + end max_distance = SiteSetting.levenshtein_distance_spammer_emails - screened_email = screened_emails.select { |se| distances[se.email] <= max_distance } - .sort { |se| distances[se.email] } - .first + screened_email = + screened_emails + .select { |se| distances[se.email] <= max_distance } + .sort { |se| distances[se.email] } + .first screened_email.record_match! if screened_email @@ -53,26 +53,19 @@ class ScreenedEmail < ActiveRecord::Base def self.levenshtein(first, second) matrix = [(0..first.length).to_a] - (1..second.length).each do |j| - matrix << [j] + [0] * (first.length) - end + (1..second.length).each { |j| matrix << [j] + [0] * (first.length) } (1..second.length).each do |i| (1..first.length).each do |j| if first[j - 1] == second[i - 1] matrix[i][j] = matrix[i - 1][j - 1] else - matrix[i][j] = [ - matrix[i - 1][j], - matrix[i][j - 1], - matrix[i - 1][j - 1], - ].min + 1 + matrix[i][j] = [matrix[i - 1][j], matrix[i][j - 1], matrix[i - 1][j - 1]].min + 1 end end end matrix.last.last end - end # == Schema Information diff --git a/app/models/screened_ip_address.rb b/app/models/screened_ip_address.rb index 13bf4a90922..ed6cc3d800b 100644 --- a/app/models/screened_ip_address.rb +++ b/app/models/screened_ip_address.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true -require 'screening_model' +require "screening_model" # A ScreenedIpAddress record represents an IP address or subnet that is being watched, # and possibly blocked from creating accounts. class ScreenedIpAddress < ActiveRecord::Base - include ScreeningModel default_action :block @@ -25,7 +24,8 @@ class ScreenedIpAddress < ActiveRecord::Base ] def self.watch(ip_address, opts = {}) - match_for_ip_address(ip_address) || create(opts.slice(:action_type).merge(ip_address: ip_address)) + match_for_ip_address(ip_address) || + create(opts.slice(:action_type).merge(ip_address: ip_address)) end def check_for_match @@ -60,8 +60,8 @@ class ScreenedIpAddress < ActiveRecord::Base write_attribute(:ip_address, v) - # this gets even messier, Ruby 1.9.2 raised a different exception to Ruby 2.0.0 - # handle both exceptions + # this gets even messier, Ruby 1.9.2 raised a different exception to Ruby 2.0.0 + # handle both exceptions rescue ArgumentError, IPAddr::InvalidAddressError self.errors.add(:ip_address, :invalid) end @@ -79,7 +79,7 @@ class ScreenedIpAddress < ActiveRecord::Base # http://www.postgresql.org/docs/9.1/static/datatype-net-types.html # http://www.postgresql.org/docs/9.1/static/functions-net.html ip_address = IPAddr === ip_address ? ip_address.to_cidr_s : ip_address.to_s - order('masklen(ip_address) DESC').find_by("? <<= ip_address", ip_address) + order("masklen(ip_address) DESC").find_by("? <<= ip_address", ip_address) end def self.should_block?(ip_address) @@ -134,31 +134,33 @@ class ScreenedIpAddress < ActiveRecord::Base def self.roll_up(current_user = Discourse.system_user) ROLLED_UP_BLOCKS.each do |family, from_masklen, to_masklen| - ScreenedIpAddress.subnets(family, from_masklen, to_masklen).map do |subnet| - next if ScreenedIpAddress.where("? <<= ip_address", subnet).exists? + ScreenedIpAddress + .subnets(family, from_masklen, to_masklen) + .map do |subnet| + next if ScreenedIpAddress.where("? <<= ip_address", subnet).exists? - old_ips = ScreenedIpAddress - .where(action_type: ScreenedIpAddress.actions[:block]) - .where("ip_address << ?", subnet) - .where("family(ip_address) = ?", family) - .where("masklen(ip_address) IN (?)", from_masklen) + old_ips = + ScreenedIpAddress + .where(action_type: ScreenedIpAddress.actions[:block]) + .where("ip_address << ?", subnet) + .where("family(ip_address) = ?", family) + .where("masklen(ip_address) IN (?)", from_masklen) - sum_match_count, max_last_match_at, min_created_at = - old_ips.pluck_first('SUM(match_count), MAX(last_match_at), MIN(created_at)') + sum_match_count, max_last_match_at, min_created_at = + old_ips.pluck_first("SUM(match_count), MAX(last_match_at), MIN(created_at)") - ScreenedIpAddress.create!( - ip_address: subnet, - match_count: sum_match_count, - last_match_at: max_last_match_at, - created_at: min_created_at, - ) + ScreenedIpAddress.create!( + ip_address: subnet, + match_count: sum_match_count, + last_match_at: max_last_match_at, + created_at: min_created_at, + ) - StaffActionLogger.new(current_user).log_roll_up(subnet, old_ips.map(&:ip_address)) - old_ips.delete_all - end + StaffActionLogger.new(current_user).log_roll_up(subnet, old_ips.map(&:ip_address)) + old_ips.delete_all + end end end - end # == Schema Information diff --git a/app/models/screened_url.rb b/app/models/screened_url.rb index 0140dd2b47c..0e827cf522d 100644 --- a/app/models/screened_url.rb +++ b/app/models/screened_url.rb @@ -6,7 +6,6 @@ # For now, nothing is done. We're just collecting the data and will decide # what to do with it later. class ScreenedUrl < ActiveRecord::Base - include ScreeningModel default_action :do_nothing @@ -18,7 +17,7 @@ class ScreenedUrl < ActiveRecord::Base def normalize self.url = ScreenedUrl.normalize_url(self.url) if self.url - self.domain = self.domain.downcase.sub(/^www\./, '') if self.domain + self.domain = self.domain.downcase.sub(/^www\./, "") if self.domain end def self.watch(url, domain, opts = {}) @@ -30,9 +29,9 @@ class ScreenedUrl < ActiveRecord::Base end def self.normalize_url(url) - normalized = url.gsub(/http(s?):\/\//i, '') - normalized.gsub!(/(\/)+$/, '') # trim trailing slashes - normalized.gsub!(/^([^\/]+)(?:\/)?/) { |m| m.downcase } # downcase the domain part of the url + normalized = url.gsub(%r{http(s?)://}i, "") + normalized.gsub!(%r{(/)+$}, "") # trim trailing slashes + normalized.gsub!(%r{^([^/]+)(?:/)?}) { |m| m.downcase } # downcase the domain part of the url normalized end end diff --git a/app/models/search_log.rb b/app/models/search_log.rb index ecaa2db6e79..8d4e1d7e7e6 100644 --- a/app/models/search_log.rb +++ b/app/models/search_log.rb @@ -14,19 +14,11 @@ class SearchLog < ActiveRecord::Base end def self.search_types - @search_types ||= Enum.new( - header: 1, - full_page: 2 - ) + @search_types ||= Enum.new(header: 1, full_page: 2) end def self.search_result_types - @search_result_types ||= Enum.new( - topic: 1, - user: 2, - category: 3, - tag: 4 - ) + @search_result_types ||= Enum.new(topic: 1, user: 2, category: 3, tag: 4) end def self.redis_key(ip_address:, user_id: nil) @@ -39,13 +31,10 @@ class SearchLog < ActiveRecord::Base # for testing def self.clear_debounce_cache! - Discourse.redis.keys("__SEARCH__LOG_*").each do |k| - Discourse.redis.del(k) - end + Discourse.redis.keys("__SEARCH__LOG_*").each { |k| Discourse.redis.del(k) } end def self.log(term:, search_type:, ip_address:, user_id: nil) - return [:error] if term.blank? search_type = search_types[search_type] @@ -60,22 +49,15 @@ class SearchLog < ActiveRecord::Base id, old_term = existing.split(",", 2) if term.start_with?(old_term) - where(id: id.to_i).update_all( - created_at: Time.zone.now, - term: term - ) + where(id: id.to_i).update_all(created_at: Time.zone.now, term: term) result = [:updated, id.to_i] end end if !result - log = self.create!( - term: term, - search_type: search_type, - ip_address: ip_address, - user_id: user_id - ) + log = + self.create!(term: term, search_type: search_type, ip_address: ip_address, user_id: user_id) result = [:created, log.id] end @@ -88,21 +70,21 @@ class SearchLog < ActiveRecord::Base def self.term_details(term, period = :weekly, search_type = :all) details = [] - result = SearchLog.select("COUNT(*) AS count, created_at::date AS date") - .where( - 'lower(term) = ? AND created_at > ?', - term.downcase, start_of(period) + result = + SearchLog.select("COUNT(*) AS count, created_at::date AS date").where( + "lower(term) = ? AND created_at > ?", + term.downcase, + start_of(period), ) - result = result.where('search_type = ?', search_types[search_type]) if search_type == :header || search_type == :full_page - result = result.where('search_result_id IS NOT NULL') if search_type == :click_through_only + result = result.where("search_type = ?", search_types[search_type]) if search_type == :header || + search_type == :full_page + result = result.where("search_result_id IS NOT NULL") if search_type == :click_through_only result .order("date") .group("date") - .each do |record| - details << { x: Date.parse(record['date'].to_s), y: record['count'] } - end + .each { |record| details << { x: Date.parse(record["date"].to_s), y: record["count"] } } { type: "search_log_term", @@ -110,7 +92,7 @@ class SearchLog < ActiveRecord::Base start_date: start_of(period), end_date: Time.zone.now, data: details, - period: period.to_s + period: period.to_s, } end @@ -132,39 +114,40 @@ class SearchLog < ActiveRecord::Base END) AS click_through SQL - result = SearchLog.select(select_sql) - .where('created_at > ?', start_date) + result = SearchLog.select(select_sql).where("created_at > ?", start_date) - if end_date - result = result.where('created_at < ?', end_date) - end + result = result.where("created_at < ?", end_date) if end_date - unless search_type == :all - result = result.where('search_type = ?', search_types[search_type]) - end + result = result.where("search_type = ?", search_types[search_type]) unless search_type == :all - result.group('lower(term)') - .order('searches DESC, click_through DESC, term ASC') - .limit(limit) + result.group("lower(term)").order("searches DESC, click_through DESC, term ASC").limit(limit) end def self.clean_up - search_id = SearchLog.order(:id).offset(SiteSetting.search_query_log_max_size).limit(1).pluck(:id) - if search_id.present? - SearchLog.where('id < ?', search_id[0]).delete_all - end - SearchLog.where('created_at < TIMESTAMP ?', SiteSetting.search_query_log_max_retention_days.days.ago).delete_all + search_id = + SearchLog.order(:id).offset(SiteSetting.search_query_log_max_size).limit(1).pluck(:id) + SearchLog.where("id < ?", search_id[0]).delete_all if search_id.present? + SearchLog.where( + "created_at < TIMESTAMP ?", + SiteSetting.search_query_log_max_retention_days.days.ago, + ).delete_all end def self.start_of(period) period = case period - when :yearly then 1.year.ago - when :monthly then 1.month.ago - when :quarterly then 3.months.ago - when :weekly then 1.week.ago - when :daily then Time.zone.now - else 1000.years.ago + when :yearly + 1.year.ago + when :monthly + 1.month.ago + when :quarterly + 3.months.ago + when :weekly + 1.week.ago + when :daily + Time.zone.now + else + 1000.years.ago end period&.to_date diff --git a/app/models/sidebar_section_link.rb b/app/models/sidebar_section_link.rb index 0b6f7bfebea..b6b23a73571 100644 --- a/app/models/sidebar_section_link.rb +++ b/app/models/sidebar_section_link.rb @@ -4,16 +4,20 @@ class SidebarSectionLink < ActiveRecord::Base belongs_to :user belongs_to :linkable, polymorphic: true - validates :user_id, presence: true, uniqueness: { scope: [:linkable_type, :linkable_id] } + validates :user_id, presence: true, uniqueness: { scope: %i[linkable_type linkable_id] } validates :linkable_id, presence: true validates :linkable_type, presence: true validate :ensure_supported_linkable_type, if: :will_save_change_to_linkable_type? - SUPPORTED_LINKABLE_TYPES = %w{Category Tag} + SUPPORTED_LINKABLE_TYPES = %w[Category Tag] private def ensure_supported_linkable_type - if (!SUPPORTED_LINKABLE_TYPES.include?(self.linkable_type)) || (self.linkable_type == 'Tag' && !SiteSetting.tagging_enabled) - self.errors.add(:linkable_type, I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid")) + if (!SUPPORTED_LINKABLE_TYPES.include?(self.linkable_type)) || + (self.linkable_type == "Tag" && !SiteSetting.tagging_enabled) + self.errors.add( + :linkable_type, + I18n.t("activerecord.errors.models.sidebar_section_link.attributes.linkable_type.invalid"), + ) end end end diff --git a/app/models/site.rb b/app/models/site.rb index 2899a932cbe..5d3b4565bc2 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -67,84 +67,90 @@ class Site # # Do note that any new association added to the eager loading needs a # corresponding ActiveRecord callback to clear the categories cache. - Discourse.cache.fetch(categories_cache_key, expires_in: 30.minutes) do - categories = Category - .includes(:uploaded_logo, :uploaded_logo_dark, :uploaded_background, :tags, :tag_groups, category_required_tag_groups: :tag_group) - .joins('LEFT JOIN topics t on t.id = categories.topic_id') - .select('categories.*, t.slug topic_slug') - .order(:position) - .to_a + Discourse + .cache + .fetch(categories_cache_key, expires_in: 30.minutes) do + categories = + Category + .includes( + :uploaded_logo, + :uploaded_logo_dark, + :uploaded_background, + :tags, + :tag_groups, + category_required_tag_groups: :tag_group, + ) + .joins("LEFT JOIN topics t on t.id = categories.topic_id") + .select("categories.*, t.slug topic_slug") + .order(:position) + .to_a - if preloaded_category_custom_fields.present? - Category.preload_custom_fields( + if preloaded_category_custom_fields.present? + Category.preload_custom_fields(categories, preloaded_category_custom_fields) + end + + ActiveModel::ArraySerializer.new( categories, - preloaded_category_custom_fields - ) + each_serializer: SiteCategorySerializer, + ).as_json end - - ActiveModel::ArraySerializer.new( - categories, - each_serializer: SiteCategorySerializer - ).as_json - end end def categories - @categories ||= begin - categories = [] + @categories ||= + begin + categories = [] - self.class.all_categories_cache.each do |category| - if @guardian.can_see_serialized_category?(category_id: category[:id], read_restricted: category[:read_restricted]) - categories << category + self.class.all_categories_cache.each do |category| + if @guardian.can_see_serialized_category?( + category_id: category[:id], + read_restricted: category[:read_restricted], + ) + categories << category + end end - end - with_children = Set.new - categories.each do |c| - if c[:parent_category_id] - with_children << c[:parent_category_id] + with_children = Set.new + categories.each { |c| with_children << c[:parent_category_id] if c[:parent_category_id] } + + allowed_topic_create = nil + unless @guardian.is_admin? + allowed_topic_create_ids = + @guardian.anonymous? ? [] : Category.topic_create_allowed(@guardian).pluck(:id) + allowed_topic_create = Set.new(allowed_topic_create_ids) end + + by_id = {} + + notification_levels = CategoryUser.notification_levels_for(@guardian.user) + default_notification_level = CategoryUser.default_notification_level + + categories.each do |category| + category[:notification_level] = notification_levels[category[:id]] || + default_notification_level + category[:permission] = CategoryGroup.permission_types[ + :full + ] if allowed_topic_create&.include?(category[:id]) || @guardian.is_admin? + category[:has_children] = with_children.include?(category[:id]) + + category[:can_edit] = @guardian.can_edit_serialized_category?( + category_id: category[:id], + read_restricted: category[:read_restricted], + ) + + by_id[category[:id]] = category + end + + categories.reject! { |c| c[:parent_category_id] && !by_id[c[:parent_category_id]] } + + self.class.categories_callbacks.each { |callback| callback.call(categories, @guardian) } + + categories end - - allowed_topic_create = nil - unless @guardian.is_admin? - allowed_topic_create_ids = - @guardian.anonymous? ? [] : Category.topic_create_allowed(@guardian).pluck(:id) - allowed_topic_create = Set.new(allowed_topic_create_ids) - end - - by_id = {} - - notification_levels = CategoryUser.notification_levels_for(@guardian.user) - default_notification_level = CategoryUser.default_notification_level - - categories.each do |category| - category[:notification_level] = notification_levels[category[:id]] || default_notification_level - category[:permission] = CategoryGroup.permission_types[:full] if allowed_topic_create&.include?(category[:id]) || @guardian.is_admin? - category[:has_children] = with_children.include?(category[:id]) - - category[:can_edit] = @guardian.can_edit_serialized_category?( - category_id: category[:id], - read_restricted: category[:read_restricted] - ) - - by_id[category[:id]] = category - end - - categories.reject! { |c| c[:parent_category_id] && !by_id[c[:parent_category_id]] } - - self.class.categories_callbacks.each do |callback| - callback.call(categories, @guardian) - end - - categories - end end def groups - Group - .visible_groups(@guardian.user, "name ASC", include_everyone: true) - .includes(:flair_upload) + Group.visible_groups(@guardian.user, "name ASC", include_everyone: true).includes(:flair_upload) end def archetypes @@ -157,24 +163,31 @@ class Site def self.json_for(guardian) if guardian.anonymous? && SiteSetting.login_required - return { - periods: TopTopic.periods.map(&:to_s), - filters: Discourse.filters.map(&:to_s), - user_fields: UserField.includes(:user_field_options).order(:position).all.map do |userfield| - UserFieldSerializer.new(userfield, root: false, scope: guardian) - end, - auth_providers: Discourse.enabled_auth_providers.map do |provider| - AuthProviderSerializer.new(provider, root: false, scope: guardian) - end - }.to_json + return( + { + periods: TopTopic.periods.map(&:to_s), + filters: Discourse.filters.map(&:to_s), + user_fields: + UserField + .includes(:user_field_options) + .order(:position) + .all + .map { |userfield| UserFieldSerializer.new(userfield, root: false, scope: guardian) }, + auth_providers: + Discourse.enabled_auth_providers.map do |provider| + AuthProviderSerializer.new(provider, root: false, scope: guardian) + end, + }.to_json + ) end seq = nil if guardian.anonymous? - seq = MessageBus.last_id('/site_json') + seq = MessageBus.last_id("/site_json") - cached_json, cached_seq, cached_version = Discourse.redis.mget('site_json', 'site_json_seq', 'site_json_version') + cached_json, cached_seq, cached_version = + Discourse.redis.mget("site_json", "site_json_seq", "site_json_version") if cached_json && seq == cached_seq.to_i && Discourse.git_version == cached_version return cached_json @@ -186,21 +199,21 @@ class Site if guardian.anonymous? Discourse.redis.multi do |transaction| - transaction.setex 'site_json', 1800, json - transaction.set 'site_json_seq', seq - transaction.set 'site_json_version', Discourse.git_version + transaction.setex "site_json", 1800, json + transaction.set "site_json_seq", seq + transaction.set "site_json_version", Discourse.git_version end end json end - SITE_JSON_CHANNEL = '/site_json' + SITE_JSON_CHANNEL = "/site_json" def self.clear_anon_cache! # publishing forces the sequence up # the cache is validated based on the sequence - MessageBus.publish(SITE_JSON_CHANNEL, '') + MessageBus.publish(SITE_JSON_CHANNEL, "") end def self.welcome_topic_banner_cache_key(user_id) @@ -208,12 +221,14 @@ class Site end def self.welcome_topic_exists_and_is_not_edited? - Post.joins(:topic) + Post + .joins(:topic) .where( "topics.id = :topic_id AND topics.deleted_at IS NULL AND posts.post_number = 1 AND posts.version = 1 AND posts.created_at > :created_at", topic_id: SiteSetting.welcome_topic_id, - created_at: 1.month.ago - ).exists? + created_at: 1.month.ago, + ) + .exists? end def self.show_welcome_topic_banner?(guardian) @@ -223,11 +238,12 @@ class Site show_welcome_topic_banner = Discourse.cache.read(welcome_topic_banner_cache_key(user_id)) return show_welcome_topic_banner unless show_welcome_topic_banner.nil? - show_welcome_topic_banner = if (user_id == User.first_login_admin_id) - welcome_topic_exists_and_is_not_edited? - else - false - end + show_welcome_topic_banner = + if (user_id == User.first_login_admin_id) + welcome_topic_exists_and_is_not_edited? + else + false + end Discourse.cache.write(welcome_topic_banner_cache_key(user_id), show_welcome_topic_banner) show_welcome_topic_banner diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index dfd2ae8bf33..01af33da64c 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -14,19 +14,21 @@ class SiteSetting < ActiveRecord::Base if self.data_type == SiteSettings::TypeSupervisor.types[:upload] UploadReference.ensure_exist!(upload_ids: [self.value], target: self) elsif self.data_type == SiteSettings::TypeSupervisor.types[:uploaded_image_list] - upload_ids = self.value.split('|').compact.uniq + upload_ids = self.value.split("|").compact.uniq UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) end end end def self.load_settings(file, plugin: nil) - SiteSettings::YamlLoader.new(file).load do |category, name, default, opts| - setting(name, default, opts.merge(category: category, plugin: plugin)) - end + SiteSettings::YamlLoader + .new(file) + .load do |category, name, default, opts| + setting(name, default, opts.merge(category: category, plugin: plugin)) + end end - load_settings(File.join(Rails.root, 'config', 'site_settings.yml')) + load_settings(File.join(Rails.root, "config", "site_settings.yml")) if GlobalSetting.load_plugins? Dir[File.join(Rails.root, "plugins", "*", "config", "settings.yml")].each do |file| @@ -62,7 +64,7 @@ class SiteSetting < ActiveRecord::Base end def self.top_menu_items - top_menu.split('|').map { |menu_item| TopMenuItem.new(menu_item) } + top_menu.split("|").map { |menu_item| TopMenuItem.new(menu_item) } end def self.homepage @@ -74,7 +76,8 @@ class SiteSetting < ActiveRecord::Base end def self.anonymous_homepage - top_menu_items.map { |item| item.name } + top_menu_items + .map { |item| item.name } .select { |item| anonymous_menu_items.include?(item) } .first end @@ -98,7 +101,10 @@ class SiteSetting < ActiveRecord::Base end def self.queue_jobs=(val) - Discourse.deprecate("queue_jobs is deprecated. Please use Jobs.run_immediately! instead", drop_from: '2.9.0') + Discourse.deprecate( + "queue_jobs is deprecated. Please use Jobs.run_immediately! instead", + drop_from: "2.9.0", + ) val ? Jobs.run_later! : Jobs.run_immediately! end @@ -159,12 +165,19 @@ class SiteSetting < ActiveRecord::Base def self.s3_base_url path = self.s3_upload_bucket.split("/", 2)[1] - "#{self.absolute_base_url}#{path ? '/' + path : ''}" + "#{self.absolute_base_url}#{path ? "/" + path : ""}" end def self.absolute_base_url - url_basename = SiteSetting.s3_endpoint.split('/')[-1] - bucket = SiteSetting.enable_s3_uploads ? Discourse.store.s3_bucket_name : GlobalSetting.s3_bucket_name + url_basename = SiteSetting.s3_endpoint.split("/")[-1] + bucket = + ( + if SiteSetting.enable_s3_uploads + Discourse.store.s3_bucket_name + else + GlobalSetting.s3_bucket_name + end + ) # cf. http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region if SiteSetting.s3_endpoint.blank? || SiteSetting.s3_endpoint.end_with?("amazonaws.com") @@ -196,7 +209,7 @@ class SiteSetting < ActiveRecord::Base end client_settings << :require_invite_code - %i{ + %i[ site_logo_url site_logo_small_url site_mobile_logo_url @@ -204,9 +217,9 @@ class SiteSetting < ActiveRecord::Base site_logo_dark_url site_logo_small_dark_url site_mobile_logo_dark_url - }.each { |client_setting| client_settings << client_setting } + ].each { |client_setting| client_settings << client_setting } - %i{ + %i[ logo logo_small digest_logo @@ -221,14 +234,14 @@ class SiteSetting < ActiveRecord::Base twitter_summary_large_image opengraph_image push_notifications_icon - }.each do |setting_name| + ].each do |setting_name| define_singleton_method("site_#{setting_name}_url") do if SiteIconManager.respond_to?("#{setting_name}_url") return SiteIconManager.public_send("#{setting_name}_url") end upload = self.public_send(setting_name) - upload ? full_cdn_url(upload.url) : '' + upload ? full_cdn_url(upload.url) : "" end end @@ -242,34 +255,42 @@ class SiteSetting < ActiveRecord::Base end ALLOWLIST_DEPRECATED_SITE_SETTINGS = { - 'email_domains_blacklist': 'blocked_email_domains', - 'email_domains_whitelist': 'allowed_email_domains', - 'unicode_username_character_whitelist': 'allowed_unicode_username_characters', - 'user_website_domains_whitelist': 'allowed_user_website_domains', - 'whitelisted_link_domains': 'allowed_link_domains', - 'embed_whitelist_selector': 'allowed_embed_selectors', - 'auto_generated_whitelist': 'auto_generated_allowlist', - 'attachment_content_type_blacklist': 'blocked_attachment_content_types', - 'attachment_filename_blacklist': 'blocked_attachment_filenames', - 'use_admin_ip_whitelist': 'use_admin_ip_allowlist', - 'blacklist_ip_blocks': 'blocked_ip_blocks', - 'whitelist_internal_hosts': 'allowed_internal_hosts', - 'whitelisted_crawler_user_agents': 'allowed_crawler_user_agents', - 'blacklisted_crawler_user_agents': 'blocked_crawler_user_agents', - 'onebox_domains_blacklist': 'blocked_onebox_domains', - 'inline_onebox_domains_whitelist': 'allowed_inline_onebox_domains', - 'white_listed_spam_host_domains': 'allowed_spam_host_domains', - 'embed_blacklist_selector': 'blocked_embed_selectors', - 'embed_classname_whitelist': 'allowed_embed_classnames', + email_domains_blacklist: "blocked_email_domains", + email_domains_whitelist: "allowed_email_domains", + unicode_username_character_whitelist: "allowed_unicode_username_characters", + user_website_domains_whitelist: "allowed_user_website_domains", + whitelisted_link_domains: "allowed_link_domains", + embed_whitelist_selector: "allowed_embed_selectors", + auto_generated_whitelist: "auto_generated_allowlist", + attachment_content_type_blacklist: "blocked_attachment_content_types", + attachment_filename_blacklist: "blocked_attachment_filenames", + use_admin_ip_whitelist: "use_admin_ip_allowlist", + blacklist_ip_blocks: "blocked_ip_blocks", + whitelist_internal_hosts: "allowed_internal_hosts", + whitelisted_crawler_user_agents: "allowed_crawler_user_agents", + blacklisted_crawler_user_agents: "blocked_crawler_user_agents", + onebox_domains_blacklist: "blocked_onebox_domains", + inline_onebox_domains_whitelist: "allowed_inline_onebox_domains", + white_listed_spam_host_domains: "allowed_spam_host_domains", + embed_blacklist_selector: "blocked_embed_selectors", + embed_classname_whitelist: "allowed_embed_classnames", } ALLOWLIST_DEPRECATED_SITE_SETTINGS.each_pair do |old_method, new_method| self.define_singleton_method(old_method) do - Discourse.deprecate("#{old_method.to_s} is deprecated, use the #{new_method.to_s}.", drop_from: "2.6", raise_error: true) + Discourse.deprecate( + "#{old_method.to_s} is deprecated, use the #{new_method.to_s}.", + drop_from: "2.6", + raise_error: true, + ) send(new_method) end self.define_singleton_method("#{old_method}=") do |args| - Discourse.deprecate("#{old_method.to_s} is deprecated, use the #{new_method.to_s}.", drop_from: "2.6", raise_error: true) + Discourse.deprecate( + "#{old_method.to_s} is deprecated, use the #{new_method.to_s}.", + drop_from: "2.6", + raise_error: true, + ) send("#{new_method}=", args) end end diff --git a/app/models/sitemap.rb b/app/models/sitemap.rb index a6ead4692ea..a0ca9a6df26 100644 --- a/app/models/sitemap.rb +++ b/app/models/sitemap.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Sitemap < ActiveRecord::Base - RECENT_SITEMAP_NAME = 'recent' - NEWS_SITEMAP_NAME = 'news' + RECENT_SITEMAP_NAME = "recent" + NEWS_SITEMAP_NAME = "news" class << self def regenerate_sitemaps @@ -26,10 +26,7 @@ class Sitemap < ActiveRecord::Base def touch(name) find_or_initialize_by(name: name).tap do |sitemap| - sitemap.update!( - last_posted_at: sitemap.last_posted_topic || 3.days.ago, - enabled: true - ) + sitemap.update!(last_posted_at: sitemap.last_posted_topic || 3.days.ago, enabled: true) end end end @@ -55,15 +52,13 @@ class Sitemap < ActiveRecord::Base private def sitemap_topics - indexable_topics = Topic - .where(visible: true) - .joins(:category) - .where(categories: { read_restricted: false }) + indexable_topics = + Topic.where(visible: true).joins(:category).where(categories: { read_restricted: false }) if name == RECENT_SITEMAP_NAME - indexable_topics.where('bumped_at > ?', 3.days.ago).order(bumped_at: :desc) + indexable_topics.where("bumped_at > ?", 3.days.ago).order(bumped_at: :desc) elsif name == NEWS_SITEMAP_NAME - indexable_topics.where('bumped_at > ?', 72.hours.ago).order(bumped_at: :desc) + indexable_topics.where("bumped_at > ?", 72.hours.ago).order(bumped_at: :desc) else offset = (name.to_i - 1) * max_page_size diff --git a/app/models/skipped_email_log.rb b/app/models/skipped_email_log.rb index 2af1b2dd12f..eb28a0749fe 100644 --- a/app/models/skipped_email_log.rb +++ b/app/models/skipped_email_log.rb @@ -14,37 +14,38 @@ class SkippedEmailLog < ActiveRecord::Base validate :ensure_valid_reason_type def self.reason_types - @types ||= Enum.new( - custom: 1, - exceeded_emails_limit: 2, - exceeded_bounces_limit: 3, - mailing_list_no_echo_mode: 4, - user_email_no_user: 5, - user_email_post_not_found: 6, - user_email_anonymous_user: 7, - user_email_user_suspended_not_pm: 8, - user_email_seen_recently: 9, - user_email_notification_already_read: 10, - user_email_topic_nil: 11, - user_email_post_user_deleted: 12, - user_email_post_deleted: 13, - user_email_user_suspended: 14, - user_email_already_read: 15, - sender_message_blank: 16, - sender_message_to_blank: 17, - sender_text_part_body_blank: 18, - sender_body_blank: 19, - sender_post_deleted: 20, - sender_message_to_invalid: 21, - user_email_access_denied: 22, - sender_topic_deleted: 23, - user_email_no_email: 24, - group_smtp_post_deleted: 25, - group_smtp_topic_deleted: 26, - group_smtp_disabled_for_group: 27, - # you need to add the reason in server.en.yml below the "skipped_email_log" key - # when you add a new enum value - ) + @types ||= + Enum.new( + custom: 1, + exceeded_emails_limit: 2, + exceeded_bounces_limit: 3, + mailing_list_no_echo_mode: 4, + user_email_no_user: 5, + user_email_post_not_found: 6, + user_email_anonymous_user: 7, + user_email_user_suspended_not_pm: 8, + user_email_seen_recently: 9, + user_email_notification_already_read: 10, + user_email_topic_nil: 11, + user_email_post_user_deleted: 12, + user_email_post_deleted: 13, + user_email_user_suspended: 14, + user_email_already_read: 15, + sender_message_blank: 16, + sender_message_to_blank: 17, + sender_text_part_body_blank: 18, + sender_body_blank: 19, + sender_post_deleted: 20, + sender_message_to_invalid: 21, + user_email_access_denied: 22, + sender_topic_deleted: 23, + user_email_no_email: 24, + group_smtp_post_deleted: 25, + group_smtp_topic_deleted: 26, + group_smtp_disabled_for_group: 27, + # you need to add the reason in server.en.yml below the "skipped_email_log" key + # when you add a new enum value + ) end def reason @@ -56,7 +57,7 @@ class SkippedEmailLog < ActiveRecord::Base I18n.t( "skipped_email_log.#{SkippedEmailLog.reason_types[type]}", user_id: self.user_id, - post_id: self.post_id + post_id: self.post_id, ) end end @@ -68,9 +69,7 @@ class SkippedEmailLog < ActiveRecord::Base end def ensure_valid_reason_type - unless self.class.reason_types[self.reason_type] - self.errors.add(:reason_type, :invalid) - end + self.errors.add(:reason_type, :invalid) unless self.class.reason_types[self.reason_type] end end diff --git a/app/models/slug_setting.rb b/app/models/slug_setting.rb index 65460f83900..bd8912996d5 100644 --- a/app/models/slug_setting.rb +++ b/app/models/slug_setting.rb @@ -1,17 +1,13 @@ # frozen_string_literal: true class SlugSetting < EnumSiteSetting - - VALUES = %w(ascii encoded none) + VALUES = %w[ascii encoded none] def self.valid_value?(val) VALUES.include?(val) end def self.values - VALUES.map do |l| - { name: l, value: l } - end + VALUES.map { |l| { name: l, value: l } } end - end diff --git a/app/models/stylesheet_cache.rb b/app/models/stylesheet_cache.rb index 819f9ab3662..df2c493e15d 100644 --- a/app/models/stylesheet_cache.rb +++ b/app/models/stylesheet_cache.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class StylesheetCache < ActiveRecord::Base - self.table_name = 'stylesheet_cache' + self.table_name = "stylesheet_cache" MAX_TO_KEEP = 50 CLEANUP_AFTER_DAYS = 150 @@ -12,21 +12,14 @@ class StylesheetCache < ActiveRecord::Base return false if where(target: target, digest: digest).exists? - if Rails.env.development? - ActiveRecord::Base.logger = nil - end + ActiveRecord::Base.logger = nil if Rails.env.development? success = create(target: target, digest: digest, content: content, source_map: source_map) count = StylesheetCache.count if count > max_to_keep - - remove_lower = StylesheetCache - .where(target: target) - .limit(max_to_keep) - .order('id desc') - .pluck(:id) - .last + remove_lower = + StylesheetCache.where(target: target).limit(max_to_keep).order("id desc").pluck(:id).last DB.exec(<<~SQL, id: remove_lower, target: target) DELETE FROM stylesheet_cache @@ -38,15 +31,12 @@ class StylesheetCache < ActiveRecord::Base rescue ActiveRecord::RecordNotUnique, ActiveRecord::ReadOnlyError false ensure - if Rails.env.development? && old_logger - ActiveRecord::Base.logger = old_logger - end + ActiveRecord::Base.logger = old_logger if Rails.env.development? && old_logger end def self.clean_up - StylesheetCache.where('created_at < ?', CLEANUP_AFTER_DAYS.days.ago).delete_all + StylesheetCache.where("created_at < ?", CLEANUP_AFTER_DAYS.days.ago).delete_all end - end # == Schema Information diff --git a/app/models/tag.rb b/app/models/tag.rb index 6311dd3dc2f..64b932d02ed 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -5,29 +5,30 @@ class Tag < ActiveRecord::Base include HasDestroyedWebHook RESERVED_TAGS = [ - 'none', - 'constructor' # prevents issues with javascript's constructor of objects + "none", + "constructor", # prevents issues with javascript's constructor of objects ] - validates :name, - presence: true, - uniqueness: { case_sensitive: false } + validates :name, presence: true, uniqueness: { case_sensitive: false } - validate :target_tag_validator, if: Proc.new { |t| t.new_record? || t.will_save_change_to_target_tag_id? } + validate :target_tag_validator, + if: Proc.new { |t| t.new_record? || t.will_save_change_to_target_tag_id? } validate :name_validator validates :description, length: { maximum: 280 } - scope :where_name, ->(name) do - name = Array(name).map(&:downcase) - where("lower(tags.name) IN (?)", name) - end + scope :where_name, + ->(name) { + name = Array(name).map(&:downcase) + where("lower(tags.name) IN (?)", name) + } # tags that have never been used and don't belong to a tag group - scope :unused, -> do - where(topic_count: 0, pm_topic_count: 0) - .joins("LEFT JOIN tag_group_memberships tgm ON tags.id = tgm.tag_id") - .where("tgm.tag_id IS NULL") - end + scope :unused, + -> { + where(topic_count: 0, pm_topic_count: 0).joins( + "LEFT JOIN tag_group_memberships tgm ON tags.id = tgm.tag_id", + ).where("tgm.tag_id IS NULL") + } scope :base_tags, -> { where(target_tag_id: nil) } @@ -93,7 +94,7 @@ class Tag < ActiveRecord::Base end def self.find_by_name(name) - self.find_by('lower(name) = ?', name.downcase) + self.find_by("lower(name) = ?", name.downcase) end def self.top_tags(limit_arg: nil, category: nil, guardian: nil) @@ -102,19 +103,24 @@ class Tag < ActiveRecord::Base limit = limit_arg || (SiteSetting.max_tags_in_filter_list + 1) scope_category_ids = (guardian || Guardian.new).allowed_category_ids - if category - scope_category_ids &= ([category.id] + category.subcategories.pluck(:id)) - end + scope_category_ids &= ([category.id] + category.subcategories.pluck(:id)) if category return [] if scope_category_ids.empty? - filter_sql = guardian&.is_staff? ? '' : " AND tags.id IN (#{DiscourseTagging.visible_tags(guardian).select(:id).to_sql})" + filter_sql = + ( + if guardian&.is_staff? + "" + else + " AND tags.id IN (#{DiscourseTagging.visible_tags(guardian).select(:id).to_sql})" + end + ) tag_names_with_counts = DB.query <<~SQL SELECT tags.name as tag_name, SUM(stats.topic_count) AS sum_topic_count FROM category_tag_stats stats JOIN tags ON stats.tag_id = tags.id AND stats.topic_count > 0 - WHERE stats.category_id in (#{scope_category_ids.join(',')}) + WHERE stats.category_id in (#{scope_category_ids.join(",")}) #{filter_sql} GROUP BY tags.name ORDER BY sum_topic_count DESC, tag_name ASC @@ -181,16 +187,16 @@ class Tag < ActiveRecord::Base def update_synonym_associations if target_tag_id && saved_change_to_target_tag_id? - target_tag.tag_groups.each { |tag_group| tag_group.tags << self unless tag_group.tags.include?(self) } - target_tag.categories.each { |category| category.tags << self unless category.tags.include?(self) } + target_tag.tag_groups.each do |tag_group| + tag_group.tags << self unless tag_group.tags.include?(self) + end + target_tag.categories.each do |category| + category.tags << self unless category.tags.include?(self) + end end end - %i{ - tag_created - tag_updated - tag_destroyed - }.each do |event| + %i[tag_created tag_updated tag_destroyed].each do |event| define_method("trigger_#{event}_event") do DiscourseEvent.trigger(event, self) true @@ -200,9 +206,7 @@ class Tag < ActiveRecord::Base private def name_validator - if name.present? && RESERVED_TAGS.include?(self.name.strip.downcase) - errors.add(:name, :invalid) - end + errors.add(:name, :invalid) if name.present? && RESERVED_TAGS.include?(self.name.strip.downcase) end end diff --git a/app/models/tag_group.rb b/app/models/tag_group.rb index c80c65f95c7..54ee33bc1cc 100644 --- a/app/models/tag_group.rb +++ b/app/models/tag_group.rb @@ -10,7 +10,7 @@ class TagGroup < ActiveRecord::Base has_many :categories, through: :category_tag_groups has_many :tag_group_permissions, dependent: :destroy - belongs_to :parent_tag, class_name: 'Tag' + belongs_to :parent_tag, class_name: "Tag" before_create :init_permissions before_save :apply_permissions @@ -28,7 +28,11 @@ class TagGroup < ActiveRecord::Base if tag_names_arg.empty? self.parent_tag = nil else - if tag_name = DiscourseTagging.tags_for_saving(tag_names_arg, Guardian.new(Discourse.system_user)).first + if tag_name = + DiscourseTagging.tags_for_saving( + tag_names_arg, + Guardian.new(Discourse.system_user), + ).first self.parent_tag = Tag.find_by_name(tag_name) || Tag.create(name: tag_name) end end @@ -40,11 +44,7 @@ class TagGroup < ActiveRecord::Base # TODO: long term we can cache this if TONs of tag groups exist def self.find_id_by_slug(slug) - self.pluck(:id, :name).each do |id, name| - if Slug.for(name) == slug - return id - end - end + self.pluck(:id, :name).each { |id, name| return id if Slug.for(name) == slug } nil end @@ -60,7 +60,7 @@ class TagGroup < ActiveRecord::Base unless tag_group_permissions.present? || @permissions tag_group_permissions.build( group_id: Group::AUTO_GROUPS[:everyone], - permission_type: TagGroupPermission.permission_types[:full] + permission_type: TagGroupPermission.permission_types[:full], ) end end @@ -98,7 +98,11 @@ class TagGroup < ActiveRecord::Base AND id IN (SELECT tag_group_id FROM tag_group_permissions WHERE group_id IN (?)) SQL - TagGroup.where(filter_sql, guardian.allowed_category_ids, DiscourseTagging.permitted_group_ids(guardian)) + TagGroup.where( + filter_sql, + guardian.allowed_category_ids, + DiscourseTagging.permitted_group_ids(guardian), + ) end end end diff --git a/app/models/tag_user.rb b/app/models/tag_user.rb index d9437ec9f4a..21a01e5c925 100644 --- a/app/models/tag_user.rb +++ b/app/models/tag_user.rb @@ -4,20 +4,27 @@ class TagUser < ActiveRecord::Base belongs_to :tag belongs_to :user - scope :notification_level_visible, -> (notification_levels = TagUser.notification_levels.values) { - select("tag_users.*") - .distinct - .joins("LEFT OUTER JOIN tag_group_memberships ON tag_users.tag_id = tag_group_memberships.tag_id") - .joins("LEFT OUTER JOIN tag_group_permissions ON tag_group_memberships.tag_group_id = tag_group_permissions.tag_group_id") - .joins("LEFT OUTER JOIN group_users on group_users.user_id = tag_users.user_id") - .where("(tag_group_permissions.group_id IS NULL + scope :notification_level_visible, + ->(notification_levels = TagUser.notification_levels.values) { + select("tag_users.*") + .distinct + .joins( + "LEFT OUTER JOIN tag_group_memberships ON tag_users.tag_id = tag_group_memberships.tag_id", + ) + .joins( + "LEFT OUTER JOIN tag_group_permissions ON tag_group_memberships.tag_group_id = tag_group_permissions.tag_group_id", + ) + .joins("LEFT OUTER JOIN group_users on group_users.user_id = tag_users.user_id") + .where( + "(tag_group_permissions.group_id IS NULL OR tag_group_permissions.group_id IN (:everyone_group_id, group_users.group_id) OR group_users.group_id = :staff_group_id) AND tag_users.notification_level IN (:notification_levels)", - staff_group_id: Group::AUTO_GROUPS[:staff], - everyone_group_id: Group::AUTO_GROUPS[:everyone], - notification_levels: notification_levels) - } + staff_group_id: Group::AUTO_GROUPS[:staff], + everyone_group_id: Group::AUTO_GROUPS[:everyone], + notification_levels: notification_levels, + ) + } def self.notification_levels NotificationLevels.all @@ -34,46 +41,48 @@ class TagUser < ActiveRecord::Base records = TagUser.where(user: user, notification_level: notification_levels[level]) old_ids = records.pluck(:tag_id) - tag_ids = if tags.empty? - [] - elsif tags.first&.is_a?(String) - Tag.where_name(tags).pluck(:id) - else - tags - end + tag_ids = + if tags.empty? + [] + elsif tags.first&.is_a?(String) + Tag.where_name(tags).pluck(:id) + else + tags + end - Tag.where(id: tag_ids).joins(:target_tag).each do |tag| - tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id - end + Tag + .where(id: tag_ids) + .joins(:target_tag) + .each { |tag| tag_ids[tag_ids.index(tag.id)] = tag.target_tag_id } tag_ids.uniq! if tag_ids.present? && - TagUser.where(user_id: user.id, tag_id: tag_ids) - .where - .not(notification_level: notification_levels[level]) - .update_all(notification_level: notification_levels[level]) > 0 - + TagUser + .where(user_id: user.id, tag_id: tag_ids) + .where.not(notification_level: notification_levels[level]) + .update_all(notification_level: notification_levels[level]) > 0 changed = true end remove = (old_ids - tag_ids) if remove.present? - records.where('tag_id in (?)', remove).destroy_all + records.where("tag_id in (?)", remove).destroy_all changed = true end now = Time.zone.now - new_records_attrs = (tag_ids - old_ids).map do |tag_id| - { - user_id: user.id, - tag_id: tag_id, - notification_level: notification_levels[level], - created_at: now, - updated_at: now - } - end + new_records_attrs = + (tag_ids - old_ids).map do |tag_id| + { + user_id: user.id, + tag_id: tag_id, + notification_level: notification_levels[level], + created_at: now, + updated_at: now, + } + end unless new_records_attrs.empty? result = TagUser.insert_all(new_records_attrs) @@ -96,9 +105,7 @@ class TagUser < ActiveRecord::Base tag = Tag.find_by_id(tag_id) end - if tag.synonym? - tag_id = tag.target_tag_id - end + tag_id = tag.target_tag_id if tag.synonym? user_id = user_id.id if user_id.is_a?(::User) @@ -168,11 +175,12 @@ class TagUser < ActiveRecord::Base builder.where("tu.user_id = :user_id", user_id: user_id) end - builder.exec(watching: notification_levels[:watching], - tracking: notification_levels[:tracking], - regular: notification_levels[:regular], - auto_watch_tag: TopicUser.notification_reasons[:auto_watch_tag]) - + builder.exec( + watching: notification_levels[:watching], + tracking: notification_levels[:tracking], + regular: notification_levels[:regular], + auto_watch_tag: TopicUser.notification_reasons[:auto_watch_tag], + ) end def self.auto_track(opts) @@ -202,36 +210,40 @@ class TagUser < ActiveRecord::Base builder.where("tu.user_id = :user_id", user_id: user_id) end - builder.exec(tracking: notification_levels[:tracking], - regular: notification_levels[:regular], - auto_track_tag: TopicUser.notification_reasons[:auto_track_tag]) + builder.exec( + tracking: notification_levels[:tracking], + regular: notification_levels[:regular], + auto_track_tag: TopicUser.notification_reasons[:auto_track_tag], + ) end def self.notification_levels_for(user) # Anonymous users have all default tags set to regular tracking, # except for default muted tags which stay muted. if user.blank? - notification_levels = [ - SiteSetting.default_tags_watching_first_post.split("|"), - SiteSetting.default_tags_watching.split("|"), - SiteSetting.default_tags_tracking.split("|") - ].flatten.map do |name| - [name, self.notification_levels[:regular]] - end + notification_levels = + [ + SiteSetting.default_tags_watching_first_post.split("|"), + SiteSetting.default_tags_watching.split("|"), + SiteSetting.default_tags_tracking.split("|"), + ].flatten.map { |name| [name, self.notification_levels[:regular]] } - notification_levels += SiteSetting.default_tags_muted.split("|").map do |name| - [name, self.notification_levels[:muted]] - end + notification_levels += + SiteSetting + .default_tags_muted + .split("|") + .map { |name| [name, self.notification_levels[:muted]] } else - notification_levels = TagUser - .notification_level_visible - .where(user: user) - .joins(:tag).pluck("tags.name", :notification_level) + notification_levels = + TagUser + .notification_level_visible + .where(user: user) + .joins(:tag) + .pluck("tags.name", :notification_level) end Hash[*notification_levels.flatten] end - end # == Schema Information diff --git a/app/models/theme.rb b/app/models/theme.rb index e14fa527edc..23701642e55 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'csv' -require 'json_schemer' +require "csv" +require "json_schemer" class Theme < ActiveRecord::Base include GlobalPath @@ -17,44 +17,67 @@ class Theme < ActiveRecord::Base has_many :theme_fields, dependent: :destroy has_many :theme_settings, dependent: :destroy has_many :theme_translation_overrides, dependent: :destroy - has_many :child_theme_relation, class_name: 'ChildTheme', foreign_key: 'parent_theme_id', dependent: :destroy - has_many :parent_theme_relation, class_name: 'ChildTheme', foreign_key: 'child_theme_id', dependent: :destroy + has_many :child_theme_relation, + class_name: "ChildTheme", + foreign_key: "parent_theme_id", + dependent: :destroy + has_many :parent_theme_relation, + class_name: "ChildTheme", + foreign_key: "child_theme_id", + dependent: :destroy has_many :child_themes, -> { order(:name) }, through: :child_theme_relation, source: :child_theme - has_many :parent_themes, -> { order(:name) }, through: :parent_theme_relation, source: :parent_theme + has_many :parent_themes, + -> { order(:name) }, + through: :parent_theme_relation, + source: :parent_theme has_many :color_schemes belongs_to :remote_theme, dependent: :destroy has_one :theme_modifier_set, dependent: :destroy - has_one :settings_field, -> { where(target_id: Theme.targets[:settings], name: "yaml") }, class_name: 'ThemeField' + has_one :settings_field, + -> { where(target_id: Theme.targets[:settings], name: "yaml") }, + class_name: "ThemeField" has_one :javascript_cache, dependent: :destroy - has_many :locale_fields, -> { filter_locale_fields(I18n.fallbacks[I18n.locale]) }, class_name: 'ThemeField' - has_many :upload_fields, -> { where(type_id: ThemeField.types[:theme_upload_var]).preload(:upload) }, class_name: 'ThemeField' - has_many :extra_scss_fields, -> { where(target_id: Theme.targets[:extra_scss]) }, class_name: 'ThemeField' - has_many :yaml_theme_fields, -> { where("name = 'yaml' AND type_id = ?", ThemeField.types[:yaml]) }, class_name: 'ThemeField' - has_many :var_theme_fields, -> { where("type_id IN (?)", ThemeField.theme_var_type_ids) }, class_name: 'ThemeField' - has_many :builder_theme_fields, -> { where("name IN (?)", ThemeField.scss_fields) }, class_name: 'ThemeField' + has_many :locale_fields, + -> { filter_locale_fields(I18n.fallbacks[I18n.locale]) }, + class_name: "ThemeField" + has_many :upload_fields, + -> { where(type_id: ThemeField.types[:theme_upload_var]).preload(:upload) }, + class_name: "ThemeField" + has_many :extra_scss_fields, + -> { where(target_id: Theme.targets[:extra_scss]) }, + class_name: "ThemeField" + has_many :yaml_theme_fields, + -> { where("name = 'yaml' AND type_id = ?", ThemeField.types[:yaml]) }, + class_name: "ThemeField" + has_many :var_theme_fields, + -> { where("type_id IN (?)", ThemeField.theme_var_type_ids) }, + class_name: "ThemeField" + has_many :builder_theme_fields, + -> { where("name IN (?)", ThemeField.scss_fields) }, + class_name: "ThemeField" validate :component_validations after_create :update_child_components - scope :user_selectable, ->() { - where('user_selectable OR id = ?', SiteSetting.default_theme_id) - } + scope :user_selectable, -> { where("user_selectable OR id = ?", SiteSetting.default_theme_id) } - scope :include_relations, -> { - includes(:child_themes, - :parent_themes, - :remote_theme, - :theme_settings, - :settings_field, - :locale_fields, - :user, - :color_scheme, - :theme_translation_overrides, - theme_fields: :upload - ) - } + scope :include_relations, + -> { + includes( + :child_themes, + :parent_themes, + :remote_theme, + :theme_settings, + :settings_field, + :locale_fields, + :user, + :color_scheme, + :theme_translation_overrides, + theme_fields: :upload, + ) + } def notify_color_change(color, scheme: nil) scheme ||= color.color_scheme @@ -78,11 +101,11 @@ class Theme < ActiveRecord::Base theme_modifier_set.save! - if saved_change_to_name? - theme_fields.select(&:basic_html_field?).each(&:invalidate_baked!) - end + theme_fields.select(&:basic_html_field?).each(&:invalidate_baked!) if saved_change_to_name? - Theme.expire_site_cache! if saved_change_to_color_scheme_id? || saved_change_to_user_selectable? || saved_change_to_name? + if saved_change_to_color_scheme_id? || saved_change_to_user_selectable? || saved_change_to_name? + Theme.expire_site_cache! + end notify_with_scheme = saved_change_to_color_scheme_id? reload @@ -115,11 +138,12 @@ class Theme < ActiveRecord::Base end def update_javascript_cache! - all_extra_js = theme_fields - .where(target_id: Theme.targets[:extra_js]) - .order(:name, :id) - .pluck(:name, :value) - .to_h + all_extra_js = + theme_fields + .where(target_id: Theme.targets[:extra_js]) + .order(:name, :id) + .pluck(:name, :value) + .to_h if all_extra_js.present? js_compiler = ThemeJavascriptCompiler.new(id, name) @@ -135,9 +159,7 @@ class Theme < ActiveRecord::Base after_destroy do remove_from_cache! - if SiteSetting.default_theme_id == self.id - Theme.clear_default! - end + Theme.clear_default! if SiteSetting.default_theme_id == self.id if self.id ColorScheme @@ -145,9 +167,7 @@ class Theme < ActiveRecord::Base .where("id NOT IN (SELECT color_scheme_id FROM themes where color_scheme_id IS NOT NULL)") .destroy_all - ColorScheme - .where(theme_id: self.id) - .update_all(theme_id: nil) + ColorScheme.where(theme_id: self.id).update_all(theme_id: nil) end Theme.expire_site_cache! @@ -162,7 +182,7 @@ class Theme < ActiveRecord::Base GlobalSetting.s3_cdn_url, GlobalSetting.s3_endpoint, GlobalSetting.s3_bucket, - Discourse.current_hostname + Discourse.current_hostname, ] Digest::SHA1.hexdigest(dependencies.join) end @@ -199,10 +219,7 @@ class Theme < ActiveRecord::Base get_set_cache "allowed_remote_theme_ids" do urls = GlobalSetting.allowed_theme_repos.split(",").map(&:strip) - Theme - .joins(:remote_theme) - .where('remote_themes.remote_url in (?)', urls) - .pluck(:id) + Theme.joins(:remote_theme).where("remote_themes.remote_url in (?)", urls).pluck(:id) end end @@ -239,10 +256,12 @@ class Theme < ActiveRecord::Base [id] end - disabled_ids = Theme.where(id: all_ids) - .includes(:remote_theme) - .select { |t| !t.supported? || !t.enabled? } - .map(&:id) + disabled_ids = + Theme + .where(id: all_ids) + .includes(:remote_theme) + .select { |t| !t.supported? || !t.enabled? } + .map(&:id) all_ids - disabled_ids end @@ -250,9 +269,7 @@ class Theme < ActiveRecord::Base def set_default! if component - raise Discourse::InvalidParameters.new( - I18n.t("themes.errors.component_no_default") - ) + raise Discourse::InvalidParameters.new(I18n.t("themes.errors.component_no_default")) end SiteSetting.default_theme_id = id Theme.expire_site_cache! @@ -335,22 +352,35 @@ class Theme < ActiveRecord::Base end def self.clear_cache! - DB.after_commit do - @cache.clear - end + DB.after_commit { @cache.clear } end def self.targets - @targets ||= Enum.new(common: 0, desktop: 1, mobile: 2, settings: 3, translations: 4, extra_scss: 5, extra_js: 6, tests_js: 7) + @targets ||= + Enum.new( + common: 0, + desktop: 1, + mobile: 2, + settings: 3, + translations: 4, + extra_scss: 5, + extra_js: 6, + tests_js: 7, + ) end def self.lookup_target(target_id) self.targets.invert[target_id] end - def self.notify_theme_change(theme_ids, with_scheme: false, clear_manager_cache: true, all_themes: false) + def self.notify_theme_change( + theme_ids, + with_scheme: false, + clear_manager_cache: true, + all_themes: false + ) Stylesheet::Manager.clear_theme_cache! - targets = [:mobile_theme, :desktop_theme] + targets = %i[mobile_theme desktop_theme] if with_scheme targets.prepend(:desktop, :mobile, :admin) @@ -364,7 +394,7 @@ class Theme < ActiveRecord::Base message = refresh_message_for_targets(targets, theme_ids).flatten end - MessageBus.publish('/file-change', message) + MessageBus.publish("/file-change", message) end def notify_theme_change(with_scheme: false) @@ -386,20 +416,22 @@ class Theme < ActiveRecord::Base def self.resolve_baked_field(theme_ids, target, name) if target == :extra_js - require_rebake = ThemeField.where(theme_id: theme_ids, target_id: Theme.targets[:extra_js]). - where("compiler_version <> ?", Theme.compiler_version) + require_rebake = + ThemeField.where(theme_id: theme_ids, target_id: Theme.targets[:extra_js]).where( + "compiler_version <> ?", + Theme.compiler_version, + ) require_rebake.each { |tf| tf.ensure_baked! } - require_rebake.map(&:theme_id).uniq.each do |theme_id| - Theme.find(theme_id).update_javascript_cache! - end + require_rebake + .map(&:theme_id) + .uniq + .each { |theme_id| Theme.find(theme_id).update_javascript_cache! } caches = JavascriptCache.where(theme_id: theme_ids) caches = caches.sort_by { |cache| theme_ids.index(cache.theme_id) } - return caches.map do |c| - <<~HTML.html_safe + return caches.map { |c| <<~HTML.html_safe }.join("\n") HTML - end.join("\n") end list_baked_fields(theme_ids, target, name).map { |f| f.value_baked || f.value }.join("\n") end @@ -413,8 +445,10 @@ class Theme < ActiveRecord::Base else target = :mobile if target == :mobile_theme target = :desktop if target == :desktop_theme - fields = ThemeField.find_by_theme_ids(theme_ids) - .where(target_id: [Theme.targets[target], Theme.targets[:common]]) + fields = + ThemeField.find_by_theme_ids(theme_ids).where( + target_id: [Theme.targets[target], Theme.targets[:common]], + ) fields = fields.where(name: name.to_s) unless name.nil? fields = fields.order(:target_id) end @@ -455,12 +489,14 @@ class Theme < ActiveRecord::Base target_id = Theme.targets[target.to_sym] raise "Unknown target #{target} passed to set field" unless target_id - type_id ||= type ? ThemeField.types[type.to_sym] : ThemeField.guess_type(name: name, target: target) + type_id ||= + type ? ThemeField.types[type.to_sym] : ThemeField.guess_type(name: name, target: target) raise "Unknown type #{type} passed to set field" unless type_id value ||= "" - field = theme_fields.find { |f| f.name == name && f.target_id == target_id && f.type_id == type_id } + field = + theme_fields.find { |f| f.name == name && f.target_id == target_id && f.type_id == type_id } if field if value.blank? && !upload_id theme_fields.delete field.destroy @@ -473,16 +509,25 @@ class Theme < ActiveRecord::Base end field else - theme_fields.build(target_id: target_id, value: value, name: name, type_id: type_id, upload_id: upload_id) if value.present? || upload_id.present? + if value.present? || upload_id.present? + theme_fields.build( + target_id: target_id, + value: value, + name: name, + type_id: type_id, + upload_id: upload_id, + ) + end end end def add_relative_theme!(kind, theme) - new_relation = if kind == :child - child_theme_relation.new(child_theme_id: theme.id) - else - parent_theme_relation.new(parent_theme_id: theme.id) - end + new_relation = + if kind == :child + child_theme_relation.new(child_theme_id: theme.id) + else + parent_theme_relation.new(parent_theme_id: theme.id) + end if new_relation.save child_themes.reload parent_themes.reload @@ -500,13 +545,20 @@ class Theme < ActiveRecord::Base def translations(internal: false) fallbacks = I18n.fallbacks[I18n.locale] begin - data = locale_fields.first&.translation_data(with_overrides: false, internal: internal, fallback_fields: locale_fields) + data = + locale_fields.first&.translation_data( + with_overrides: false, + internal: internal, + fallback_fields: locale_fields, + ) return {} if data.nil? best_translations = {} - fallbacks.reverse.each do |locale| - best_translations.deep_merge! data[locale] if data[locale] - end - ThemeTranslationManager.list_from_hash(theme: self, hash: best_translations, locale: I18n.locale) + fallbacks.reverse.each { |locale| best_translations.deep_merge! data[locale] if data[locale] } + ThemeTranslationManager.list_from_hash( + theme: self, + hash: best_translations, + locale: I18n.locale, + ) rescue ThemeTranslationParser::InvalidYaml {} end @@ -517,9 +569,11 @@ class Theme < ActiveRecord::Base return [] unless field && field.error.nil? settings = [] - ThemeSettingsParser.new(field).load do |name, default, type, opts| - settings << ThemeSettingsManager.create(name, default, type, self, opts) - end + ThemeSettingsParser + .new(field) + .load do |name, default, type, opts| + settings << ThemeSettingsManager.create(name, default, type, self, opts) + end settings end @@ -532,15 +586,13 @@ class Theme < ActiveRecord::Base def cached_default_settings Theme.get_set_cache "default_settings_for_theme_#{self.id}" do settings_hash = {} - self.settings.each do |setting| - settings_hash[setting.name] = setting.default - end + self.settings.each { |setting| settings_hash[setting.name] = setting.default } theme_uploads = build_theme_uploads_hash - settings_hash['theme_uploads'] = theme_uploads if theme_uploads.present? + settings_hash["theme_uploads"] = theme_uploads if theme_uploads.present? theme_uploads_local = build_local_theme_uploads_hash - settings_hash['theme_uploads_local'] = theme_uploads_local if theme_uploads_local.present? + settings_hash["theme_uploads_local"] = theme_uploads_local if theme_uploads_local.present? settings_hash end @@ -548,15 +600,13 @@ class Theme < ActiveRecord::Base def build_settings_hash hash = {} - self.settings.each do |setting| - hash[setting.name] = setting.value - end + self.settings.each { |setting| hash[setting.name] = setting.value } theme_uploads = build_theme_uploads_hash - hash['theme_uploads'] = theme_uploads if theme_uploads.present? + hash["theme_uploads"] = theme_uploads if theme_uploads.present? theme_uploads_local = build_local_theme_uploads_hash - hash['theme_uploads_local'] = theme_uploads_local if theme_uploads_local.present? + hash["theme_uploads_local"] = theme_uploads_local if theme_uploads_local.present? hash end @@ -564,9 +614,7 @@ class Theme < ActiveRecord::Base def build_theme_uploads_hash hash = {} upload_fields.each do |field| - if field.upload&.url - hash[field.name] = Discourse.store.cdn_url(field.upload.url) - end + hash[field.name] = Discourse.store.cdn_url(field.upload.url) if field.upload&.url end hash end @@ -574,9 +622,7 @@ class Theme < ActiveRecord::Base def build_local_theme_uploads_hash hash = {} upload_fields.each do |field| - if field.javascript_cache - hash[field.name] = field.javascript_cache.local_url - end + hash[field.name] = field.javascript_cache.local_url if field.javascript_cache end hash end @@ -587,9 +633,7 @@ class Theme < ActiveRecord::Base target_setting.value = new_value - if target_setting.requests_refresh? - self.theme_setting_requests_refresh = true - end + self.theme_setting_requests_refresh = true if target_setting.requests_refresh? end def update_translation(translation_key, new_value) @@ -603,9 +647,7 @@ class Theme < ActiveRecord::Base theme_translation_overrides.each do |override| cursor = hash path = [override.locale] + override.translation_key.split(".") - path[0..-2].each do |key| - cursor = (cursor[key] ||= {}) - end + path[0..-2].each { |key| cursor = (cursor[key] ||= {}) } cursor[path[-1]] = override.value end hash @@ -622,9 +664,9 @@ class Theme < ActiveRecord::Base end meta[:assets] = {}.tap do |hash| - theme_fields.where(type_id: ThemeField.types[:theme_upload_var]).each do |field| - hash[field.name] = field.file_path - end + theme_fields + .where(type_id: ThemeField.types[:theme_upload_var]) + .each { |field| hash[field.name] = field.file_path } end meta[:color_schemes] = {}.tap do |hash| @@ -632,7 +674,9 @@ class Theme < ActiveRecord::Base # The selected color scheme may not belong to the theme, so include it anyway schemes = [self.color_scheme] + schemes if self.color_scheme schemes.uniq.each do |scheme| - hash[scheme.name] = {}.tap { |colors| scheme.colors.each { |color| colors[color.name] = color.hex } } + hash[scheme.name] = {}.tap do |colors| + scheme.colors.each { |color| colors[color.name] = color.hex } + end end end @@ -643,8 +687,9 @@ class Theme < ActiveRecord::Base end end - meta[:learn_more] = "https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966" - + meta[ + :learn_more + ] = "https://meta.discourse.org/t/beginners-guide-to-using-discourse-themes/91966" end end @@ -659,9 +704,9 @@ class Theme < ActiveRecord::Base def with_scss_load_paths return yield([]) if self.extra_scss_fields.empty? - ThemeStore::ZipExporter.new(self).with_export_dir(extra_scss_only: true) do |dir| - yield ["#{dir}/stylesheets"] - end + ThemeStore::ZipExporter + .new(self) + .with_export_dir(extra_scss_only: true) { |dir| yield ["#{dir}/stylesheets"] } end def scss_variables @@ -696,12 +741,15 @@ class Theme < ActiveRecord::Base setting_row = ThemeSetting.where(theme_id: self.id, name: setting.name.to_s).first if setting_row && setting_row.data_type != setting.type - if (setting_row.data_type == ThemeSetting.types[:list] && - setting.type == ThemeSetting.types[:string] && - setting.json_schema.present?) + if ( + setting_row.data_type == ThemeSetting.types[:list] && + setting.type == ThemeSetting.types[:string] && setting.json_schema.present? + ) convert_list_to_json_schema(setting_row, setting) else - Rails.logger.warn("Theme setting type has changed but cannot be converted. \n\n #{setting.inspect}") + Rails.logger.warn( + "Theme setting type has changed but cannot be converted. \n\n #{setting.inspect}", + ) end end end @@ -713,10 +761,10 @@ class Theme < ActiveRecord::Base keys = schema["items"]["properties"].keys return if !keys - current_values = CSV.parse(setting_row.value, **{ col_sep: '|' }).flatten + current_values = CSV.parse(setting_row.value, **{ col_sep: "|" }).flatten new_values = [] current_values.each do |item| - parts = CSV.parse(item, **{ col_sep: ',' }).flatten + parts = CSV.parse(item, **{ col_sep: "," }).flatten props = parts.map.with_index { |p, idx| [keys[idx], p] }.to_h new_values << props end @@ -730,13 +778,14 @@ class Theme < ActiveRecord::Base end def baked_js_tests_with_digest - tests_tree = theme_fields - .where(target_id: Theme.targets[:tests_js]) - .order(name: :asc) - .pluck(:name, :value) - .to_h + tests_tree = + theme_fields + .where(target_id: Theme.targets[:tests_js]) + .order(name: :asc) + .pluck(:name, :value) + .to_h - return [nil, nil] if tests_tree.blank? + return nil, nil if tests_tree.blank? compiler = ThemeJavascriptCompiler.new(id, name) compiler.append_tree(tests_tree, for_tests: true) @@ -748,7 +797,8 @@ class Theme < ActiveRecord::Base content = compiler.content if compiler.source_map - content += "\n//# sourceMappingURL=data:application/json;base64,#{Base64.strict_encode64(compiler.source_map)}\n" + content += + "\n//# sourceMappingURL=data:application/json;base64,#{Base64.strict_encode64(compiler.source_map)}\n" end [content, Digest::SHA1.hexdigest(content)] @@ -765,7 +815,11 @@ class Theme < ActiveRecord::Base def find_disable_action_log if component? && !enabled? - @disable_log ||= UserHistory.where(context: id.to_s, action: UserHistory.actions[:disable_theme_component]).order("created_at DESC").first + @disable_log ||= + UserHistory + .where(context: id.to_s, action: UserHistory.actions[:disable_theme_component]) + .order("created_at DESC") + .first end end end diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb index 4c45b08b60d..780789c42bf 100644 --- a/app/models/theme_field.rb +++ b/app/models/theme_field.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ThemeField < ActiveRecord::Base - belongs_to :upload has_one :javascript_cache, dependent: :destroy has_one :upload_reference, as: :target, dependent: :destroy @@ -12,45 +11,50 @@ class ThemeField < ActiveRecord::Base end end - scope :find_by_theme_ids, ->(theme_ids) { - return none unless theme_ids.present? + scope :find_by_theme_ids, + ->(theme_ids) { + return none unless theme_ids.present? - where(theme_id: theme_ids) - .joins( - "JOIN ( + where(theme_id: theme_ids).joins( + "JOIN ( SELECT #{theme_ids.map.with_index { |id, idx| "#{id.to_i} AS theme_id, #{idx} AS theme_sort_column" }.join(" UNION ALL SELECT ")} - ) as X ON X.theme_id = theme_fields.theme_id") - .order("theme_sort_column") - } + ) as X ON X.theme_id = theme_fields.theme_id", + ).order("theme_sort_column") + } - scope :filter_locale_fields, ->(locale_codes) { - return none unless locale_codes.present? + scope :filter_locale_fields, + ->(locale_codes) { + return none unless locale_codes.present? - where(target_id: Theme.targets[:translations], name: locale_codes) - .joins(DB.sql_fragment( - "JOIN ( + where(target_id: Theme.targets[:translations], name: locale_codes).joins( + DB.sql_fragment( + "JOIN ( SELECT * FROM (VALUES #{locale_codes.map { "(?)" }.join(",")}) as Y (locale_code, locale_sort_column) ) as Y ON Y.locale_code = theme_fields.name", - *locale_codes.map.with_index { |code, index| [code, index] } - )) - .order("Y.locale_sort_column") - } + *locale_codes.map.with_index { |code, index| [code, index] }, + ), + ).order("Y.locale_sort_column") + } - scope :find_first_locale_fields, ->(theme_ids, locale_codes) { - find_by_theme_ids(theme_ids) - .filter_locale_fields(locale_codes) - .reorder("X.theme_sort_column", "Y.locale_sort_column") - .select("DISTINCT ON (X.theme_sort_column) *") - } + scope :find_first_locale_fields, + ->(theme_ids, locale_codes) { + find_by_theme_ids(theme_ids) + .filter_locale_fields(locale_codes) + .reorder("X.theme_sort_column", "Y.locale_sort_column") + .select("DISTINCT ON (X.theme_sort_column) *") + } def self.types - @types ||= Enum.new(html: 0, - scss: 1, - theme_upload_var: 2, - theme_color_var: 3, # No longer used - theme_var: 4, # No longer used - yaml: 5, - js: 6) + @types ||= + Enum.new( + html: 0, + scss: 1, + theme_upload_var: 2, + theme_color_var: 3, # No longer used + theme_var: 4, # No longer used + yaml: 5, + js: 6, + ) end def self.theme_var_type_ids @@ -68,8 +72,11 @@ class ThemeField < ActiveRecord::Base end end - validates :name, format: { with: /\A[a-z_][a-z0-9_-]*\z/i }, - if: Proc.new { |field| ThemeField.theme_var_type_ids.include?(field.type_id) } + validates :name, + format: { + with: /\A[a-z_][a-z0-9_-]*\z/i, + }, + if: Proc.new { |field| ThemeField.theme_var_type_ids.include?(field.type_id) } belongs_to :theme @@ -83,36 +90,38 @@ class ThemeField < ActiveRecord::Base doc = Nokogiri::HTML5.fragment(html) - doc.css('script[type="text/x-handlebars"]').each do |node| - name = node["name"] || node["data-template-name"] || "broken" - is_raw = name =~ /\.(raw|hbr)$/ - hbs_template = node.inner_html + doc + .css('script[type="text/x-handlebars"]') + .each do |node| + name = node["name"] || node["data-template-name"] || "broken" + is_raw = name =~ /\.(raw|hbr)$/ + hbs_template = node.inner_html - begin - if is_raw - js_compiler.append_raw_template(name, hbs_template) - else - js_compiler.append_ember_template("discourse/templates/#{name}", hbs_template) + begin + if is_raw + js_compiler.append_raw_template(name, hbs_template) + else + js_compiler.append_ember_template("discourse/templates/#{name}", hbs_template) + end + rescue ThemeJavascriptCompiler::CompileError => ex + js_compiler.append_js_error("discourse/templates/#{name}", ex.message) + errors << ex.message end - rescue ThemeJavascriptCompiler::CompileError => ex - js_compiler.append_js_error("discourse/templates/#{name}", ex.message) - errors << ex.message + + node.remove end - node.remove - end + doc + .css('script[type="text/discourse-plugin"]') + .each_with_index do |node, index| + version = node["version"] + next if version.blank? - doc.css('script[type="text/discourse-plugin"]').each_with_index do |node, index| - version = node['version'] - next if version.blank? - - initializer_name = "theme-field" + - "-#{self.id}" + - "-#{Theme.targets[self.target_id]}" + - "-#{ThemeField.types[self.type_id]}" + - "-script-#{index + 1}" - begin - js = <<~JS + initializer_name = + "theme-field" + "-#{self.id}" + "-#{Theme.targets[self.target_id]}" + + "-#{ThemeField.types[self.type_id]}" + "-script-#{index + 1}" + begin + js = <<~JS import { withPluginApi } from "discourse/lib/plugin-api"; export default { @@ -127,43 +136,60 @@ class ThemeField < ActiveRecord::Base }; JS - js_compiler.append_module(js, "discourse/initializers/#{initializer_name}", include_variables: true) - rescue ThemeJavascriptCompiler::CompileError => ex - js_compiler.append_js_error("discourse/initializers/#{initializer_name}", ex.message) - errors << ex.message + js_compiler.append_module( + js, + "discourse/initializers/#{initializer_name}", + include_variables: true, + ) + rescue ThemeJavascriptCompiler::CompileError => ex + js_compiler.append_js_error("discourse/initializers/#{initializer_name}", ex.message) + errors << ex.message + end + + node.remove end - node.remove - end - - doc.css('script').each_with_index do |node, index| - next unless inline_javascript?(node) - js_compiler.append_raw_script("_html/#{Theme.targets[self.target_id]}/#{name}_#{index + 1}.js", node.inner_html) - node.remove - end + doc + .css("script") + .each_with_index do |node, index| + next unless inline_javascript?(node) + js_compiler.append_raw_script( + "_html/#{Theme.targets[self.target_id]}/#{name}_#{index + 1}.js", + node.inner_html, + ) + node.remove + end settings_hash = theme.build_settings_hash - js_compiler.prepend_settings(settings_hash) if js_compiler.has_content? && settings_hash.present? + if js_compiler.has_content? && settings_hash.present? + js_compiler.prepend_settings(settings_hash) + end javascript_cache.content = js_compiler.content javascript_cache.source_map = js_compiler.source_map javascript_cache.save! - if javascript_cache.content.present? - doc.add_child( - <<~HTML.html_safe + doc.add_child(<<~HTML.html_safe) if javascript_cache.content.present? HTML - ) - end [doc.to_s, errors&.join("\n")] end def validate_svg_sprite_xml - upload = Upload.find(self.upload_id) rescue nil + upload = + begin + Upload.find(self.upload_id) + rescue StandardError + nil + end if Discourse.store.external? - external_copy = Discourse.store.download(upload) rescue nil + external_copy = + begin + Discourse.store.download(upload) + rescue StandardError + nil + end path = external_copy.try(:path) else path = Discourse.store.path_for(upload) @@ -173,9 +199,7 @@ class ThemeField < ActiveRecord::Base begin content = File.read(path) - Nokogiri::XML(content) do |config| - config.options = Nokogiri::XML::ParseOptions::NOBLANKS - end + Nokogiri.XML(content) { |config| config.options = Nokogiri::XML::ParseOptions::NOBLANKS } rescue => e error = "Error with #{self.name}: #{e.inspect}" end @@ -190,16 +214,17 @@ class ThemeField < ActiveRecord::Base def translation_data(with_overrides: true, internal: false, fallback_fields: nil) fallback_fields ||= theme.theme_fields.filter_locale_fields(I18n.fallbacks[name]) - fallback_data = fallback_fields.each_with_index.map do |field, index| - begin - field.raw_translation_data(internal: internal) - rescue ThemeTranslationParser::InvalidYaml - # If this is the locale with the error, raise it. - # If not, let the other theme_field raise the error when it processes itself - raise if field.id == id - {} + fallback_data = + fallback_fields.each_with_index.map do |field, index| + begin + field.raw_translation_data(internal: internal) + rescue ThemeTranslationParser::InvalidYaml + # If this is the locale with the error, raise it. + # If not, let the other theme_field raise the error when it processes itself + raise if field.id == id + {} + end end - end # TODO: Deduplicate the fallback data in the same way as JSLocaleHelper#load_translations_merged # this would reduce the size of the payload, without affecting functionality @@ -239,7 +264,11 @@ class ThemeField < ActiveRecord::Base }; JS - js_compiler.append_module(js, "discourse/pre-initializers/theme-#{theme_id}-translations", include_variables: false) + js_compiler.append_module( + js, + "discourse/pre-initializers/theme-#{theme_id}-translations", + include_variables: false, + ) rescue ThemeTranslationParser::InvalidYaml => e errors << e.message end @@ -248,13 +277,10 @@ class ThemeField < ActiveRecord::Base javascript_cache.source_map = js_compiler.source_map javascript_cache.save! doc = "" - if javascript_cache.content.present? - doc = - <<~HTML.html_safe + doc = <<~HTML.html_safe if javascript_cache.content.present? HTML - end [doc, errors&.join("\n")] end @@ -263,32 +289,32 @@ class ThemeField < ActiveRecord::Base errors = [] begin - ThemeSettingsParser.new(self).load do |name, default, type, opts| - setting = ThemeSetting.new(name: name, data_type: type, theme: theme) - translation_key = "themes.settings_errors" + ThemeSettingsParser + .new(self) + .load do |name, default, type, opts| + setting = ThemeSetting.new(name: name, data_type: type, theme: theme) + translation_key = "themes.settings_errors" - if setting.invalid? - setting.errors.details.each_pair do |attribute, _errors| - _errors.each do |hash| - errors << I18n.t("#{translation_key}.#{attribute}_#{hash[:error]}", name: name) + if setting.invalid? + setting.errors.details.each_pair do |attribute, _errors| + _errors.each do |hash| + errors << I18n.t("#{translation_key}.#{attribute}_#{hash[:error]}", name: name) + end end end - end - if default.nil? - errors << I18n.t("#{translation_key}.default_value_missing", name: name) - end + errors << I18n.t("#{translation_key}.default_value_missing", name: name) if default.nil? - if (min = opts[:min]) && (max = opts[:max]) - unless ThemeSetting.value_in_range?(default, (min..max), type) - errors << I18n.t("#{translation_key}.default_out_range", name: name) + if (min = opts[:min]) && (max = opts[:max]) + unless ThemeSetting.value_in_range?(default, (min..max), type) + errors << I18n.t("#{translation_key}.default_out_range", name: name) + end + end + + unless ThemeSetting.acceptable_value_for_type?(default, type) + errors << I18n.t("#{translation_key}.default_not_match_type", name: name) end end - - unless ThemeSetting.acceptable_value_for_type?(default, type) - errors << I18n.t("#{translation_key}.default_not_match_type", name: name) - end - end rescue ThemeSettingsParser::InvalidYaml => e errors << e.message end @@ -311,15 +337,15 @@ class ThemeField < ActiveRecord::Base end def self.html_fields - @html_fields ||= %w(body_tag head_tag header footer after_header) + @html_fields ||= %w[body_tag head_tag header footer after_header] end def self.scss_fields - @scss_fields ||= %w(scss embedded_scss color_definitions) + @scss_fields ||= %w[scss embedded_scss color_definitions] end def self.basic_targets - @basic_targets ||= %w(common desktop mobile) + @basic_targets ||= %w[common desktop mobile] end def basic_html_field? @@ -353,7 +379,8 @@ class ThemeField < ActiveRecord::Base end def svg_sprite_field? - ThemeField.theme_var_type_ids.include?(self.type_id) && self.name == SvgSprite.theme_sprite_variable_name + ThemeField.theme_var_type_ids.include?(self.type_id) && + self.name == SvgSprite.theme_sprite_variable_name end def ensure_baked! @@ -361,7 +388,8 @@ class ThemeField < ActiveRecord::Base return unless needs_baking if basic_html_field? || translation_field? - self.value_baked, self.error = translation_field? ? process_translation : process_html(self.value) + self.value_baked, self.error = + translation_field? ? process_translation : process_html(self.value) self.error = nil unless self.error.present? self.compiler_version = Theme.compiler_version DB.after_commit { CSP::Extension.clear_theme_extensions_cache! } @@ -385,13 +413,13 @@ class ThemeField < ActiveRecord::Base self.compiler_version = Theme.compiler_version end - if self.will_save_change_to_value_baked? || - self.will_save_change_to_compiler_version? || - self.will_save_change_to_error? - - self.update_columns(value_baked: value_baked, - compiler_version: compiler_version, - error: error) + if self.will_save_change_to_value_baked? || self.will_save_change_to_compiler_version? || + self.will_save_change_to_error? + self.update_columns( + value_baked: value_baked, + compiler_version: compiler_version, + error: error, + ) end end @@ -399,23 +427,25 @@ class ThemeField < ActiveRecord::Base prepended_scss ||= Stylesheet::Importer.new({}).prepended_scss self.theme.with_scss_load_paths do |load_paths| - Stylesheet::Compiler.compile("#{prepended_scss} #{self.theme.scss_variables.to_s} #{self.value}", + Stylesheet::Compiler.compile( + "#{prepended_scss} #{self.theme.scss_variables.to_s} #{self.value}", "#{Theme.targets[self.target_id]}.scss", theme: self.theme, - load_paths: load_paths + load_paths: load_paths, ) end end def compiled_css(prepended_scss) - css, _source_map = begin - compile_scss(prepended_scss) - rescue SassC::SyntaxError => e - # We don't want to raise a blocking error here - # admin theme editor or discourse_theme CLI will show it nonetheless - Rails.logger.error "SCSS compilation error: #{e.message}" - ["", nil] - end + css, _source_map = + begin + compile_scss(prepended_scss) + rescue SassC::SyntaxError => e + # We don't want to raise a blocking error here + # admin theme editor or discourse_theme CLI will show it nonetheless + Rails.logger.error "SCSS compilation error: #{e.message}" + ["", nil] + end css end @@ -450,7 +480,7 @@ class ThemeField < ActiveRecord::Base end class ThemeFileMatcher - OPTIONS = %i{name type target} + OPTIONS = %i[name type target] # regex: used to match file names to fields (import). # can contain named capture groups for name/type/target # canonical: a lambda which converts name/type/target @@ -480,55 +510,100 @@ class ThemeField < ActiveRecord::Base end def filename_from_opts(opts) - is_match = OPTIONS.all? do |option| - plural = :"#{option}s" - next true if @allowed_values[plural] == nil # Allows any value - next true if @allowed_values[plural].include?(opts[option]) # Value is allowed - end + is_match = + OPTIONS.all? do |option| + plural = :"#{option}s" + next true if @allowed_values[plural] == nil # Allows any value + next true if @allowed_values[plural].include?(opts[option]) # Value is allowed + end is_match ? @canonical.call(opts) : nil end end FILE_MATCHERS = [ - ThemeFileMatcher.new(regex: /^(?(?:mobile|desktop|common))\/(?(?:head_tag|header|after_header|body_tag|footer))\.html$/, - targets: [:mobile, :desktop, :common], names: ["head_tag", "header", "after_header", "body_tag", "footer"], types: :html, - canonical: -> (h) { "#{h[:target]}/#{h[:name]}.html" }), - ThemeFileMatcher.new(regex: /^(?(?:mobile|desktop|common))\/(?:\k)\.scss$/, - targets: [:mobile, :desktop, :common], names: "scss", types: :scss, - canonical: -> (h) { "#{h[:target]}/#{h[:target]}.scss" }), - ThemeFileMatcher.new(regex: /^common\/embedded\.scss$/, - targets: :common, names: "embedded_scss", types: :scss, - canonical: -> (h) { "common/embedded.scss" }), - ThemeFileMatcher.new(regex: /^common\/color_definitions\.scss$/, - targets: :common, names: "color_definitions", types: :scss, - canonical: -> (h) { "common/color_definitions.scss" }), - ThemeFileMatcher.new(regex: /^(?:scss|stylesheets)\/(?.+)\.scss$/, - targets: :extra_scss, names: nil, types: :scss, - canonical: -> (h) { "stylesheets/#{h[:name]}.scss" }), - ThemeFileMatcher.new(regex: /^javascripts\/(?.+)$/, - targets: :extra_js, names: nil, types: :js, - canonical: -> (h) { "javascripts/#{h[:name]}" }), - ThemeFileMatcher.new(regex: /^test\/(?.+)$/, - targets: :tests_js, names: nil, types: :js, - canonical: -> (h) { "test/#{h[:name]}" }), - ThemeFileMatcher.new(regex: /^settings\.ya?ml$/, - names: "yaml", types: :yaml, targets: :settings, - canonical: -> (h) { "settings.yml" }), - ThemeFileMatcher.new(regex: /^locales\/(?(?:#{I18n.available_locales.join("|")}))\.yml$/, - names: I18n.available_locales.map(&:to_s), types: :yaml, targets: :translations, - canonical: -> (h) { "locales/#{h[:name]}.yml" }), - ThemeFileMatcher.new(regex: /(?!)/, # Never match uploads by filename, they must be named in about.json - names: nil, types: :theme_upload_var, targets: :common, - canonical: -> (h) { "assets/#{h[:name]}#{File.extname(h[:filename])}" }), + ThemeFileMatcher.new( + regex: + %r{^(?(?:mobile|desktop|common))/(?(?:head_tag|header|after_header|body_tag|footer))\.html$}, + targets: %i[mobile desktop common], + names: %w[head_tag header after_header body_tag footer], + types: :html, + canonical: ->(h) { "#{h[:target]}/#{h[:name]}.html" }, + ), + ThemeFileMatcher.new( + regex: %r{^(?(?:mobile|desktop|common))/(?:\k)\.scss$}, + targets: %i[mobile desktop common], + names: "scss", + types: :scss, + canonical: ->(h) { "#{h[:target]}/#{h[:target]}.scss" }, + ), + ThemeFileMatcher.new( + regex: %r{^common/embedded\.scss$}, + targets: :common, + names: "embedded_scss", + types: :scss, + canonical: ->(h) { "common/embedded.scss" }, + ), + ThemeFileMatcher.new( + regex: %r{^common/color_definitions\.scss$}, + targets: :common, + names: "color_definitions", + types: :scss, + canonical: ->(h) { "common/color_definitions.scss" }, + ), + ThemeFileMatcher.new( + regex: %r{^(?:scss|stylesheets)/(?.+)\.scss$}, + targets: :extra_scss, + names: nil, + types: :scss, + canonical: ->(h) { "stylesheets/#{h[:name]}.scss" }, + ), + ThemeFileMatcher.new( + regex: %r{^javascripts/(?.+)$}, + targets: :extra_js, + names: nil, + types: :js, + canonical: ->(h) { "javascripts/#{h[:name]}" }, + ), + ThemeFileMatcher.new( + regex: %r{^test/(?.+)$}, + targets: :tests_js, + names: nil, + types: :js, + canonical: ->(h) { "test/#{h[:name]}" }, + ), + ThemeFileMatcher.new( + regex: /^settings\.ya?ml$/, + names: "yaml", + types: :yaml, + targets: :settings, + canonical: ->(h) { "settings.yml" }, + ), + ThemeFileMatcher.new( + regex: %r{^locales/(?(?:#{I18n.available_locales.join("|")}))\.yml$}, + names: I18n.available_locales.map(&:to_s), + types: :yaml, + targets: :translations, + canonical: ->(h) { "locales/#{h[:name]}.yml" }, + ), + ThemeFileMatcher.new( + regex: /(?!)/, # Never match uploads by filename, they must be named in about.json + names: nil, + types: :theme_upload_var, + targets: :common, + canonical: ->(h) { "assets/#{h[:name]}#{File.extname(h[:filename])}" }, + ), ] # For now just work for standard fields def file_path FILE_MATCHERS.each do |matcher| - if filename = matcher.filename_from_opts(target: target_name.to_sym, - name: name, - type: ThemeField.types[type_id], - filename: upload&.original_filename) + if filename = + matcher.filename_from_opts( + target: target_name.to_sym, + name: name, + type: ThemeField.types[type_id], + filename: upload&.original_filename, + ) return filename end end @@ -546,11 +621,19 @@ class ThemeField < ActiveRecord::Base def dependent_fields if extra_scss_field? - return theme.theme_fields.where(target_id: ThemeField.basic_targets.map { |t| Theme.targets[t.to_sym] }, - name: ThemeField.scss_fields) + return( + theme.theme_fields.where( + target_id: ThemeField.basic_targets.map { |t| Theme.targets[t.to_sym] }, + name: ThemeField.scss_fields, + ) + ) elsif settings_field? - return theme.theme_fields.where(target_id: ThemeField.basic_targets.map { |t| Theme.targets[t.to_sym] }, - name: ThemeField.scss_fields + ThemeField.html_fields) + return( + theme.theme_fields.where( + target_id: ThemeField.basic_targets.map { |t| Theme.targets[t.to_sym] }, + name: ThemeField.scss_fields + ThemeField.html_fields, + ) + ) end ThemeField.none end @@ -561,7 +644,8 @@ class ThemeField < ActiveRecord::Base end before_save do - if (will_save_change_to_value? || will_save_change_to_upload_id?) && !will_save_change_to_value_baked? + if (will_save_change_to_value? || will_save_change_to_upload_id?) && + !will_save_change_to_value_baked? self.value_baked = nil end if upload && upload.extension == "js" @@ -572,29 +656,19 @@ class ThemeField < ActiveRecord::Base end end - after_save do - dependent_fields.each(&:invalidate_baked!) - end + after_save { dependent_fields.each(&:invalidate_baked!) } - after_destroy do - if svg_sprite_field? - DB.after_commit { SvgSprite.expire_cache } - end - end + after_destroy { DB.after_commit { SvgSprite.expire_cache } if svg_sprite_field? } private - JAVASCRIPT_TYPES = %w( - text/javascript - application/javascript - application/ecmascript - ) + JAVASCRIPT_TYPES = %w[text/javascript application/javascript application/ecmascript] def inline_javascript?(node) - if node['src'].present? + if node["src"].present? false - elsif node['type'].present? - JAVASCRIPT_TYPES.include?(node['type'].downcase) + elsif node["type"].present? + JAVASCRIPT_TYPES.include?(node["type"].downcase) else true end diff --git a/app/models/theme_modifier_set.rb b/app/models/theme_modifier_set.rb index 781ea42f51f..1d1c8e59f2b 100644 --- a/app/models/theme_modifier_set.rb +++ b/app/models/theme_modifier_set.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class ThemeModifierSet < ActiveRecord::Base - class ThemeModifierSetError < StandardError; end + class ThemeModifierSetError < StandardError + end belongs_to :theme @@ -26,12 +27,8 @@ class ThemeModifierSet < ActiveRecord::Base end after_save do - if saved_change_to_svg_icons? - SvgSprite.expire_cache - end - if saved_change_to_csp_extensions? - CSP::Extension.clear_theme_extensions_cache! - end + SvgSprite.expire_cache if saved_change_to_svg_icons? + CSP::Extension.clear_theme_extensions_cache! if saved_change_to_csp_extensions? end # Given the ids of multiple active themes / theme components, this function @@ -39,7 +36,11 @@ class ThemeModifierSet < ActiveRecord::Base def self.resolve_modifier_for_themes(theme_ids, modifier_name) return nil if !(config = self.modifiers[modifier_name]) - all_values = self.where(theme_id: theme_ids).where.not(modifier_name => nil).map { |s| s.public_send(modifier_name) } + all_values = + self + .where(theme_id: theme_ids) + .where.not(modifier_name => nil) + .map { |s| s.public_send(modifier_name) } case config[:type] when :boolean all_values.any? @@ -55,17 +56,21 @@ class ThemeModifierSet < ActiveRecord::Base return if array.nil? - array.map do |dimension| - parts = dimension.split("x") - next if parts.length != 2 - [parts[0].to_i, parts[1].to_i] - end.filter(&:present?) + array + .map do |dimension| + parts = dimension.split("x") + next if parts.length != 2 + [parts[0].to_i, parts[1].to_i] + end + .filter(&:present?) end def topic_thumbnail_sizes=(val) return write_attribute(:topic_thumbnail_sizes, val) if val.nil? return write_attribute(:topic_thumbnail_sizes, val) if !val.is_a?(Array) - return write_attribute(:topic_thumbnail_sizes, val) if !val.all? { |v| v.is_a?(Array) && v.length == 2 } + if !val.all? { |v| v.is_a?(Array) && v.length == 2 } + return write_attribute(:topic_thumbnail_sizes, val) + end super(val.map { |dim| "#{dim[0]}x#{dim[1]}" }) end @@ -77,7 +82,7 @@ class ThemeModifierSet < ActiveRecord::Base def self.load_modifiers hash = {} columns_hash.each do |column_name, info| - next if ["id", "theme_id"].include?(column_name) + next if %w[id theme_id].include?(column_name) type = nil if info.type == :string && info.array? @@ -85,7 +90,9 @@ class ThemeModifierSet < ActiveRecord::Base elsif info.type == :boolean && !info.array? type = :boolean else - raise ThemeModifierSetError "Invalid theme modifier column type" if ![:boolean, :string].include?(info.type) + if !%i[boolean string].include?(info.type) + raise ThemeModifierSetError "Invalid theme modifier column type" + end end hash[column_name.to_sym] = { type: type } diff --git a/app/models/top_menu_item.rb b/app/models/top_menu_item.rb index fad4c9b1ad1..8334f73104f 100644 --- a/app/models/top_menu_item.rb +++ b/app/models/top_menu_item.rb @@ -26,7 +26,7 @@ # item.specific_category # => "hardware" class TopMenuItem def initialize(value) - parts = value.split(',') + parts = value.split(",") @name = parts[0] @filter = initialize_filter(parts[1]) end @@ -38,18 +38,18 @@ class TopMenuItem end def has_specific_category? - name.split('/')[0] == 'category' + name.split("/")[0] == "category" end def specific_category - name.split('/')[1] + name.split("/")[1] end private def initialize_filter(value) if value - if value.start_with?('-') + if value.start_with?("-") value[1..-1] # all but the leading - else Rails.logger.warn "WARNING: found top_menu_item with invalid filter, ignoring '#{value}'..." diff --git a/app/models/top_topic.rb b/app/models/top_topic.rb index 3f09fda10e7..975adc8ec72 100644 --- a/app/models/top_topic.rb +++ b/app/models/top_topic.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TopTopic < ActiveRecord::Base - belongs_to :topic # The top topics we want to refresh often @@ -16,13 +15,9 @@ class TopTopic < ActiveRecord::Base # We don't have to refresh these as often def self.refresh_older! - older_periods = periods - [:daily, :all] + older_periods = periods - %i[daily all] - transaction do - older_periods.each do |period| - update_counts_and_compute_scores_for(period) - end - end + transaction { older_periods.each { |period| update_counts_and_compute_scores_for(period) } } compute_top_score_for(:all) end @@ -33,16 +28,11 @@ class TopTopic < ActiveRecord::Base end def self.periods - @@periods ||= [:all, :yearly, :quarterly, :monthly, :weekly, :daily].freeze + @@periods ||= %i[all yearly quarterly monthly weekly daily].freeze end def self.sorted_periods - ascending_periods ||= Enum.new(daily: 1, - weekly: 2, - monthly: 3, - quarterly: 4, - yearly: 5, - all: 6) + ascending_periods ||= Enum.new(daily: 1, weekly: 2, monthly: 3, quarterly: 4, yearly: 5, all: 6) end def self.score_column_for_period(period) @@ -52,25 +42,26 @@ class TopTopic < ActiveRecord::Base def self.validate_period(period) if period.blank? || !periods.include?(period.to_sym) - raise Discourse::InvalidParameters.new("Invalid period. Valid periods are #{periods.join(", ")}") + raise Discourse::InvalidParameters.new( + "Invalid period. Valid periods are #{periods.join(", ")}", + ) end end private def self.sort_orders - @@sort_orders ||= [:posts, :views, :likes, :op_likes].freeze + @@sort_orders ||= %i[posts views likes op_likes].freeze end def self.update_counts_and_compute_scores_for(period) - sort_orders.each do |sort| - TopTopic.public_send("update_#{sort}_count_for", period) - end + sort_orders.each { |sort| TopTopic.public_send("update_#{sort}_count_for", period) } compute_top_score_for(period) end def self.remove_invisible_topics - DB.exec("WITH category_definition_topic_ids AS ( + DB.exec( + "WITH category_definition_topic_ids AS ( SELECT COALESCE(topic_id, 0) AS id FROM categories ), invisible_topic_ids AS ( SELECT id @@ -83,11 +74,13 @@ class TopTopic < ActiveRecord::Base ) DELETE FROM top_topics WHERE topic_id IN (SELECT id FROM invisible_topic_ids)", - private_message: Archetype::private_message) + private_message: Archetype.private_message, + ) end def self.add_new_visible_topics - DB.exec("WITH category_definition_topic_ids AS ( + DB.exec( + "WITH category_definition_topic_ids AS ( SELECT COALESCE(topic_id, 0) AS id FROM categories ), visible_topics AS ( SELECT t.id @@ -102,11 +95,13 @@ class TopTopic < ActiveRecord::Base ) INSERT INTO top_topics (topic_id) SELECT id FROM visible_topics", - private_message: Archetype::private_message) + private_message: Archetype.private_message, + ) end def self.update_posts_count_for(period) - sql = "SELECT topic_id, GREATEST(COUNT(*), 1) AS count + sql = + "SELECT topic_id, GREATEST(COUNT(*), 1) AS count FROM posts WHERE created_at >= :from AND deleted_at IS NULL @@ -119,7 +114,8 @@ class TopTopic < ActiveRecord::Base end def self.update_views_count_for(period) - sql = "SELECT topic_id, COUNT(*) AS count + sql = + "SELECT topic_id, COUNT(*) AS count FROM topic_views WHERE viewed_at >= :from GROUP BY topic_id" @@ -128,7 +124,8 @@ class TopTopic < ActiveRecord::Base end def self.update_likes_count_for(period) - sql = "SELECT topic_id, SUM(like_count) AS count + sql = + "SELECT topic_id, SUM(like_count) AS count FROM posts WHERE created_at >= :from AND deleted_at IS NULL @@ -140,7 +137,8 @@ class TopTopic < ActiveRecord::Base end def self.update_op_likes_count_for(period) - sql = "SELECT topic_id, like_count AS count + sql = + "SELECT topic_id, like_count AS count FROM posts WHERE created_at >= :from AND post_number = 1 @@ -152,18 +150,19 @@ class TopTopic < ActiveRecord::Base end def self.compute_top_score_for(period) - log_views_multiplier = SiteSetting.top_topics_formula_log_views_multiplier.to_f log_views_multiplier = 2 if log_views_multiplier == 0 first_post_likes_multiplier = SiteSetting.top_topics_formula_first_post_likes_multiplier.to_f first_post_likes_multiplier = 0.5 if first_post_likes_multiplier == 0 - least_likes_per_post_multiplier = SiteSetting.top_topics_formula_least_likes_per_post_multiplier.to_f + least_likes_per_post_multiplier = + SiteSetting.top_topics_formula_least_likes_per_post_multiplier.to_f least_likes_per_post_multiplier = 3 if least_likes_per_post_multiplier == 0 if period == :all - top_topics = "( + top_topics = + "( SELECT t.like_count all_likes_count, t.id topic_id, t.posts_count all_posts_count, @@ -213,22 +212,29 @@ class TopTopic < ActiveRecord::Base def self.start_of(period) case period - when :yearly then 1.year.ago - when :monthly then 1.month.ago - when :quarterly then 3.months.ago - when :weekly then 1.week.ago - when :daily then 1.day.ago + when :yearly + 1.year.ago + when :monthly + 1.month.ago + when :quarterly + 3.months.ago + when :weekly + 1.week.ago + when :daily + 1.day.ago end end def self.update_top_topics(period, sort, inner_join) - DB.exec("UPDATE top_topics + DB.exec( + "UPDATE top_topics SET #{period}_#{sort}_count = c.count FROM top_topics tt INNER JOIN (#{inner_join}) c ON tt.topic_id = c.topic_id WHERE tt.topic_id = top_topics.topic_id AND tt.#{period}_#{sort}_count <> c.count", - from: start_of(period)) + from: start_of(period), + ) end end diff --git a/app/models/topic.rb b/app/models/topic.rb index 8c407873e16..b15bbc519f6 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class Topic < ActiveRecord::Base - class UserExists < StandardError; end - class NotAllowed < StandardError; end + class UserExists < StandardError + end + class NotAllowed < StandardError + end include RateLimiter::OnCreateRecord include HasCustomFields include Trashable @@ -14,7 +16,7 @@ class Topic < ActiveRecord::Base self.ignored_columns = [ "avg_time", # TODO(2021-01-04): remove - "image_url" # TODO(2021-06-01): remove + "image_url", # TODO(2021-06-01): remove ] def_delegator :featured_users, :user_ids, :featured_user_ids @@ -37,7 +39,7 @@ class Topic < ActiveRecord::Base end def self.thumbnail_sizes - [ self.share_thumbnail_size ] + DiscoursePluginRegistry.topic_thumbnail_sizes + [self.share_thumbnail_size] + DiscoursePluginRegistry.topic_thumbnail_sizes end def thumbnail_job_redis_key(sizes) @@ -49,7 +51,9 @@ class Topic < ActiveRecord::Base return nil unless original.read_attribute(:width) && original.read_attribute(:height) thumbnail_sizes = Topic.thumbnail_sizes + extra_sizes - topic_thumbnails.filter { |record| thumbnail_sizes.include?([record.max_width, record.max_height]) } + topic_thumbnails.filter do |record| + thumbnail_sizes.include?([record.max_width, record.max_height]) + end end def thumbnail_info(enqueue_if_missing: false, extra_sizes: []) @@ -59,12 +63,12 @@ class Topic < ActiveRecord::Base infos = [] infos << { # Always add original - max_width: nil, - max_height: nil, - width: original.width, - height: original.height, - url: original.url - } + max_width: nil, + max_height: nil, + width: original.width, + height: original.height, + url: original.url, + } records = filtered_topic_thumbnails(extra_sizes: extra_sizes) @@ -76,15 +80,14 @@ class Topic < ActiveRecord::Base max_height: record.max_height, width: record.optimized_image&.width, height: record.optimized_image&.height, - url: record.optimized_image&.url + url: record.optimized_image&.url, } end thumbnail_sizes = Topic.thumbnail_sizes + extra_sizes - if SiteSetting.create_thumbnails && - enqueue_if_missing && - records.length < thumbnail_sizes.length && - Discourse.redis.set(thumbnail_job_redis_key(extra_sizes), 1, nx: true, ex: 1.minute) + if SiteSetting.create_thumbnails && enqueue_if_missing && + records.length < thumbnail_sizes.length && + Discourse.redis.set(thumbnail_job_redis_key(extra_sizes), 1, nx: true, ex: 1.minute) Jobs.enqueue(:generate_topic_thumbnails, { topic_id: id, extra_sizes: extra_sizes }) end @@ -106,19 +109,17 @@ class Topic < ActiveRecord::Base end def image_url(enqueue_if_missing: false) - thumbnail = topic_thumbnails.detect do |record| - record.max_width == Topic.share_thumbnail_size[0] && - record.max_height == Topic.share_thumbnail_size[1] - end + thumbnail = + topic_thumbnails.detect do |record| + record.max_width == Topic.share_thumbnail_size[0] && + record.max_height == Topic.share_thumbnail_size[1] + end - if thumbnail.nil? && - image_upload && - SiteSetting.create_thumbnails && - image_upload.filesize < SiteSetting.max_image_size_kb.kilobytes && - image_upload.read_attribute(:width) && - image_upload.read_attribute(:height) && - enqueue_if_missing && - Discourse.redis.set(thumbnail_job_redis_key([]), 1, nx: true, ex: 1.minute) + if thumbnail.nil? && image_upload && SiteSetting.create_thumbnails && + image_upload.filesize < SiteSetting.max_image_size_kb.kilobytes && + image_upload.read_attribute(:width) && image_upload.read_attribute(:height) && + enqueue_if_missing && + Discourse.redis.set(thumbnail_job_redis_key([]), 1, nx: true, ex: 1.minute) Jobs.enqueue(:generate_topic_thumbnails, { topic_id: id }) end @@ -169,34 +170,42 @@ class Topic < ActiveRecord::Base rate_limit :limit_topics_per_day rate_limit :limit_private_messages_per_day - validates :title, if: Proc.new { |t| t.new_record? || t.title_changed? }, - presence: true, - topic_title_length: true, - censored_words: true, - watched_words: true, - quality_title: { unless: :private_message? }, - max_emojis: true, - unique_among: { unless: Proc.new { |t| (SiteSetting.allow_duplicate_topic_titles? || t.private_message?) }, - message: :has_already_been_used, - allow_blank: true, - case_sensitive: false, - collection: Proc.new { |t| - SiteSetting.allow_duplicate_topic_titles_category? ? - Topic.listable_topics.where("category_id = ?", t.category_id) : - Topic.listable_topics - } - } + validates :title, + if: Proc.new { |t| t.new_record? || t.title_changed? }, + presence: true, + topic_title_length: true, + censored_words: true, + watched_words: true, + quality_title: { + unless: :private_message?, + }, + max_emojis: true, + unique_among: { + unless: + Proc.new { |t| (SiteSetting.allow_duplicate_topic_titles? || t.private_message?) }, + message: :has_already_been_used, + allow_blank: true, + case_sensitive: false, + collection: + Proc.new { |t| + if SiteSetting.allow_duplicate_topic_titles_category? + Topic.listable_topics.where("category_id = ?", t.category_id) + else + Topic.listable_topics + end + }, + } validates :category_id, presence: true, exclusion: { - in: Proc.new { [SiteSetting.uncategorized_category_id] } + in: Proc.new { [SiteSetting.uncategorized_category_id] }, }, - if: Proc.new { |t| - (t.new_record? || t.category_id_changed?) && - !SiteSetting.allow_uncategorized_topics && - (t.archetype.nil? || t.regular?) - } + if: + Proc.new { |t| + (t.new_record? || t.category_id_changed?) && + !SiteSetting.allow_uncategorized_topics && (t.archetype.nil? || t.regular?) + } validates :featured_link, allow_nil: true, url: true validate if: :featured_link do @@ -205,10 +214,22 @@ class Topic < ActiveRecord::Base end end - validates :external_id, allow_nil: true, uniqueness: { case_sensitive: false }, length: { maximum: EXTERNAL_ID_MAX_LENGTH }, format: { with: /\A[\w-]+\z/ } + validates :external_id, + allow_nil: true, + uniqueness: { + case_sensitive: false, + }, + length: { + maximum: EXTERNAL_ID_MAX_LENGTH, + }, + format: { + with: /\A[\w-]+\z/, + } before_validation do - self.title = TextCleaner.clean_title(TextSentinel.title_sentinel(title).text) if errors[:title].empty? + self.title = TextCleaner.clean_title(TextSentinel.title_sentinel(title).text) if errors[ + :title + ].empty? self.featured_link = self.featured_link.strip.presence if self.featured_link end @@ -241,11 +262,11 @@ class Topic < ActiveRecord::Base has_one :published_page belongs_to :user - belongs_to :last_poster, class_name: 'User', foreign_key: :last_post_user_id - belongs_to :featured_user1, class_name: 'User', foreign_key: :featured_user1_id - belongs_to :featured_user2, class_name: 'User', foreign_key: :featured_user2_id - belongs_to :featured_user3, class_name: 'User', foreign_key: :featured_user3_id - belongs_to :featured_user4, class_name: 'User', foreign_key: :featured_user4_id + belongs_to :last_poster, class_name: "User", foreign_key: :last_post_user_id + belongs_to :featured_user1, class_name: "User", foreign_key: :featured_user1_id + belongs_to :featured_user2, class_name: "User", foreign_key: :featured_user2_id + belongs_to :featured_user3, class_name: "User", foreign_key: :featured_user3_id + belongs_to :featured_user4, class_name: "User", foreign_key: :featured_user4_id has_many :topic_users has_many :dismissed_topic_users @@ -257,12 +278,12 @@ class Topic < ActiveRecord::Base has_many :user_profiles has_one :user_warning - has_one :first_post, -> { where post_number: 1 }, class_name: 'Post' + has_one :first_post, -> { where post_number: 1 }, class_name: "Post" has_one :topic_search_data has_one :topic_embed, dependent: :destroy has_one :linked_topic, dependent: :destroy - belongs_to :image_upload, class_name: 'Upload' + belongs_to :image_upload, class_name: "Upload" has_many :topic_thumbnails, through: :image_upload # When we want to temporarily attach some data to a forum topic (usually before serialization) @@ -270,7 +291,7 @@ class Topic < ActiveRecord::Base attr_accessor :category_user_data attr_accessor :dismissed - attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code + attr_accessor :posters # TODO: can replace with posters_summary once we remove old list code attr_accessor :participants attr_accessor :topic_list attr_accessor :meta_data @@ -278,7 +299,7 @@ class Topic < ActiveRecord::Base attr_accessor :import_mode # set to true to optimize creation and save for imports # The regular order - scope :topic_list_order, -> { order('topics.bumped_at desc') } + scope :topic_list_order, -> { order("topics.bumped_at desc") } # Return private message topics scope :private_messages, -> { where(archetype: Archetype.private_message) } @@ -295,50 +316,57 @@ class Topic < ActiveRecord::Base JOIN group_users gu ON gu.user_id = :user_id AND gu.group_id = tg.group_id SQL - scope :private_messages_for_user, ->(user) do - private_messages.where( - "topics.id IN (#{PRIVATE_MESSAGES_SQL_USER}) + scope :private_messages_for_user, + ->(user) { + private_messages.where( + "topics.id IN (#{PRIVATE_MESSAGES_SQL_USER}) OR topics.id IN (#{PRIVATE_MESSAGES_SQL_GROUP})", - user_id: user.id - ) - end + user_id: user.id, + ) + } - scope :listable_topics, -> { where('topics.archetype <> ?', Archetype.private_message) } + scope :listable_topics, -> { where("topics.archetype <> ?", Archetype.private_message) } - scope :by_newest, -> { order('topics.created_at desc, topics.id desc') } + scope :by_newest, -> { order("topics.created_at desc, topics.id desc") } scope :visible, -> { where(visible: true) } - scope :created_since, lambda { |time_ago| where('topics.created_at > ?', time_ago) } + scope :created_since, lambda { |time_ago| where("topics.created_at > ?", time_ago) } scope :exclude_scheduled_bump_topics, -> { where.not(id: TopicTimer.scheduled_bump_topics) } - scope :secured, lambda { |guardian = nil| - ids = guardian.secure_category_ids if guardian + scope :secured, + lambda { |guardian = nil| + ids = guardian.secure_category_ids if guardian - # Query conditions - condition = if ids.present? - ["NOT read_restricted OR id IN (:cats)", cats: ids] - else - ["NOT read_restricted"] - end + # Query conditions + condition = + if ids.present? + ["NOT read_restricted OR id IN (:cats)", cats: ids] + else + ["NOT read_restricted"] + end - where("topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE #{condition[0]})", condition[1]) - } + where( + "topics.category_id IS NULL OR topics.category_id IN (SELECT id FROM categories WHERE #{condition[0]})", + condition[1], + ) + } - scope :in_category_and_subcategories, lambda { |category_id| - where("topics.category_id IN (?)", Category.subcategory_ids(category_id.to_i)) if category_id - } + scope :in_category_and_subcategories, + lambda { |category_id| + if category_id + where("topics.category_id IN (?)", Category.subcategory_ids(category_id.to_i)) + end + } - scope :with_subtype, ->(subtype) { where('topics.subtype = ?', subtype) } + scope :with_subtype, ->(subtype) { where("topics.subtype = ?", subtype) } attr_accessor :ignore_category_auto_close attr_accessor :skip_callbacks attr_accessor :advance_draft - before_create do - initialize_default_values - end + before_create { initialize_default_values } after_create do unless skip_callbacks @@ -348,13 +376,9 @@ class Topic < ActiveRecord::Base end before_save do - unless skip_callbacks - ensure_topic_has_a_category - end + ensure_topic_has_a_category unless skip_callbacks - if title_changed? - write_attribute(:fancy_title, Topic.fancy_title(title)) - end + write_attribute(:fancy_title, Topic.fancy_title(title)) if title_changed? if category_id_changed? || new_record? inherit_auto_close_from_category @@ -369,7 +393,8 @@ class Topic < ActiveRecord::Base ApplicationController.banner_json_cache.clear end - if tags_changed || saved_change_to_attribute?(:category_id) || saved_change_to_attribute?(:title) + if tags_changed || saved_change_to_attribute?(:category_id) || + saved_change_to_attribute?(:title) SearchIndexer.queue_post_reindex(self.id) if tags_changed @@ -418,11 +443,11 @@ class Topic < ActiveRecord::Base end def self.top_viewed(max = 10) - Topic.listable_topics.visible.secured.order('views desc').limit(max) + Topic.listable_topics.visible.secured.order("views desc").limit(max) end def self.recent(max = 10) - Topic.listable_topics.visible.secured.order('created_at desc').limit(max) + Topic.listable_topics.visible.secured.order("created_at desc").limit(max) end def self.count_exceeds_minimum? @@ -430,7 +455,11 @@ class Topic < ActiveRecord::Base end def best_post - posts.where(post_type: Post.types[:regular], user_deleted: false).order('score desc nulls last').limit(1).first + posts + .where(post_type: Post.types[:regular], user_deleted: false) + .order("score desc nulls last") + .limit(1) + .first end def self.has_flag_scope @@ -447,8 +476,11 @@ class Topic < ActiveRecord::Base # all users (in groups or directly targeted) that are going to get the pm def all_allowed_users - moderators_sql = " UNION #{User.moderators.to_sql}" if private_message? && (has_flags? || is_official_warning?) - User.from("(#{allowed_users.to_sql} UNION #{allowed_group_users.to_sql}#{moderators_sql}) as users") + moderators_sql = " UNION #{User.moderators.to_sql}" if private_message? && + (has_flags? || is_official_warning?) + User.from( + "(#{allowed_users.to_sql} UNION #{allowed_group_users.to_sql}#{moderators_sql}) as users", + ) end # Additional rate limits on topics: per day and private messages per day @@ -482,7 +514,11 @@ class Topic < ActiveRecord::Base if !new_record? && !Discourse.readonly_mode? # make sure data is set in table, this also allows us to change algorithm # by simply nulling this column - DB.exec("UPDATE topics SET fancy_title = :fancy_title where id = :id", id: self.id, fancy_title: fancy_title) + DB.exec( + "UPDATE topics SET fancy_title = :fancy_title where id = :id", + id: self.id, + fancy_title: fancy_title, + ) end end @@ -494,25 +530,34 @@ class Topic < ActiveRecord::Base opts = opts || {} period = ListController.best_period_for(since) - topics = Topic - .visible - .secured(Guardian.new(user)) - .joins("LEFT OUTER JOIN topic_users ON topic_users.topic_id = topics.id AND topic_users.user_id = #{user.id.to_i}") - .joins("LEFT OUTER JOIN category_users ON category_users.category_id = topics.category_id AND category_users.user_id = #{user.id.to_i}") - .joins("LEFT OUTER JOIN users ON users.id = topics.user_id") - .where(closed: false, archived: false) - .where("COALESCE(topic_users.notification_level, 1) <> ?", TopicUser.notification_levels[:muted]) - .created_since(since) - .where('topics.created_at < ?', (SiteSetting.editing_grace_period || 0).seconds.ago) - .listable_topics - .includes(:category) + topics = + Topic + .visible + .secured(Guardian.new(user)) + .joins( + "LEFT OUTER JOIN topic_users ON topic_users.topic_id = topics.id AND topic_users.user_id = #{user.id.to_i}", + ) + .joins( + "LEFT OUTER JOIN category_users ON category_users.category_id = topics.category_id AND category_users.user_id = #{user.id.to_i}", + ) + .joins("LEFT OUTER JOIN users ON users.id = topics.user_id") + .where(closed: false, archived: false) + .where( + "COALESCE(topic_users.notification_level, 1) <> ?", + TopicUser.notification_levels[:muted], + ) + .created_since(since) + .where("topics.created_at < ?", (SiteSetting.editing_grace_period || 0).seconds.ago) + .listable_topics + .includes(:category) unless opts[:include_tl0] || user.user_option.try(:include_tl0_in_digests) topics = topics.where("COALESCE(users.trust_level, 0) > 0") end if !!opts[:top_order] - topics = topics.joins("LEFT OUTER JOIN top_topics ON top_topics.topic_id = topics.id").order(<<~SQL) + topics = + topics.joins("LEFT OUTER JOIN top_topics ON top_topics.topic_id = topics.id").order(<<~SQL) COALESCE(topic_users.notification_level, 1) DESC, COALESCE(category_users.notification_level, 1) DESC, COALESCE(top_topics.#{TopTopic.score_column_for_period(period)}, 0) DESC, @@ -520,27 +565,34 @@ class Topic < ActiveRecord::Base SQL end - if opts[:limit] - topics = topics.limit(opts[:limit]) - end + topics = topics.limit(opts[:limit]) if opts[:limit] # Remove category topics category_topic_ids = Category.pluck(:topic_id).compact! - if category_topic_ids.present? - topics = topics.where("topics.id NOT IN (?)", category_topic_ids) - end + topics = topics.where("topics.id NOT IN (?)", category_topic_ids) if category_topic_ids.present? # Remove muted and shared draft categories - remove_category_ids = CategoryUser.where(user_id: user.id, notification_level: CategoryUser.notification_levels[:muted]).pluck(:category_id) + remove_category_ids = + CategoryUser.where( + user_id: user.id, + notification_level: CategoryUser.notification_levels[:muted], + ).pluck(:category_id) if SiteSetting.digest_suppress_categories.present? - topics = topics.where("topics.category_id NOT IN (?)", SiteSetting.digest_suppress_categories.split("|").map(&:to_i)) - end - if SiteSetting.shared_drafts_enabled? - remove_category_ids << SiteSetting.shared_drafts_category + topics = + topics.where( + "topics.category_id NOT IN (?)", + SiteSetting.digest_suppress_categories.split("|").map(&:to_i), + ) end + remove_category_ids << SiteSetting.shared_drafts_category if SiteSetting.shared_drafts_enabled? if remove_category_ids.present? remove_category_ids.uniq! - topics = topics.where("topic_users.notification_level != ? OR topics.category_id NOT IN (?)", TopicUser.notification_levels[:muted], remove_category_ids) + topics = + topics.where( + "topic_users.notification_level != ? OR topics.category_id NOT IN (?)", + TopicUser.notification_levels[:muted], + remove_category_ids, + ) end # Remove muted tags @@ -548,9 +600,12 @@ class Topic < ActiveRecord::Base unless muted_tag_ids.empty? # If multiple tags per topic, include topics with tags that aren't muted, # and don't forget untagged topics. - topics = topics.where( - "EXISTS ( SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = topics.id AND tag_id NOT IN (?) ) - OR NOT EXISTS (SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = topics.id)", muted_tag_ids) + topics = + topics.where( + "EXISTS ( SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = topics.id AND tag_id NOT IN (?) ) + OR NOT EXISTS (SELECT 1 FROM topic_tags WHERE topic_tags.topic_id = topics.id)", + muted_tag_ids, + ) end topics @@ -585,10 +640,23 @@ class Topic < ActiveRecord::Base ((Time.zone.now - created_at) / 1.minute).round end - def self.listable_count_per_day(start_date, end_date, category_id = nil, include_subcategories = false) - result = listable_topics.where("topics.created_at >= ? AND topics.created_at <= ?", start_date, end_date) - result = result.group('date(topics.created_at)').order('date(topics.created_at)') - result = result.where(category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id) if category_id + def self.listable_count_per_day( + start_date, + end_date, + category_id = nil, + include_subcategories = false + ) + result = + listable_topics.where( + "topics.created_at >= ? AND topics.created_at <= ?", + start_date, + end_date, + ) + result = result.group("date(topics.created_at)").order("date(topics.created_at)") + result = + result.where( + category_id: include_subcategories ? Category.subcategory_ids(category_id) : category_id, + ) if category_id result.count end @@ -613,20 +681,16 @@ class Topic < ActiveRecord::Base return [] if search_data.blank? - tsquery = Search.set_tsquery_weight_filter(search_data, 'A') + tsquery = Search.set_tsquery_weight_filter(search_data, "A") if raw.present? - cooked = SearchIndexer::HtmlScrubber.scrub( - PrettyText.cook(raw[0...MAX_SIMILAR_BODY_LENGTH].strip) - ) + cooked = + SearchIndexer::HtmlScrubber.scrub(PrettyText.cook(raw[0...MAX_SIMILAR_BODY_LENGTH].strip)) prepared_data = cooked.present? && Search.prepare_data(cooked) if prepared_data.present? - raw_tsquery = Search.set_tsquery_weight_filter( - prepared_data, - 'B' - ) + raw_tsquery = Search.set_tsquery_weight_filter(prepared_data, "B") tsquery = "#{tsquery} & #{raw_tsquery}" end @@ -636,46 +700,62 @@ class Topic < ActiveRecord::Base guardian = Guardian.new(user) - excluded_category_ids_sql = Category.secured(guardian).where(search_priority: Searchable::PRIORITIES[:ignore]).select(:id).to_sql + excluded_category_ids_sql = + Category + .secured(guardian) + .where(search_priority: Searchable::PRIORITIES[:ignore]) + .select(:id) + .to_sql - if user - excluded_category_ids_sql = <<~SQL + excluded_category_ids_sql = <<~SQL if user #{excluded_category_ids_sql} UNION #{CategoryUser.muted_category_ids_query(user, include_direct: true).select("categories.id").to_sql} SQL - end - candidates = Topic - .visible - .listable_topics - .secured(guardian) - .joins("JOIN topic_search_data s ON topics.id = s.topic_id") - .joins("LEFT JOIN categories c ON topics.id = c.topic_id") - .where("search_data @@ #{tsquery}") - .where("c.topic_id IS NULL") - .where("topics.category_id NOT IN (#{excluded_category_ids_sql})") - .order("ts_rank(search_data, #{tsquery}) DESC") - .limit(SiteSetting.max_similar_results * 3) + candidates = + Topic + .visible + .listable_topics + .secured(guardian) + .joins("JOIN topic_search_data s ON topics.id = s.topic_id") + .joins("LEFT JOIN categories c ON topics.id = c.topic_id") + .where("search_data @@ #{tsquery}") + .where("c.topic_id IS NULL") + .where("topics.category_id NOT IN (#{excluded_category_ids_sql})") + .order("ts_rank(search_data, #{tsquery}) DESC") + .limit(SiteSetting.max_similar_results * 3) candidate_ids = candidates.pluck(:id) return [] if candidate_ids.blank? - similars = Topic - .joins("JOIN posts AS p ON p.topic_id = topics.id AND p.post_number = 1") - .where("topics.id IN (?)", candidate_ids) - .order("similarity DESC") - .limit(SiteSetting.max_similar_results) + similars = + Topic + .joins("JOIN posts AS p ON p.topic_id = topics.id AND p.post_number = 1") + .where("topics.id IN (?)", candidate_ids) + .order("similarity DESC") + .limit(SiteSetting.max_similar_results) if raw.present? - similars - .select(DB.sql_fragment("topics.*, similarity(topics.title, :title) + similarity(p.raw, :raw) AS similarity, p.cooked AS blurb", title: title, raw: raw)) - .where("similarity(topics.title, :title) + similarity(p.raw, :raw) > 0.2", title: title, raw: raw) + similars.select( + DB.sql_fragment( + "topics.*, similarity(topics.title, :title) + similarity(p.raw, :raw) AS similarity, p.cooked AS blurb", + title: title, + raw: raw, + ), + ).where( + "similarity(topics.title, :title) + similarity(p.raw, :raw) > 0.2", + title: title, + raw: raw, + ) else - similars - .select(DB.sql_fragment("topics.*, similarity(topics.title, :title) AS similarity, p.cooked AS blurb", title: title)) - .where("similarity(topics.title, :title) > 0.2", title: title) + similars.select( + DB.sql_fragment( + "topics.*, similarity(topics.title, :title) AS similarity, p.cooked AS blurb", + title: title, + ), + ).where("similarity(topics.title, :title) > 0.2", title: title) end end @@ -683,30 +763,34 @@ class Topic < ActiveRecord::Base TopicStatusUpdater.new(self, user).update!(status, enabled, opts) DiscourseEvent.trigger(:topic_status_updated, self, status, enabled) - if status == 'closed' + if status == "closed" StaffActionLogger.new(user).log_topic_closed(self, closed: enabled) - elsif status == 'archived' + elsif status == "archived" StaffActionLogger.new(user).log_topic_archived(self, archived: enabled) end if enabled && private_message? && status.to_s["closed"] group_ids = user.groups.pluck(:id) if group_ids.present? - allowed_group_ids = self.allowed_groups - .where('topic_allowed_groups.group_id IN (?)', group_ids).pluck(:id) - allowed_group_ids.each do |id| - GroupArchivedMessage.archive!(id, self) - end + allowed_group_ids = + self.allowed_groups.where("topic_allowed_groups.group_id IN (?)", group_ids).pluck(:id) + allowed_group_ids.each { |id| GroupArchivedMessage.archive!(id, self) } end end end # Atomically creates the next post number def self.next_post_number(topic_id, opts = {}) - highest = DB.query_single("SELECT coalesce(max(post_number),0) AS max FROM posts WHERE topic_id = ?", topic_id).first.to_i + highest = + DB + .query_single( + "SELECT coalesce(max(post_number),0) AS max FROM posts WHERE topic_id = ?", + topic_id, + ) + .first + .to_i if opts[:whisper] - result = DB.query_single(<<~SQL, highest, topic_id) UPDATE topics SET highest_staff_post_number = ? + 1 @@ -715,11 +799,9 @@ class Topic < ActiveRecord::Base SQL result.first.to_i - else - reply_sql = opts[:reply] ? ", reply_count = reply_count + 1" : "" - posts_sql = opts[:post] ? ", posts_count = posts_count + 1" : "" + posts_sql = opts[:post] ? ", posts_count = posts_count + 1" : "" result = DB.query_single(<<~SQL, highest: highest, topic_id: topic_id) UPDATE topics @@ -814,7 +896,8 @@ class Topic < ActiveRecord::Base archetype = Topic.where(id: topic_id).pluck_first(:archetype) # ignore small_action replies for private messages - post_type = archetype == Archetype.private_message ? " AND post_type <> #{Post.types[:small_action]}" : '' + post_type = + archetype == Archetype.private_message ? " AND post_type <> #{Post.types[:small_action]}" : "" result = DB.query_single(<<~SQL, topic_id: topic_id) UPDATE topics @@ -875,7 +958,10 @@ class Topic < ActiveRecord::Base def changed_to_category(new_category) return true if new_category.blank? || Category.exists?(topic_id: id) - return false if new_category.id == SiteSetting.uncategorized_category_id && !SiteSetting.allow_uncategorized_topics + if new_category.id == SiteSetting.uncategorized_category_id && + !SiteSetting.allow_uncategorized_topics + return false + end Topic.transaction do old_category = category @@ -884,9 +970,7 @@ class Topic < ActiveRecord::Base self.update_attribute(:category_id, new_category.id) if old_category - Category - .where(id: old_category.id) - .update_all("topic_count = topic_count - 1") + Category.where(id: old_category.id).update_all("topic_count = topic_count - 1") end # when a topic changes category we may have to start watching it @@ -897,7 +981,11 @@ class Topic < ActiveRecord::Base if !SiteSetting.disable_category_edit_notifications && (post = self.ordered_posts.first) notified_user_ids = [post.user_id, post.last_editor_id].uniq DB.after_commit do - Jobs.enqueue(:notify_category_change, post_id: post.id, notified_user_ids: notified_user_ids) + Jobs.enqueue( + :notify_category_change, + post_id: post.id, + notified_user_ids: notified_user_ids, + ) end end @@ -905,16 +993,16 @@ class Topic < ActiveRecord::Base # linked to posts secure/not secure depending on whether the # category is private. this is only done if the category # has actually changed to avoid noise. - DB.after_commit do - Jobs.enqueue(:update_topic_upload_security, topic_id: self.id) - end + DB.after_commit { Jobs.enqueue(:update_topic_upload_security, topic_id: self.id) } end Category.where(id: new_category.id).update_all("topic_count = topic_count + 1") if Topic.update_featured_topics != false CategoryFeaturedTopic.feature_topics_for(old_category) unless @import_mode - CategoryFeaturedTopic.feature_topics_for(new_category) unless @import_mode || old_category.try(:id) == new_category.id + unless @import_mode || old_category.try(:id) == new_category.id + CategoryFeaturedTopic.feature_topics_for(new_category) + end end end @@ -924,11 +1012,12 @@ class Topic < ActiveRecord::Base def add_small_action(user, action_code, who = nil, opts = {}) custom_fields = {} custom_fields["action_code_who"] = who if who.present? - opts = opts.merge( - post_type: Post.types[:small_action], - action_code: action_code, - custom_fields: custom_fields - ) + opts = + opts.merge( + post_type: Post.types[:small_action], + action_code: action_code, + custom_fields: custom_fields, + ) add_moderator_post(user, nil, opts) end @@ -936,22 +1025,27 @@ class Topic < ActiveRecord::Base def add_moderator_post(user, text, opts = nil) opts ||= {} new_post = nil - creator = PostCreator.new(user, - raw: text, - post_type: opts[:post_type] || Post.types[:moderator_action], - action_code: opts[:action_code], - no_bump: opts[:bump].blank?, - topic_id: self.id, - silent: opts[:silent], - skip_validations: true, - custom_fields: opts[:custom_fields], - import_mode: opts[:import_mode]) + creator = + PostCreator.new( + user, + raw: text, + post_type: opts[:post_type] || Post.types[:moderator_action], + action_code: opts[:action_code], + no_bump: opts[:bump].blank?, + topic_id: self.id, + silent: opts[:silent], + skip_validations: true, + custom_fields: opts[:custom_fields], + import_mode: opts[:import_mode], + ) if (new_post = creator.create) && new_post.present? increment!(:moderator_posts_count) if new_post.persisted? # If we are moving posts, we want to insert the moderator post where the previous posts were # in the stream, not at the end. - new_post.update!(post_number: opts[:post_number], sort_order: opts[:post_number]) if opts[:post_number].present? + if opts[:post_number].present? + new_post.update!(post_number: opts[:post_number], sort_order: opts[:post_number]) + end # Grab any links that are present TopicLink.extract_from(new_post) @@ -1016,14 +1110,16 @@ class Topic < ActiveRecord::Base def reached_recipients_limit? return false unless private_message? - topic_allowed_users.count + topic_allowed_groups.count >= SiteSetting.max_allowed_message_recipients + topic_allowed_users.count + topic_allowed_groups.count >= + SiteSetting.max_allowed_message_recipients end def invite_group(user, group) TopicAllowedGroup.create!(topic_id: self.id, group_id: group.id) self.allowed_groups.reload - last_post = self.posts.order('post_number desc').where('not hidden AND posts.deleted_at IS NULL').first + last_post = + self.posts.order("post_number desc").where("not hidden AND posts.deleted_at IS NULL").first if last_post Jobs.enqueue(:post_alert, post_id: last_post.id) add_small_action(user, "invited_group", group.name) @@ -1045,12 +1141,14 @@ class Topic < ActiveRecord::Base topic_allowed_users.user_id != :op_user_id ) SQL - User.where([ - allowed_user_where_clause, - { group_id: group.id, topic_id: self.id, op_user_id: self.user_id } - ]).find_each do |allowed_user| - remove_allowed_user(Discourse.system_user, allowed_user) - end + User + .where( + [ + allowed_user_where_clause, + { group_id: group.id, topic_id: self.id, op_user_id: self.user_id }, + ], + ) + .find_each { |allowed_user| remove_allowed_user(Discourse.system_user, allowed_user) } true end @@ -1068,11 +1166,11 @@ class Topic < ActiveRecord::Base raise NotAllowed.new(I18n.t("not_accepting_pms", username: target_user.username)) end - if TopicUser - .where(topic: self, - user: target_user, - notification_level: TopicUser.notification_levels[:muted]) - .exists? + if TopicUser.where( + topic: self, + user: target_user, + notification_level: TopicUser.notification_levels[:muted], + ).exists? raise NotAllowed.new(I18n.t("topic_invite.muted_topic")) end @@ -1080,7 +1178,10 @@ class Topic < ActiveRecord::Base raise NotAllowed.new(I18n.t("topic_invite.receiver_does_not_allow_pm")) end - if UserCommScreener.new(acting_user: target_user, target_user_ids: invited_by.id).disallowing_pms_from_actor?(invited_by.id) + if UserCommScreener.new( + acting_user: target_user, + target_user_ids: invited_by.id, + ).disallowing_pms_from_actor?(invited_by.id) raise NotAllowed.new(I18n.t("topic_invite.sender_does_not_allow_pm")) end @@ -1090,12 +1191,13 @@ class Topic < ActiveRecord::Base !!invite_to_topic(invited_by, target_user, group_ids, guardian) end elsif username_or_email =~ /^.+@.+$/ && guardian.can_invite_via_email?(self) - !!Invite.generate(invited_by, + !!Invite.generate( + invited_by, email: username_or_email, topic: self, group_ids: group_ids, custom_message: custom_message, - invite_to_topic: true + invite_to_topic: true, ) end end @@ -1106,7 +1208,9 @@ class Topic < ActiveRecord::Base def grant_permission_to_user(lower_email) user = User.find_by_email(lower_email) - topic_allowed_users.create!(user_id: user.id) unless topic_allowed_users.exists?(user_id: user.id) + unless topic_allowed_users.exists?(user_id: user.id) + topic_allowed_users.create!(user_id: user.id) + end end def max_post_number @@ -1114,15 +1218,18 @@ class Topic < ActiveRecord::Base end def move_posts(moved_by, post_ids, opts) - post_mover = PostMover.new(self, moved_by, post_ids, move_to_pm: opts[:archetype].present? && opts[:archetype] == "private_message") + post_mover = + PostMover.new( + self, + moved_by, + post_ids, + move_to_pm: opts[:archetype].present? && opts[:archetype] == "private_message", + ) if opts[:destination_topic_id] topic = post_mover.to_topic(opts[:destination_topic_id], participants: opts[:participants]) - DiscourseEvent.trigger(:topic_merged, - post_mover.original_topic, - post_mover.destination_topic - ) + DiscourseEvent.trigger(:topic_merged, post_mover.original_topic, post_mover.destination_topic) topic elsif opts[:title] @@ -1142,10 +1249,7 @@ class Topic < ActiveRecord::Base def update_action_counts update_column( :like_count, - Post - .where.not(post_type: Post.types[:whisper]) - .where(topic_id: id) - .sum(:like_count) + Post.where.not(post_type: Post.types[:whisper]).where(topic_id: id).sum(:like_count), ) end @@ -1159,26 +1263,26 @@ class Topic < ActiveRecord::Base def make_banner!(user, bannered_until = nil) if bannered_until - bannered_until = begin - Time.parse(bannered_until) - rescue ArgumentError - raise Discourse::InvalidParameters.new(:bannered_until) - end + bannered_until = + begin + Time.parse(bannered_until) + rescue ArgumentError + raise Discourse::InvalidParameters.new(:bannered_until) + end end # only one banner at the same time previous_banner = Topic.where(archetype: Archetype.banner).first previous_banner.remove_banner!(user) if previous_banner.present? - UserProfile.where("dismissed_banner_key IS NOT NULL") - .update_all(dismissed_banner_key: nil) + UserProfile.where("dismissed_banner_key IS NOT NULL").update_all(dismissed_banner_key: nil) self.archetype = Archetype.banner self.bannered_until = bannered_until self.add_small_action(user, "banner.enabled") self.save - MessageBus.publish('/site/banner', banner) + MessageBus.publish("/site/banner", banner) Jobs.cancel_scheduled_job(:remove_banner, topic_id: self.id) Jobs.enqueue_at(bannered_until, :remove_banner, topic_id: self.id) if bannered_until @@ -1190,7 +1294,7 @@ class Topic < ActiveRecord::Base self.add_small_action(user, "banner.disabled") self.save - MessageBus.publish('/site/banner', nil) + MessageBus.publish("/site/banner", nil) Jobs.cancel_scheduled_job(:remove_banner, topic_id: self.id) end @@ -1198,24 +1302,18 @@ class Topic < ActiveRecord::Base def banner post = self.ordered_posts.first - { - html: post.cooked, - key: self.id, - url: self.url - } + { html: post.cooked, key: self.id, url: self.url } end cattr_accessor :slug_computed_callbacks self.slug_computed_callbacks = [] def slug_for_topic(title) - return '' unless title.present? + return "" unless title.present? slug = Slug.for(title) # this is a hook for plugins that need to modify the generated slug - self.class.slug_computed_callbacks.each do |callback| - slug = callback.call(self, slug, title) - end + self.class.slug_computed_callbacks.each { |callback| slug = callback.call(self, slug, title) } slug end @@ -1223,7 +1321,7 @@ class Topic < ActiveRecord::Base # Even if the slug column in the database is null, topic.slug will return something: def slug unless slug = read_attribute(:slug) - return '' unless title.present? + return "" unless title.present? slug = slug_for_topic(title) if new_record? write_attribute(:slug, slug) @@ -1295,17 +1393,18 @@ class Topic < ActiveRecord::Base def update_pinned(status, global = false, pinned_until = nil) if pinned_until - pinned_until = begin - Time.parse(pinned_until) - rescue ArgumentError - raise Discourse::InvalidParameters.new(:pinned_until) - end + pinned_until = + begin + Time.parse(pinned_until) + rescue ArgumentError + raise Discourse::InvalidParameters.new(:pinned_until) + end end update_columns( pinned_at: status ? Time.zone.now : nil, pinned_globally: global, - pinned_until: pinned_until + pinned_until: pinned_until, ) Jobs.cancel_scheduled_job(:unpin_topic, topic_id: self.id) @@ -1321,17 +1420,19 @@ class Topic < ActiveRecord::Base end def muted?(user) - if user && user.id - notifier.muted?(user.id) - end + notifier.muted?(user.id) if user && user.id end def self.ensure_consistency! # unpin topics that might have been missed - Topic.where('pinned_until < ?', Time.now).update_all(pinned_at: nil, pinned_globally: false, pinned_until: nil) - Topic.where('bannered_until < ?', Time.now).find_each do |topic| - topic.remove_banner!(Discourse.system_user) - end + Topic.where("pinned_until < ?", Time.now).update_all( + pinned_at: nil, + pinned_globally: false, + pinned_until: nil, + ) + Topic + .where("bannered_until < ?", Time.now) + .find_each { |topic| topic.remove_banner!(Discourse.system_user) } end def inherit_slow_mode_from_category @@ -1343,11 +1444,8 @@ class Topic < ActiveRecord::Base def inherit_auto_close_from_category(timer_type: :close) auto_close_hours = self.category&.auto_close_hours - if self.open? && - !@ignore_category_auto_close && - auto_close_hours.present? && - public_topic_timer&.execute_at.blank? - + if self.open? && !@ignore_category_auto_close && auto_close_hours.present? && + public_topic_timer&.execute_at.blank? based_on_last_post = self.category.auto_close_based_on_last_post duration_minutes = based_on_last_post ? auto_close_hours * 60 : nil @@ -1374,7 +1472,7 @@ class Topic < ActiveRecord::Base auto_close_time, by_user: Discourse.system_user, based_on_last_post: based_on_last_post, - duration_minutes: duration_minutes + duration_minutes: duration_minutes, ) end end @@ -1407,8 +1505,18 @@ class Topic < ActiveRecord::Base # * duration_minutes: The duration of the timer in minutes, which is used if the timer is based # on the last post or if the timer type is delete_replies. # * silent: Affects whether the close topic timer status change will be silent or not. - def set_or_create_timer(status_type, time, by_user: nil, based_on_last_post: false, category_id: SiteSetting.uncategorized_category_id, duration_minutes: nil, silent: nil) - return delete_topic_timer(status_type, by_user: by_user) if time.blank? && duration_minutes.blank? + def set_or_create_timer( + status_type, + time, + by_user: nil, + based_on_last_post: false, + category_id: SiteSetting.uncategorized_category_id, + duration_minutes: nil, + silent: nil + ) + if time.blank? && duration_minutes.blank? + return delete_topic_timer(status_type, by_user: by_user) + end duration_minutes = duration_minutes ? duration_minutes.to_i : 0 public_topic_timer = !!TopicTimer.public_types[status_type] @@ -1427,21 +1535,30 @@ class Topic < ActiveRecord::Base if topic_timer.based_on_last_post if duration_minutes > 0 - last_post_created_at = self.ordered_posts.last.present? ? self.ordered_posts.last.created_at : time_now + last_post_created_at = + self.ordered_posts.last.present? ? self.ordered_posts.last.created_at : time_now topic_timer.duration_minutes = duration_minutes topic_timer.execute_at = last_post_created_at + duration_minutes.minutes topic_timer.created_at = last_post_created_at end elsif topic_timer.status_type == TopicTimer.types[:delete_replies] if duration_minutes > 0 - first_reply_created_at = (self.ordered_posts.where("post_number > 1").minimum(:created_at) || time_now) + first_reply_created_at = + (self.ordered_posts.where("post_number > 1").minimum(:created_at) || time_now) topic_timer.duration_minutes = duration_minutes topic_timer.execute_at = first_reply_created_at + duration_minutes.minutes topic_timer.created_at = first_reply_created_at end else utc = Time.find_zone("UTC") - is_float = (Float(time) rescue nil) + is_float = + ( + begin + Float(time) + rescue StandardError + nil + end + ) if is_float num_hours = time.to_f @@ -1458,7 +1575,14 @@ class Topic < ActiveRecord::Base if by_user&.staff? || by_user&.trust_level == TrustLevel[4] topic_timer.user = by_user else - topic_timer.user ||= (self.user.staff? || self.user.trust_level == TrustLevel[4] ? self.user : Discourse.system_user) + topic_timer.user ||= + ( + if self.user.staff? || self.user.trust_level == TrustLevel[4] + self.user + else + Discourse.system_user + end + ) end if self.persisted? @@ -1490,9 +1614,8 @@ class Topic < ActiveRecord::Base end def secure_group_ids - @secure_group_ids ||= if self.category && self.category.read_restricted? - self.category.secure_group_ids - end + @secure_group_ids ||= + (self.category.secure_group_ids if self.category && self.category.read_restricted?) end def has_topic_embed? @@ -1584,7 +1707,10 @@ class Topic < ActiveRecord::Base end def self.time_to_first_response_per_day(start_date, end_date, opts = {}) - time_to_first_response(TIME_TO_FIRST_RESPONSE_SQL, opts.merge(start_date: start_date, end_date: end_date)) + time_to_first_response( + TIME_TO_FIRST_RESPONSE_SQL, + opts.merge(start_date: start_date, end_date: end_date), + ) end def self.time_to_first_response_total(opts = nil) @@ -1606,7 +1732,12 @@ class Topic < ActiveRecord::Base ORDER BY tt.created_at SQL - def self.with_no_response_per_day(start_date, end_date, category_id = nil, include_subcategories = nil) + def self.with_no_response_per_day( + start_date, + end_date, + category_id = nil, + include_subcategories = nil + ) builder = DB.build(WITH_NO_RESPONSE_SQL) builder.where("t.created_at >= :start_date", start_date: start_date) if start_date builder.where("t.created_at < :end_date", end_date: end_date) if end_date @@ -1662,9 +1793,7 @@ class Topic < ActiveRecord::Base def update_excerpt(excerpt) update_column(:excerpt, excerpt) - if archetype == "banner" - ApplicationController.banner_json_cache.clear - end + ApplicationController.banner_json_cache.clear if archetype == "banner" end def pm_with_non_human_user? @@ -1692,9 +1821,9 @@ class Topic < ActiveRecord::Base def self.private_message_topics_count_per_day(start_date, end_date, topic_subtype) private_messages .with_subtype(topic_subtype) - .where('topics.created_at >= ? AND topics.created_at <= ?', start_date, end_date) - .group('date(topics.created_at)') - .order('date(topics.created_at)') + .where("topics.created_at >= ? AND topics.created_at <= ?", start_date, end_date) + .group("date(topics.created_at)") + .order("date(topics.created_at)") .count end @@ -1703,11 +1832,12 @@ class Topic < ActiveRecord::Base end def reset_bumped_at - post = ordered_posts.where( - user_deleted: false, - hidden: false, - post_type: Post.types[:regular] - ).last || first_post + post = + ordered_posts.where( + user_deleted: false, + hidden: false, + post_type: Post.types[:regular], + ).last || first_post self.bumped_at = post.created_at self.save(validate: false) @@ -1716,21 +1846,26 @@ class Topic < ActiveRecord::Base def auto_close_threshold_reached? return if user&.staff? - scores = ReviewableScore.pending - .joins(:reviewable) - .where('reviewable_scores.score >= ?', Reviewable.min_score_for_priority) - .where('reviewables.topic_id = ?', self.id) - .pluck('COUNT(DISTINCT reviewable_scores.user_id), COALESCE(SUM(reviewable_scores.score), 0.0)') - .first + scores = + ReviewableScore + .pending + .joins(:reviewable) + .where("reviewable_scores.score >= ?", Reviewable.min_score_for_priority) + .where("reviewables.topic_id = ?", self.id) + .pluck( + "COUNT(DISTINCT reviewable_scores.user_id), COALESCE(SUM(reviewable_scores.score), 0.0)", + ) + .first - scores[0] >= SiteSetting.num_flaggers_to_close_topic && scores[1] >= Reviewable.score_to_auto_close_topic + scores[0] >= SiteSetting.num_flaggers_to_close_topic && + scores[1] >= Reviewable.score_to_auto_close_topic end def update_category_topic_count_by(num) if category_id.present? Category - .where('id = ?', category_id) - .where('topic_id != ? OR topic_id IS NULL', self.id) + .where("id = ?", category_id) + .where("topic_id != ? OR topic_id IS NULL", self.id) .update_all("topic_count = topic_count + #{num.to_i}") end end @@ -1749,40 +1884,46 @@ class Topic < ActiveRecord::Base # TODO(martin) Look at improving this N1, it will just get slower the # more replies/incoming emails there are for the topic. - self.incoming_email.where("created_at <= ?", received_before).each do |incoming_email| - to_addresses = incoming_email.to_addresses_split - cc_addresses = incoming_email.cc_addresses_split - combined_addresses = [to_addresses, cc_addresses].flatten + self + .incoming_email + .where("created_at <= ?", received_before) + .each do |incoming_email| + to_addresses = incoming_email.to_addresses_split + cc_addresses = incoming_email.cc_addresses_split + combined_addresses = [to_addresses, cc_addresses].flatten - # We only care about the emails addressed to the group or CC'd to the - # group if the group is present. If combined addresses is empty we do - # not need to do this check, and instead can proceed on to adding the - # from address. - # - # Will not include test1@gmail.com if the only IncomingEmail - # is: - # - # from: test1@gmail.com - # to: test+support@discoursemail.com - # - # Because we don't care about the from addresses and also the to address - # is not the email_username, which will be something like test1@gmail.com. - if group.present? && combined_addresses.any? - next if combined_addresses.none? { |address| address =~ group.email_username_regex } + # We only care about the emails addressed to the group or CC'd to the + # group if the group is present. If combined addresses is empty we do + # not need to do this check, and instead can proceed on to adding the + # from address. + # + # Will not include test1@gmail.com if the only IncomingEmail + # is: + # + # from: test1@gmail.com + # to: test+support@discoursemail.com + # + # Because we don't care about the from addresses and also the to address + # is not the email_username, which will be something like test1@gmail.com. + if group.present? && combined_addresses.any? + next if combined_addresses.none? { |address| address =~ group.email_username_regex } + end + + email_addresses.add(incoming_email.from_address) + email_addresses.merge(combined_addresses) end - email_addresses.add(incoming_email.from_address) - email_addresses.merge(combined_addresses) - end - - email_addresses.subtract([nil, '']) + email_addresses.subtract([nil, ""]) email_addresses.delete(group.email_username) if group.present? email_addresses.to_a end def create_invite_notification!(target_user, notification_type, invited_by, post_number: 1) - if UserCommScreener.new(acting_user: invited_by, target_user_ids: target_user.id).ignoring_or_muting_actor?(target_user.id) + if UserCommScreener.new( + acting_user: invited_by, + target_user_ids: target_user.id, + ).ignoring_or_muting_actor?(target_user.id) raise NotAllowed.new(I18n.t("not_accepting_pms", username: target_user.username)) end @@ -1794,8 +1935,8 @@ class Topic < ActiveRecord::Base topic_title: self.title, display_username: invited_by.username, original_user_id: user.id, - original_username: user.username - }.to_json + original_username: user.username, + }.to_json, ) end @@ -1804,28 +1945,35 @@ class Topic < ActiveRecord::Base invited_by, "topic-invitations-per-day", SiteSetting.max_topic_invitations_per_day, - 1.day.to_i + 1.day.to_i, ).performed! RateLimiter.new( invited_by, "topic-invitations-per-minute", SiteSetting.max_topic_invitations_per_minute, - 1.day.to_i + 1.day.to_i, ).performed! end def cannot_permanently_delete_reason(user) - all_posts_count = Post.with_deleted - .where(topic_id: self.id) - .where(post_type: [Post.types[:regular], Post.types[:moderator_action], Post.types[:whisper]]) - .count + all_posts_count = + Post + .with_deleted + .where(topic_id: self.id) + .where( + post_type: [Post.types[:regular], Post.types[:moderator_action], Post.types[:whisper]], + ) + .count if posts_count > 0 || all_posts_count > 1 - I18n.t('post.cannot_permanently_delete.many_posts') + I18n.t("post.cannot_permanently_delete.many_posts") elsif self.deleted_by_id == user&.id && self.deleted_at >= Post::PERMANENT_DELETE_TIMER.ago - time_left = RateLimiter.time_left(Post::PERMANENT_DELETE_TIMER.to_i - Time.zone.now.to_i + self.deleted_at.to_i) - I18n.t('post.cannot_permanently_delete.wait_or_different_admin', time_left: time_left) + time_left = + RateLimiter.time_left( + Post::PERMANENT_DELETE_TIMER.to_i - Time.zone.now.to_i + self.deleted_at.to_i, + ) + I18n.t("post.cannot_permanently_delete.wait_or_different_admin", time_left: time_left) end end @@ -1855,9 +2003,11 @@ class Topic < ActiveRecord::Base when :liked, :unliked stats = { like_count: topic.like_count } when :created, :destroyed, :deleted, :recovered - stats = { posts_count: topic.posts_count, - last_posted_at: topic.last_posted_at.as_json, - last_poster: BasicUserSerializer.new(topic.last_poster, root: false).as_json } + stats = { + posts_count: topic.posts_count, + last_posted_at: topic.last_posted_at.as_json, + last_poster: BasicUserSerializer.new(topic.last_poster, root: false).as_json, + } else stats = nil end @@ -1866,11 +2016,7 @@ class Topic < ActiveRecord::Base secure_audience = topic.secure_audience_publish_messages if secure_audience[:user_ids] != [] && secure_audience[:group_ids] != [] - message = stats.merge({ - id: topic_id, - updated_at: Time.now, - type: :stats, - }) + message = stats.merge({ id: topic_id, updated_at: Time.now, type: :stats }) MessageBus.publish("/topic/#{topic_id}", message, opts.merge(secure_audience)) end end @@ -1880,15 +2026,15 @@ class Topic < ActiveRecord::Base def invite_to_private_message(invited_by, target_user, guardian) if !guardian.can_send_private_message?(target_user) - raise UserExists.new(I18n.t( - "activerecord.errors.models.topic.attributes.base.cant_send_pm" - )) + raise UserExists.new(I18n.t("activerecord.errors.models.topic.attributes.base.cant_send_pm")) end rate_limit_topic_invitation(invited_by) Topic.transaction do - topic_allowed_users.create!(user_id: target_user.id) unless topic_allowed_users.exists?(user_id: target_user.id) + unless topic_allowed_users.exists?(user_id: target_user.id) + topic_allowed_users.create!(user_id: target_user.id) + end user_in_allowed_group = (user.group_ids & topic_allowed_groups.map(&:group_id)).present? add_small_action(invited_by, "invited_user", target_user.username) if !user_in_allowed_group @@ -1896,7 +2042,7 @@ class Topic < ActiveRecord::Base create_invite_notification!( target_user, Notification.types[:invited_to_private_message], - invited_by + invited_by, ) end end @@ -1908,24 +2054,18 @@ class Topic < ActiveRecord::Base if group_ids.present? ( self.category.groups.where(id: group_ids).where(automatic: false) - - target_user.groups.where(automatic: false) + target_user.groups.where(automatic: false) ).each do |group| if guardian.can_edit_group?(group) group.add(target_user) - GroupActionLogger - .new(invited_by, group) - .log_add_user_to_group(target_user) + GroupActionLogger.new(invited_by, group).log_add_user_to_group(target_user) end end end if Guardian.new(target_user).can_see_topic?(self) - create_invite_notification!( - target_user, - Notification.types[:invited_to_topic], - invited_by - ) + create_invite_notification!(target_user, Notification.types[:invited_to_topic], invited_by) end end end diff --git a/app/models/topic_converter.rb b/app/models/topic_converter.rb index 47c8b4a3c72..8eab3a31d0c 100644 --- a/app/models/topic_converter.rb +++ b/app/models/topic_converter.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TopicConverter - attr_reader :topic def initialize(topic, user) @@ -17,16 +16,17 @@ class TopicConverter elsif SiteSetting.allow_uncategorized_topics SiteSetting.uncategorized_category_id else - Category.where(read_restricted: false) + Category + .where(read_restricted: false) .where.not(id: SiteSetting.uncategorized_category_id) - .order('id asc') + .order("id asc") .pluck_first(:id) end PostRevisor.new(@topic.first_post, @topic).revise!( @user, category_id: category_id, - archetype: Archetype.default + archetype: Archetype.default, ) update_user_stats @@ -45,7 +45,7 @@ class TopicConverter PostRevisor.new(@topic.first_post, @topic).revise!( @user, category_id: nil, - archetype: Archetype.private_message + archetype: Archetype.private_message, ) add_allowed_users @@ -63,9 +63,12 @@ class TopicConverter private def posters - @posters ||= @topic.posts - .where.not(post_type: [Post.types[:small_action], Post.types[:whisper]]) - .distinct.pluck(:user_id) + @posters ||= + @topic + .posts + .where.not(post_type: [Post.types[:small_action], Post.types[:whisper]]) + .distinct + .pluck(:user_id) end def increment_users_post_count @@ -134,8 +137,6 @@ class TopicConverter end def update_post_uploads_secure_status - DB.after_commit do - Jobs.enqueue(:update_topic_upload_security, topic_id: @topic.id) - end + DB.after_commit { Jobs.enqueue(:update_topic_upload_security, topic_id: @topic.id) } end end diff --git a/app/models/topic_embed.rb b/app/models/topic_embed.rb index 6b0e3662f84..3b9545bf815 100644 --- a/app/models/topic_embed.rb +++ b/app/models/topic_embed.rb @@ -9,7 +9,13 @@ class TopicEmbed < ActiveRecord::Base validates_uniqueness_of :embed_url before_validation(on: :create) do - unless (topic_embed = TopicEmbed.with_deleted.where('deleted_at IS NOT NULL AND embed_url = ?', embed_url).first).nil? + unless ( + topic_embed = + TopicEmbed + .with_deleted + .where("deleted_at IS NOT NULL AND embed_url = ?", embed_url) + .first + ).nil? topic_embed.destroy! end end @@ -19,23 +25,21 @@ class TopicEmbed < ActiveRecord::Base end def self.normalize_url(url) - url.downcase.sub(/\/$/, '').sub(/\-+/, '-').strip + url.downcase.sub(%r{/$}, "").sub(/\-+/, "-").strip end def self.imported_from_html(url) I18n.with_locale(SiteSetting.default_locale) do - "\n
    \n#{I18n.t('embed.imported_from', link: "
    #{url}")}\n" + "\n
    \n#{I18n.t("embed.imported_from", link: "#{url}")}\n" end end # Import an article from a source (RSS/Atom/Other) def self.import(user, url, title, contents, category_id: nil, cook_method: nil, tags: nil) - return unless url =~ /^https?\:\/\// + return unless url =~ %r{^https?\://} - if SiteSetting.embed_truncate && cook_method.nil? - contents = first_paragraph_from(contents) - end - contents ||= '' + contents = first_paragraph_from(contents) if SiteSetting.embed_truncate && cook_method.nil? + contents ||= "" contents = contents.dup << imported_from_html(url) url = normalize_url(url) @@ -49,11 +53,12 @@ class TopicEmbed < ActiveRecord::Base Topic.transaction do eh = EmbeddableHost.record_for_url(url) - cook_method ||= if SiteSetting.embed_support_markdown - Post.cook_methods[:regular] - else - Post.cook_methods[:raw_html] - end + cook_method ||= + if SiteSetting.embed_support_markdown + Post.cook_methods[:regular] + else + Post.cook_methods[:raw_html] + end create_args = { title: title, @@ -63,17 +68,17 @@ class TopicEmbed < ActiveRecord::Base category: category_id || eh.try(:category_id), tags: SiteSetting.tagging_enabled ? tags : nil, } - if SiteSetting.embed_unlisted? - create_args[:visible] = false - end + create_args[:visible] = false if SiteSetting.embed_unlisted? creator = PostCreator.new(user, create_args) post = creator.create if post.present? - TopicEmbed.create!(topic_id: post.topic_id, - embed_url: url, - content_sha1: content_sha1, - post_id: post.id) + TopicEmbed.create!( + topic_id: post.topic_id, + embed_url: url, + content_sha1: content_sha1, + post_id: post.id, + ) end end else @@ -87,7 +92,7 @@ class TopicEmbed < ActiveRecord::Base post_ids: [post.id], topic_id: post.topic_id, new_owner: user, - acting_user: Discourse.system_user + acting_user: Discourse.system_user, ).change_owner! # make sure the post returned has the right author @@ -108,16 +113,11 @@ class TopicEmbed < ActiveRecord::Base end def self.find_remote(url) - require 'ruby-readability' + require "ruby-readability" url = UrlHelper.normalized_encode(url) original_uri = URI.parse(url) - fd = FinalDestination.new( - url, - validate_uri: true, - max_redirects: 5, - follow_canonical: true, - ) + fd = FinalDestination.new(url, validate_uri: true, max_redirects: 5, follow_canonical: true) uri = fd.resolve return if uri.blank? @@ -125,12 +125,17 @@ class TopicEmbed < ActiveRecord::Base opts = { tags: %w[div p code pre h1 h2 h3 b em i strong a img ul li ol blockquote], attributes: %w[href src class], - remove_empty_nodes: false + remove_empty_nodes: false, } - opts[:whitelist] = SiteSetting.allowed_embed_selectors if SiteSetting.allowed_embed_selectors.present? - opts[:blacklist] = SiteSetting.blocked_embed_selectors if SiteSetting.blocked_embed_selectors.present? - allowed_embed_classnames = SiteSetting.allowed_embed_classnames if SiteSetting.allowed_embed_classnames.present? + opts[ + :whitelist + ] = SiteSetting.allowed_embed_selectors if SiteSetting.allowed_embed_selectors.present? + opts[ + :blacklist + ] = SiteSetting.blocked_embed_selectors if SiteSetting.blocked_embed_selectors.present? + allowed_embed_classnames = + SiteSetting.allowed_embed_classnames if SiteSetting.allowed_embed_classnames.present? response = FetchResponse.new begin @@ -139,7 +144,7 @@ class TopicEmbed < ActiveRecord::Base return end - raw_doc = Nokogiri::HTML5(html) + raw_doc = Nokogiri.HTML5(html) auth_element = raw_doc.at('meta[@name="author"]') if auth_element.present? response.author = User.where(username_lower: auth_element[:content].strip).first @@ -147,39 +152,51 @@ class TopicEmbed < ActiveRecord::Base read_doc = Readability::Document.new(html, opts) - title = +(raw_doc.title || '') + title = +(raw_doc.title || "") title.strip! if SiteSetting.embed_title_scrubber.present? - title.sub!(Regexp.new(SiteSetting.embed_title_scrubber), '') + title.sub!(Regexp.new(SiteSetting.embed_title_scrubber), "") title.strip! end response.title = title - doc = Nokogiri::HTML5(read_doc.content) + doc = Nokogiri.HTML5(read_doc.content) - tags = { 'img' => 'src', 'script' => 'src', 'a' => 'href' } - doc.search(tags.keys.join(',')).each do |node| - url_param = tags[node.name] - src = node[url_param] - unless (src.nil? || src.empty?) - begin - # convert URL to absolute form - node[url_param] = URI.join(url, UrlHelper.normalized_encode(src)).to_s - rescue URI::Error, Addressable::URI::InvalidURIError - # If there is a mistyped URL, just do nothing + tags = { "img" => "src", "script" => "src", "a" => "href" } + doc + .search(tags.keys.join(",")) + .each do |node| + url_param = tags[node.name] + src = node[url_param] + unless (src.nil? || src.empty?) + begin + # convert URL to absolute form + node[url_param] = URI.join(url, UrlHelper.normalized_encode(src)).to_s + rescue URI::Error, Addressable::URI::InvalidURIError + # If there is a mistyped URL, just do nothing + end end + # only allow classes in the allowlist + allowed_classes = + if allowed_embed_classnames.blank? + [] + else + allowed_embed_classnames.split(/[ ,]+/i) + end + doc + .search('[class]:not([class=""])') + .each do |classnode| + classes = + classnode[:class] + .split(" ") + .select { |classname| allowed_classes.include?(classname) } + if classes.length === 0 + classnode.delete("class") + else + classnode[:class] = classes.join(" ") + end + end end - # only allow classes in the allowlist - allowed_classes = if allowed_embed_classnames.blank? then [] else allowed_embed_classnames.split(/[ ,]+/i) end - doc.search('[class]:not([class=""])').each do |classnode| - classes = classnode[:class].split(' ').select { |classname| allowed_classes.include?(classname) } - if classes.length === 0 - classnode.delete('class') - else - classnode[:class] = classes.join(' ') - end - end - end response.body = doc.to_html response @@ -208,59 +225,69 @@ class TopicEmbed < ActiveRecord::Base prefix += ":#{uri.port}" if uri.port != 80 && uri.port != 443 fragment = Nokogiri::HTML5.fragment("
    #{contents}
    ") - fragment.css('a').each do |a| - if a['href'].present? - begin - a['href'] = URI.join(prefix, a['href']).to_s - rescue URI::InvalidURIError - # NOOP, URL is malformed + fragment + .css("a") + .each do |a| + if a["href"].present? + begin + a["href"] = URI.join(prefix, a["href"]).to_s + rescue URI::InvalidURIError + # NOOP, URL is malformed + end end end - end - fragment.css('img').each do |a| - if a['src'].present? - begin - a['src'] = URI.join(prefix, a['src']).to_s - rescue URI::InvalidURIError - # NOOP, URL is malformed + fragment + .css("img") + .each do |a| + if a["src"].present? + begin + a["src"] = URI.join(prefix, a["src"]).to_s + rescue URI::InvalidURIError + # NOOP, URL is malformed + end end end - end - fragment.at('div').inner_html + fragment.at("div").inner_html end def self.topic_id_for_embed(embed_url) - embed_url = normalize_url(embed_url).sub(/^https?\:\/\//, '') - TopicEmbed.where("embed_url ~* ?", "^https?://#{Regexp.escape(embed_url)}$").pluck_first(:topic_id) + embed_url = normalize_url(embed_url).sub(%r{^https?\://}, "") + TopicEmbed.where("embed_url ~* ?", "^https?://#{Regexp.escape(embed_url)}$").pluck_first( + :topic_id, + ) end def self.first_paragraph_from(html) - doc = Nokogiri::HTML5(html) + doc = Nokogiri.HTML5(html) result = +"" - doc.css('p').each do |p| - if p.text.present? - result << p.to_s - return result if result.size >= 100 + doc + .css("p") + .each do |p| + if p.text.present? + result << p.to_s + return result if result.size >= 100 + end end - end return result unless result.blank? # If there is no first paragraph, return the first div (onebox) - doc.css('div').first.to_s + doc.css("div").first.to_s end def self.expanded_for(post) - Discourse.cache.fetch("embed-topic:#{post.topic_id}", expires_in: 10.minutes) do - url = TopicEmbed.where(topic_id: post.topic_id).pluck_first(:embed_url) - response = TopicEmbed.find_remote(url) + Discourse + .cache + .fetch("embed-topic:#{post.topic_id}", expires_in: 10.minutes) do + url = TopicEmbed.where(topic_id: post.topic_id).pluck_first(:embed_url) + response = TopicEmbed.find_remote(url) - body = response.body - body << TopicEmbed.imported_from_html(url) - body - end + body = response.body + body << TopicEmbed.imported_from_html(url) + body + end end end diff --git a/app/models/topic_featured_users.rb b/app/models/topic_featured_users.rb index 3cd3c7fda31..5383ae6655f 100644 --- a/app/models/topic_featured_users.rb +++ b/app/models/topic_featured_users.rb @@ -18,14 +18,15 @@ class TopicFeaturedUsers end def user_ids - [topic.featured_user1_id, - topic.featured_user2_id, - topic.featured_user3_id, - topic.featured_user4_id].uniq.compact + [ + topic.featured_user1_id, + topic.featured_user2_id, + topic.featured_user3_id, + topic.featured_user4_id, + ].uniq.compact end def self.ensure_consistency!(topic_id = nil) - filter = "#{"AND t.id = #{topic_id.to_i}" if topic_id}" filter2 = "#{"AND tt.id = #{topic_id.to_i}" if topic_id}" @@ -87,7 +88,11 @@ SQL private def update_participant_count - count = topic.posts.where('NOT hidden AND post_type in (?)', Topic.visible_post_types).count('distinct user_id') + count = + topic + .posts + .where("NOT hidden AND post_type in (?)", Topic.visible_post_types) + .count("distinct user_id") topic.update_columns(participant_count: count) end end diff --git a/app/models/topic_group.rb b/app/models/topic_group.rb index 7f3c4060f34..f9e0f65fb01 100644 --- a/app/models/topic_group.rb +++ b/app/models/topic_group.rb @@ -31,10 +31,14 @@ class TopicGroup < ActiveRecord::Base tg.group_id SQL - updated_groups = DB.query( - update_query, - user_id: user.id, topic_id: topic_id, post_number: post_number, now: DateTime.now - ) + updated_groups = + DB.query( + update_query, + user_id: user.id, + topic_id: topic_id, + post_number: post_number, + now: DateTime.now, + ) end def self.create_topic_group(user, topic_id, post_number, updated_group_ids) @@ -47,7 +51,8 @@ class TopicGroup < ActiveRecord::Base AND tag.topic_id = :topic_id SQL - query += 'AND NOT(tag.group_id IN (:already_updated_groups))' unless updated_group_ids.length.zero? + query += + "AND NOT(tag.group_id IN (:already_updated_groups))" unless updated_group_ids.length.zero? query += <<~SQL ON CONFLICT(topic_id, group_id) @@ -56,7 +61,11 @@ class TopicGroup < ActiveRecord::Base DB.exec( query, - user_id: user.id, topic_id: topic_id, post_number: post_number, now: DateTime.now, already_updated_groups: updated_group_ids + user_id: user.id, + topic_id: topic_id, + post_number: post_number, + now: DateTime.now, + already_updated_groups: updated_group_ids, ) end end diff --git a/app/models/topic_link.rb b/app/models/topic_link.rb index 690779cb06e..03bc3587b45 100644 --- a/app/models/topic_link.rb +++ b/app/models/topic_link.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'uri' +require "uri" class TopicLink < ActiveRecord::Base - def self.max_domain_length 100 end @@ -15,14 +14,14 @@ class TopicLink < ActiveRecord::Base belongs_to :topic belongs_to :user belongs_to :post - belongs_to :link_topic, class_name: 'Topic' - belongs_to :link_post, class_name: 'Post' + belongs_to :link_topic, class_name: "Topic" + belongs_to :link_post, class_name: "Post" validates_presence_of :url validates_length_of :url, maximum: 500 - validates_uniqueness_of :url, scope: [:topic_id, :post_id] + validates_uniqueness_of :url, scope: %i[topic_id post_id] has_many :topic_link_clicks, dependent: :destroy @@ -36,7 +35,6 @@ class TopicLink < ActiveRecord::Base end def self.topic_map(guardian, topic_id) - # Sam: complicated reports are really hard in AR builder = DB.build(<<~SQL) SELECT ftl.url, @@ -56,16 +54,18 @@ class TopicLink < ActiveRecord::Base LIMIT 50 SQL - builder.where('ftl.topic_id = :topic_id', topic_id: topic_id) - builder.where('ft.deleted_at IS NULL') + builder.where("ftl.topic_id = :topic_id", topic_id: topic_id) + builder.where("ft.deleted_at IS NULL") builder.where("ftl.extension IS NULL OR ftl.extension NOT IN ('png','jpg','gif')") - builder.where("COALESCE(ft.archetype, 'regular') <> :archetype", archetype: Archetype.private_message) + builder.where( + "COALESCE(ft.archetype, 'regular') <> :archetype", + archetype: Archetype.private_message, + ) builder.where("clicks > 0") builder.secure_category(guardian.secure_category_ids) builder.query - end def self.counts_for(guardian, topic, posts) @@ -73,7 +73,9 @@ class TopicLink < ActiveRecord::Base # Sam: this is not tidy in AR and also happens to be a critical path # for topic view - builder = DB.build("SELECT + builder = + DB.build( + "SELECT l.post_id, l.url, l.clicks, @@ -86,28 +88,39 @@ class TopicLink < ActiveRecord::Base LEFT JOIN categories AS c ON c.id = t.category_id /*left_join*/ /*where*/ - ORDER BY reflection ASC, clicks DESC") + ORDER BY reflection ASC, clicks DESC", + ) - builder.where('t.deleted_at IS NULL') - builder.where("COALESCE(t.archetype, 'regular') <> :archetype", archetype: Archetype.private_message) + builder.where("t.deleted_at IS NULL") + builder.where( + "COALESCE(t.archetype, 'regular') <> :archetype", + archetype: Archetype.private_message, + ) if guardian.authenticated? - builder.left_join("topic_users AS tu ON (t.id = tu.topic_id AND tu.user_id = #{guardian.user.id.to_i})") - builder.where('COALESCE(tu.notification_level,1) > :muted', muted: TopicUser.notification_levels[:muted]) + builder.left_join( + "topic_users AS tu ON (t.id = tu.topic_id AND tu.user_id = #{guardian.user.id.to_i})", + ) + builder.where( + "COALESCE(tu.notification_level,1) > :muted", + muted: TopicUser.notification_levels[:muted], + ) end # not certain if pluck is right, cause it may interfere with caching - builder.where('l.post_id in (:post_ids)', post_ids: posts.map(&:id)) + builder.where("l.post_id in (:post_ids)", post_ids: posts.map(&:id)) builder.secure_category(guardian.secure_category_ids) result = {} builder.query.each do |l| result[l.post_id] ||= [] - result[l.post_id] << { url: l.url, - clicks: l.clicks, - title: l.title, - internal: l.internal, - reflection: l.reflection } + result[l.post_id] << { + url: l.url, + clicks: l.clicks, + title: l.title, + internal: l.internal, + reflection: l.reflection, + } end result end @@ -127,22 +140,20 @@ class TopicLink < ActiveRecord::Base .reject { |_, p| p.nil? || "mailto" == p.scheme } .uniq { |_, p| p } .each do |link, parsed| - - TopicLink.transaction do - begin - url, reflected_id = self.ensure_entry_for(post, link, parsed) - current_urls << url unless url.nil? - reflected_ids << reflected_id unless reflected_id.nil? - rescue URI::Error - # if the URI is invalid, don't store it. - rescue ActionController::RoutingError - # If we can't find the route, no big deal + TopicLink.transaction do + begin + url, reflected_id = self.ensure_entry_for(post, link, parsed) + current_urls << url unless url.nil? + reflected_ids << reflected_id unless reflected_id.nil? + rescue URI::Error + # if the URI is invalid, don't store it. + rescue ActionController::RoutingError + # If we can't find the route, no big deal + end end end - end self.cleanup_entries(post, current_urls, reflected_ids) - end def self.crawl_link_title(topic_link_id) @@ -154,20 +165,23 @@ class TopicLink < ActiveRecord::Base end def self.duplicate_lookup(topic) - results = TopicLink - .includes(:post, :user) - .joins(:post, :user) - .where("posts.id IS NOT NULL AND users.id IS NOT NULL") - .where(topic_id: topic.id, reflection: false) - .last(200) + results = + TopicLink + .includes(:post, :user) + .joins(:post, :user) + .where("posts.id IS NOT NULL AND users.id IS NOT NULL") + .where(topic_id: topic.id, reflection: false) + .last(200) lookup = {} results.each do |tl| - normalized = tl.url.downcase.sub(/^https?:\/\//, '').sub(/\/$/, '') - lookup[normalized] = { domain: tl.domain, - username: tl.user.username_lower, - posted_at: tl.post.created_at, - post_number: tl.post.post_number } + normalized = tl.url.downcase.sub(%r{^https?://}, "").sub(%r{/$}, "") + lookup[normalized] = { + domain: tl.domain, + username: tl.user.username_lower, + posted_at: tl.post.created_at, + post_number: tl.post.post_number, + } end lookup @@ -199,7 +213,6 @@ class TopicLink < ActiveRecord::Base extension: nil, reflection: false ) - domain ||= Discourse.current_hostname sql = <<~SQL @@ -242,26 +255,24 @@ class TopicLink < ActiveRecord::Base ), (SELECT id FROM new_row) IS NOT NULL SQL - topic_link_id, new_record = DB.query_single(sql, - post_id: post_id, - user_id: user_id, - topic_id: topic_id, - url: url, - domain: domain, - internal: internal, - link_topic_id: link_topic_id, - link_post_id: link_post_id, - quote: quote, - extension: extension, - reflection: reflection, - now: Time.now - ) + topic_link_id, new_record = + DB.query_single( + sql, + post_id: post_id, + user_id: user_id, + topic_id: topic_id, + url: url, + domain: domain, + internal: internal, + link_topic_id: link_topic_id, + link_post_id: link_post_id, + quote: quote, + extension: extension, + reflection: reflection, + now: Time.now, + ) - if new_record - DB.after_commit do - crawl_link_title(topic_link_id) - end - end + DB.after_commit { crawl_link_title(topic_link_id) } if new_record topic_link_id end @@ -314,7 +325,8 @@ class TopicLink < ActiveRecord::Base url = url[0...TopicLink.max_url_length] return nil if parsed && parsed.host && parsed.host.length > TopicLink.max_domain_length - file_extension = File.extname(parsed.path)[1..10].downcase unless parsed.path.nil? || File.extname(parsed.path).empty? + file_extension = File.extname(parsed.path)[1..10].downcase unless parsed.path.nil? || + File.extname(parsed.path).empty? safe_create_topic_link( post_id: post.id, @@ -332,22 +344,23 @@ class TopicLink < ActiveRecord::Base reflected_id = nil # Create the reflection if we can - if topic && post.topic && topic.archetype != 'private_message' && post.topic.archetype != 'private_message' && post.topic.visible? + if topic && post.topic && topic.archetype != "private_message" && + post.topic.archetype != "private_message" && post.topic.visible? prefix = Discourse.base_url_no_prefix reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}" - reflected_id = safe_create_topic_link( - user_id: post.user_id, - topic_id: topic&.id, - post_id: reflected_post&.id, - url: reflected_url, - domain: Discourse.current_hostname, - reflection: true, - internal: true, - link_topic_id: post.topic_id, - link_post_id: post.id - ) - + reflected_id = + safe_create_topic_link( + user_id: post.user_id, + topic_id: topic&.id, + post_id: reflected_post&.id, + url: reflected_url, + domain: Discourse.current_hostname, + reflection: true, + internal: true, + link_topic_id: post.topic_id, + link_post_id: post.id, + ) end [url, reflected_id] @@ -358,27 +371,25 @@ class TopicLink < ActiveRecord::Base if current_urls.present? TopicLink.where( "(url not in (:urls)) AND (post_id = :post_id AND NOT reflection)", - urls: current_urls, post_id: post.id + urls: current_urls, + post_id: post.id, ).delete_all current_reflected_ids.compact! if current_reflected_ids.present? TopicLink.where( "(id not in (:reflected_ids)) AND (link_post_id = :post_id AND reflection)", - reflected_ids: current_reflected_ids, post_id: post.id + reflected_ids: current_reflected_ids, + post_id: post.id, ).delete_all else - TopicLink - .where("link_post_id = :post_id AND reflection", post_id: post.id) - .delete_all + TopicLink.where("link_post_id = :post_id AND reflection", post_id: post.id).delete_all end else - TopicLink - .where( - "(post_id = :post_id AND NOT reflection) OR (link_post_id = :post_id AND reflection)", - post_id: post.id - ) - .delete_all + TopicLink.where( + "(post_id = :post_id AND NOT reflection) OR (link_post_id = :post_id AND reflection)", + post_id: post.id, + ).delete_all end end end diff --git a/app/models/topic_link_click.rb b/app/models/topic_link_click.rb index 8770567997d..a1f121ee844 100644 --- a/app/models/topic_link_click.rb +++ b/app/models/topic_link_click.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'ipaddr' -require 'url_helper' +require "ipaddr" +require "url_helper" class TopicLinkClick < ActiveRecord::Base belongs_to :topic_link, counter_cache: :clicks @@ -9,9 +9,9 @@ class TopicLinkClick < ActiveRecord::Base validates_presence_of :topic_link_id - ALLOWED_REDIRECT_HOSTNAMES = Set.new(%W{www.youtube.com youtu.be}) + ALLOWED_REDIRECT_HOSTNAMES = Set.new(%W[www.youtube.com youtu.be]) include ActiveSupport::Deprecation::DeprecatedConstantAccessor - deprecate_constant 'WHITELISTED_REDIRECT_HOSTNAMES', 'TopicLinkClick::ALLOWED_REDIRECT_HOSTNAMES' + deprecate_constant "WHITELISTED_REDIRECT_HOSTNAMES", "TopicLinkClick::ALLOWED_REDIRECT_HOSTNAMES" # Create a click from a URL and post_id def self.create_from(args = {}) @@ -22,32 +22,33 @@ class TopicLinkClick < ActiveRecord::Base urls = Set.new urls << url if url =~ /^http/ - urls << url.sub(/^https/, 'http') - urls << url.sub(/^http:/, 'https:') + urls << url.sub(/^https/, "http") + urls << url.sub(/^http:/, "https:") urls << UrlHelper.schemaless(url) end urls << UrlHelper.absolute_without_cdn(url) urls << uri.path if uri.try(:host) == Discourse.current_hostname - query = url.index('?') + query = url.index("?") unless query.nil? - endpos = url.index('#') || url.size + endpos = url.index("#") || url.size urls << url[0..query - 1] + url[endpos..-1] end # link can have query params, and analytics can add more to the end: i = url.length - while i = url.rindex('&', i - 1) + while i = url.rindex("&", i - 1) urls << url[0...i] end # add a cdn link if uri if Discourse.asset_host.present? - cdn_uri = begin - URI.parse(Discourse.asset_host) - rescue URI::Error - end + cdn_uri = + begin + URI.parse(Discourse.asset_host) + rescue URI::Error + end if cdn_uri && cdn_uri.hostname == uri.hostname && uri.path.starts_with?(cdn_uri.path) is_cdn_link = true @@ -56,10 +57,11 @@ class TopicLinkClick < ActiveRecord::Base end if SiteSetting.Upload.s3_cdn_url.present? - cdn_uri = begin - URI.parse(SiteSetting.Upload.s3_cdn_url) - rescue URI::Error - end + cdn_uri = + begin + URI.parse(SiteSetting.Upload.s3_cdn_url) + rescue URI::Error + end if cdn_uri && cdn_uri.hostname == uri.hostname && uri.path.starts_with?(cdn_uri.path) is_cdn_link = true @@ -80,12 +82,15 @@ class TopicLinkClick < ActiveRecord::Base link = link.where(topic_id: args[:topic_id]) if args[:topic_id].present? # select the TopicLink associated to first url - link = link.order("array_position(ARRAY[#{urls.map { |s| "#{ActiveRecord::Base.connection.quote(s)}" }.join(',')}], url::text)").first + link = + link.order( + "array_position(ARRAY[#{urls.map { |s| "#{ActiveRecord::Base.connection.quote(s)}" }.join(",")}], url::text)", + ).first # If no link is found... unless link.present? # ... return the url for relative links or when using the same host - return url if url =~ /^\/[^\/]/ || uri.try(:host) == Discourse.current_hostname + return url if url =~ %r{^/[^/]} || uri.try(:host) == Discourse.current_hostname # If we have it somewhere else on the site, just allow the redirect. # This is likely due to a onebox of another topic. @@ -112,7 +117,6 @@ class TopicLinkClick < ActiveRecord::Base url end - end # == Schema Information diff --git a/app/models/topic_list.rb b/app/models/topic_list.rb index 815e9c23085..b4be8c367fd 100644 --- a/app/models/topic_list.rb +++ b/app/models/topic_list.rb @@ -13,16 +13,12 @@ class TopicList def self.cancel_preload(&blk) if @preload @preload.delete blk - if @preload.length == 0 - @preload = nil - end + @preload = nil if @preload.length == 0 end end def self.preload(topics, object) - if @preload - @preload.each { |preload| preload.call(topics, object) } - end + @preload.each { |preload| preload.call(topics, object) } if @preload end def self.on_preload_user_ids(&blk) @@ -49,7 +45,7 @@ class TopicList :tags, :shared_drafts, :category, - :publish_read_state + :publish_read_state, ) def initialize(filter, current_user, topics, opts = nil) @@ -58,13 +54,9 @@ class TopicList @topics_input = topics @opts = opts || {} - if @opts[:category] - @category = Category.find_by(id: @opts[:category_id]) - end + @category = Category.find_by(id: @opts[:category_id]) if @opts[:category] - if @opts[:tags] - @tags = Tag.where(id: @opts[:tags]).all - end + @tags = Tag.where(id: @opts[:tags]).all if @opts[:tags] @publish_read_state = !!@opts[:publish_read_state] end @@ -89,19 +81,19 @@ class TopicList # Attach some data for serialization to each topic @topic_lookup = TopicUser.lookup_for(@current_user, @topics) if @current_user - @dismissed_topic_users_lookup = DismissedTopicUser.lookup_for(@current_user, @topics) if @current_user + @dismissed_topic_users_lookup = + DismissedTopicUser.lookup_for(@current_user, @topics) if @current_user post_action_type = if @current_user if @opts[:filter].present? - if @opts[:filter] == "liked" - PostActionType.types[:like] - end + PostActionType.types[:like] if @opts[:filter] == "liked" end end # Data for bookmarks or likes - post_action_lookup = PostAction.lookup_for(@current_user, @topics, post_action_type) if post_action_type + post_action_lookup = + PostAction.lookup_for(@current_user, @topics, post_action_type) if post_action_type # Create a lookup for all the user ids we need user_ids = [] @@ -125,23 +117,19 @@ class TopicList ft.user_data.post_action_data = { post_action_type => actions } end - ft.posters = ft.posters_summary( - user_lookup: user_lookup - ) + ft.posters = ft.posters_summary(user_lookup: user_lookup) - ft.participants = ft.participants_summary( - user_lookup: user_lookup, - user: @current_user - ) + ft.participants = ft.participants_summary(user_lookup: user_lookup, user: @current_user) ft.topic_list = self end topic_preloader_associations = [:image_upload, { topic_thumbnails: :optimized_image }] topic_preloader_associations.concat(DiscoursePluginRegistry.topic_preloader_associations.to_a) - ActiveRecord::Associations::Preloader - .new(records: @topics, associations: topic_preloader_associations) - .call + ActiveRecord::Associations::Preloader.new( + records: @topics, + associations: topic_preloader_associations, + ).call if preloaded_custom_fields.present? Topic.preload_custom_fields(@topics, preloaded_custom_fields) @@ -153,18 +141,19 @@ class TopicList end def attributes - { 'more_topics_url' => page } + { "more_topics_url" => page } end private def category_user_lookup - @category_user_lookup ||= begin - if @current_user - CategoryUser.lookup_for(@current_user, @topics.map(&:category_id).uniq) - else - [] + @category_user_lookup ||= + begin + if @current_user + CategoryUser.lookup_for(@current_user, @topics.map(&:category_id).uniq) + else + [] + end end - end end end diff --git a/app/models/topic_notifier.rb b/app/models/topic_notifier.rb index 2bcc6beb1c0..d733698d3d5 100644 --- a/app/models/topic_notifier.rb +++ b/app/models/topic_notifier.rb @@ -5,15 +5,15 @@ class TopicNotifier @topic = topic end - { watch!: :watching, + { + watch!: :watching, track!: :tracking, regular!: :regular, - mute!: :muted }.each_pair do |method_name, level| - + mute!: :muted, + }.each_pair do |method_name, level| define_method method_name do |user_id| change_level user_id, level end - end def watch_topic!(user_id, reason = :created_topic) diff --git a/app/models/topic_participants_summary.rb b/app/models/topic_participants_summary.rb index ba365075228..8f098bd84cc 100644 --- a/app/models/topic_participants_summary.rb +++ b/app/models/topic_participants_summary.rb @@ -18,7 +18,7 @@ class TopicParticipantsSummary def new_topic_poster_for(user) TopicPoster.new.tap do |topic_poster| topic_poster.user = user - topic_poster.extras = 'latest' if is_latest_poster?(user) + topic_poster.extras = "latest" if is_latest_poster?(user) end end diff --git a/app/models/topic_poster.rb b/app/models/topic_poster.rb index 762734799c2..99d8e3741d9 100644 --- a/app/models/topic_poster.rb +++ b/app/models/topic_poster.rb @@ -7,11 +7,11 @@ class TopicPoster < OpenStruct def attributes { - 'user' => user, - 'description' => description, - 'extras' => extras, - 'id' => id, - 'primary_group' => primary_group + "user" => user, + "description" => description, + "extras" => extras, + "id" => id, + "primary_group" => primary_group, } end diff --git a/app/models/topic_posters_summary.rb b/app/models/topic_posters_summary.rb index cded3ebbce8..abf47658ef9 100644 --- a/app/models/topic_posters_summary.rb +++ b/app/models/topic_posters_summary.rb @@ -2,7 +2,6 @@ # This is used in topic lists class TopicPostersSummary - # localization is fast, but this allows us to avoid # calling it in a loop which adds up def self.translations @@ -10,7 +9,7 @@ class TopicPostersSummary original_poster: I18n.t(:original_poster), most_recent_poster: I18n.t(:most_recent_poster), frequent_poster: I18n.t(:frequent_poster), - joiner: I18n.t(:poster_description_joiner) + joiner: I18n.t(:poster_description_joiner), } end @@ -35,34 +34,35 @@ class TopicPostersSummary topic_poster.primary_group = user_lookup.primary_groups[user.id] topic_poster.flair_group = user_lookup.flair_groups[user.id] if topic.last_post_user_id == user.id - topic_poster.extras = +'latest' - topic_poster.extras << ' single' if user_ids.uniq.size == 1 + topic_poster.extras = +"latest" + topic_poster.extras << " single" if user_ids.uniq.size == 1 end topic_poster end def descriptions_by_id(ids: nil) - @descriptions_by_id ||= begin - result = {} - ids = ids || user_ids + @descriptions_by_id ||= + begin + result = {} + ids = ids || user_ids - if id = ids.shift - result[id] ||= [] - result[id] << @translations[:original_poster] + if id = ids.shift + result[id] ||= [] + result[id] << @translations[:original_poster] + end + + if id = ids.shift + result[id] ||= [] + result[id] << @translations[:most_recent_poster] + end + + while id = ids.shift + result[id] ||= [] + result[id] << @translations[:frequent_poster] + end + + result end - - if id = ids.shift - result[id] ||= [] - result[id] << @translations[:most_recent_poster] - end - - while id = ids.shift - result[id] ||= [] - result[id] << @translations[:frequent_poster] - end - - result - end end def descriptions_for(user) @@ -90,7 +90,7 @@ class TopicPostersSummary end def user_ids - [ topic.user_id, topic.last_post_user_id, *topic.featured_user_ids ] + [topic.user_id, topic.last_post_user_id, *topic.featured_user_ids] end def user_lookup diff --git a/app/models/topic_tag.rb b/app/models/topic_tag.rb index 60943b393ca..67a8218d94a 100644 --- a/app/models/topic_tag.rb +++ b/app/models/topic_tag.rb @@ -27,7 +27,8 @@ class TopicTag < ActiveRecord::Base if topic.archetype == Archetype.private_message tag.decrement!(:pm_topic_count) else - if topic.category_id && stat = CategoryTagStat.find_by(tag_id: tag_id, category: topic.category_id) + if topic.category_id && + stat = CategoryTagStat.find_by(tag_id: tag_id, category: topic.category_id) stat.topic_count == 1 ? stat.destroy : stat.decrement!(:topic_count) end diff --git a/app/models/topic_thumbnail.rb b/app/models/topic_thumbnail.rb index f12161510f0..7800e4ed0b6 100644 --- a/app/models/topic_thumbnail.rb +++ b/app/models/topic_thumbnail.rb @@ -10,30 +10,34 @@ class TopicThumbnail < ActiveRecord::Base belongs_to :upload belongs_to :optimized_image - def self.find_or_create_for!(original, max_width: , max_height:) - existing = TopicThumbnail.find_by(upload: original, max_width: max_width, max_height: max_height) + def self.find_or_create_for!(original, max_width:, max_height:) + existing = + TopicThumbnail.find_by(upload: original, max_width: max_width, max_height: max_height) return existing if existing return nil if !SiteSetting.create_thumbnails? - target_width, target_height = ImageSizer.resize(original.width, original.height, { max_width: max_width, max_height: max_height }) + target_width, target_height = + ImageSizer.resize( + original.width, + original.height, + { max_width: max_width, max_height: max_height }, + ) if target_width < original.width && target_height < original.height optimized = OptimizedImage.create_for(original, target_width, target_height) end # may have been associated already, bulk insert will skip dupes - TopicThumbnail.insert_all([ - upload_id: original.id, - max_width: max_width, - max_height: max_height, - optimized_image_id: optimized&.id - ]) - - TopicThumbnail.find_by( - upload: original, - max_width: max_width, - max_height: max_height + TopicThumbnail.insert_all( + [ + upload_id: original.id, + max_width: max_width, + max_height: max_height, + optimized_image_id: optimized&.id, + ], ) + + TopicThumbnail.find_by(upload: original, max_width: max_width, max_height: max_height) end def self.ensure_consistency! @@ -48,8 +52,11 @@ class TopicThumbnail < ActiveRecord::Base .delete_all # Delete records for sizes which are no longer needed - sizes = Topic.thumbnail_sizes + ThemeModifierHelper.new(theme_ids: Theme.pluck(:id)).topic_thumbnail_sizes - sizes_sql = sizes.map { |s| "(max_width = #{s[0].to_i} AND max_height = #{s[1].to_i})" }.join(" OR ") + sizes = + Topic.thumbnail_sizes + + ThemeModifierHelper.new(theme_ids: Theme.pluck(:id)).topic_thumbnail_sizes + sizes_sql = + sizes.map { |s| "(max_width = #{s[0].to_i} AND max_height = #{s[1].to_i})" }.join(" OR ") TopicThumbnail.where.not(sizes_sql).delete_all end end diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index 82a00e8dc3c..22abd8b3c96 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -13,25 +13,26 @@ class TopicTimer < ActiveRecord::Base validates :topic_id, presence: true validates :execute_at, presence: true validates :status_type, presence: true - validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at] }, if: :public_type? - validates :status_type, uniqueness: { scope: [:topic_id, :deleted_at, :user_id] }, if: :private_type? + validates :status_type, uniqueness: { scope: %i[topic_id deleted_at] }, if: :public_type? + validates :status_type, uniqueness: { scope: %i[topic_id deleted_at user_id] }, if: :private_type? validates :category_id, presence: true, if: :publishing_to_category? validate :executed_at_in_future? validate :duration_in_range? - scope :scheduled_bump_topics, -> { where(status_type: TopicTimer.types[:bump], deleted_at: nil).pluck(:topic_id) } - scope :pending_timers, ->(before_time = Time.now.utc) do - where("execute_at <= :before_time AND deleted_at IS NULL", before_time: before_time) - end + scope :scheduled_bump_topics, + -> { where(status_type: TopicTimer.types[:bump], deleted_at: nil).pluck(:topic_id) } + scope :pending_timers, + ->(before_time = Time.now.utc) { + where("execute_at <= :before_time AND deleted_at IS NULL", before_time: before_time) + } before_save do self.created_at ||= Time.zone.now if execute_at self.public_type = self.public_type? - if (will_save_change_to_execute_at? && - !attribute_in_database(:execute_at).nil?) || - will_save_change_to_user_id? + if (will_save_change_to_execute_at? && !attribute_in_database(:execute_at).nil?) || + will_save_change_to_user_id? end end @@ -46,10 +47,10 @@ class TopicTimer < ActiveRecord::Base after_save do if (saved_change_to_execute_at? || saved_change_to_user_id?) if status_type == TopicTimer.types[:silent_close] || status_type == TopicTimer.types[:close] - topic.update_status('closed', false, user) if topic.closed? + topic.update_status("closed", false, user) if topic.closed? end if status_type == TopicTimer.types[:open] - topic.update_status('closed', true, user) if topic.open? + topic.update_status("closed", true, user) if topic.open? end end end @@ -72,22 +73,23 @@ class TopicTimer < ActiveRecord::Base bump: :bump_topic, delete_replies: :delete_replies, silent_close: :close_topic, - clear_slow_mode: :clear_slow_mode + clear_slow_mode: :clear_slow_mode, } end def self.types - @types ||= Enum.new( - close: 1, - open: 2, - publish_to_category: 3, - delete: 4, - reminder: 5, - bump: 6, - delete_replies: 7, - silent_close: 8, - clear_slow_mode: 9 - ) + @types ||= + Enum.new( + close: 1, + open: 2, + publish_to_category: 3, + delete: 4, + reminder: 5, + bump: 6, + delete_replies: 7, + silent_close: 8, + clear_slow_mode: 9, + ) end def self.public_types @@ -126,24 +128,29 @@ class TopicTimer < ActiveRecord::Base return if duration_minutes.blank? if duration_minutes.to_i <= 0 - errors.add(:duration_minutes, I18n.t( - 'activerecord.errors.models.topic_timer.attributes.duration_minutes.cannot_be_zero' - )) + errors.add( + :duration_minutes, + I18n.t("activerecord.errors.models.topic_timer.attributes.duration_minutes.cannot_be_zero"), + ) end if duration_minutes.to_i > MAX_DURATION_MINUTES - errors.add(:duration_minutes, I18n.t( - 'activerecord.errors.models.topic_timer.attributes.duration_minutes.exceeds_maximum' - )) + errors.add( + :duration_minutes, + I18n.t( + "activerecord.errors.models.topic_timer.attributes.duration_minutes.exceeds_maximum", + ), + ) end end def executed_at_in_future? return if created_at.blank? || (execute_at > created_at) - errors.add(:execute_at, I18n.t( - 'activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past' - )) + errors.add( + :execute_at, + I18n.t("activerecord.errors.models.topic_timer.attributes.execute_at.in_the_past"), + ) end def schedule_auto_delete_replies_job diff --git a/app/models/topic_tracking_state.rb b/app/models/topic_tracking_state.rb index 65bf2fa59ca..038f7ba7eb2 100644 --- a/app/models/topic_tracking_state.rb +++ b/app/models/topic_tracking_state.rb @@ -35,9 +35,7 @@ class TopicTrackingState return unless topic.regular? tag_ids, tags = nil - if SiteSetting.tagging_enabled - tag_ids, tags = topic.tags.pluck(:id, :name).transpose - end + tag_ids, tags = topic.tags.pluck(:id, :name).transpose if SiteSetting.tagging_enabled payload = { last_read_post_number: nil, @@ -45,7 +43,7 @@ class TopicTrackingState created_at: topic.created_at, category_id: topic.category_id, archetype: topic.archetype, - created_in_new_period: true + created_in_new_period: true, } if tags @@ -53,11 +51,7 @@ class TopicTrackingState payload[:topic_tag_ids] = tag_ids end - message = { - topic_id: topic.id, - message_type: NEW_TOPIC_MESSAGE_TYPE, - payload: payload - } + message = { topic_id: topic.id, message_type: NEW_TOPIC_MESSAGE_TYPE, payload: payload } group_ids = topic.category && topic.category.secure_group_ids @@ -69,9 +63,7 @@ class TopicTrackingState return unless topic.regular? tag_ids, tags = nil - if SiteSetting.tagging_enabled - tag_ids, tags = topic.tags.pluck(:id, :name).transpose - end + tag_ids, tags = topic.tags.pluck(:id, :name).transpose if SiteSetting.tagging_enabled message = { topic_id: topic.id, @@ -79,8 +71,8 @@ class TopicTrackingState payload: { bumped_at: topic.bumped_at, category_id: topic.category_id, - archetype: topic.archetype - } + archetype: topic.archetype, + }, } if tags @@ -102,32 +94,30 @@ class TopicTrackingState end def self.publish_muted(topic) - user_ids = topic.topic_users - .where(notification_level: NotificationLevels.all[:muted]) - .joins(:user) - .where("users.last_seen_at > ?", 7.days.ago) - .order("users.last_seen_at DESC") - .limit(100) - .pluck(:user_id) + user_ids = + topic + .topic_users + .where(notification_level: NotificationLevels.all[:muted]) + .joins(:user) + .where("users.last_seen_at > ?", 7.days.ago) + .order("users.last_seen_at DESC") + .limit(100) + .pluck(:user_id) return if user_ids.blank? - message = { - topic_id: topic.id, - message_type: MUTED_MESSAGE_TYPE, - } + message = { topic_id: topic.id, message_type: MUTED_MESSAGE_TYPE } MessageBus.publish("/latest", message.as_json, user_ids: user_ids) end def self.publish_unmuted(topic) - user_ids = User.watching_topic(topic) - .where("users.last_seen_at > ?", 7.days.ago) - .order("users.last_seen_at DESC") - .limit(100) - .pluck(:id) + user_ids = + User + .watching_topic(topic) + .where("users.last_seen_at > ?", 7.days.ago) + .order("users.last_seen_at DESC") + .limit(100) + .pluck(:id) return if user_ids.blank? - message = { - topic_id: topic.id, - message_type: UNMUTED_MESSAGE_TYPE, - } + message = { topic_id: topic.id, message_type: UNMUTED_MESSAGE_TYPE } MessageBus.publish("/latest", message.as_json, user_ids: user_ids) end @@ -137,16 +127,12 @@ class TopicTrackingState # perhaps cut down to users that are around in the last 7 days as well tags = nil tag_ids = nil - if include_tags_in_report? - tag_ids, tags = post.topic.tags.pluck(:id, :name).transpose - end + tag_ids, tags = post.topic.tags.pluck(:id, :name).transpose if include_tags_in_report? # We don't need to publish unread to the person who just made the post, # this is why they are excluded from the initial scope. - scope = TopicUser - .tracking(post.topic_id) - .includes(user: :user_stat) - .where.not(user_id: post.user_id) + scope = + TopicUser.tracking(post.topic_id).includes(user: :user_stat).where.not(user_id: post.user_id) group_ids = if post.post_type == Post.types[:whisper] @@ -156,9 +142,11 @@ class TopicTrackingState end if group_ids.present? - scope = scope - .joins("INNER JOIN group_users gu ON gu.user_id = topic_users.user_id") - .where("gu.group_id IN (?)", group_ids) + scope = + scope.joins("INNER JOIN group_users gu ON gu.user_id = topic_users.user_id").where( + "gu.group_id IN (?)", + group_ids, + ) end user_ids = scope.pluck(:user_id) @@ -177,36 +165,23 @@ class TopicTrackingState payload[:topic_tag_ids] = tag_ids end - message = { - topic_id: post.topic_id, - message_type: UNREAD_MESSAGE_TYPE, - payload: payload - } + message = { topic_id: post.topic_id, message_type: UNREAD_MESSAGE_TYPE, payload: payload } - MessageBus.publish("/unread", message.as_json, - user_ids: user_ids - ) + MessageBus.publish("/unread", message.as_json, user_ids: user_ids) end def self.publish_recover(topic) group_ids = topic.category && topic.category.secure_group_ids - message = { - topic_id: topic.id, - message_type: RECOVER_MESSAGE_TYPE - } + message = { topic_id: topic.id, message_type: RECOVER_MESSAGE_TYPE } MessageBus.publish("/recover", message.as_json, group_ids: group_ids) - end def self.publish_delete(topic) group_ids = topic.category && topic.category.secure_group_ids - message = { - topic_id: topic.id, - message_type: DELETE_MESSAGE_TYPE - } + message = { topic_id: topic.id, message_type: DELETE_MESSAGE_TYPE } MessageBus.publish("/delete", message.as_json, group_ids: group_ids) end @@ -214,10 +189,7 @@ class TopicTrackingState def self.publish_destroy(topic) group_ids = topic.category && topic.category.secure_group_ids - message = { - topic_id: topic.id, - message_type: DESTROY_MESSAGE_TYPE - } + message = { topic_id: topic.id, message_type: DESTROY_MESSAGE_TYPE } MessageBus.publish("/destroy", message.as_json, group_ids: group_ids) end @@ -229,25 +201,21 @@ class TopicTrackingState topic_id: topic_id, user: user, last_read_post_number: last_read_post_number, - notification_level: notification_level + notification_level: notification_level, ) end def self.publish_dismiss_new(user_id, topic_ids: []) - message = { - message_type: DISMISS_NEW_MESSAGE_TYPE, - payload: { - topic_ids: topic_ids - } - } + message = { message_type: DISMISS_NEW_MESSAGE_TYPE, payload: { topic_ids: topic_ids } } MessageBus.publish(self.unread_channel_key(user_id), message.as_json, user_ids: [user_id]) end def self.new_filter_sql - TopicQuery.new_filter( - Topic, treat_as_new_topic_clause_sql: treat_as_new_topic_clause - ).where_clause.ast.to_sql + - " AND topics.created_at > :min_new_topic_date" + + TopicQuery + .new_filter(Topic, treat_as_new_topic_clause_sql: treat_as_new_topic_clause) + .where_clause + .ast + .to_sql + " AND topics.created_at > :min_new_topic_date" + " AND dismissed_topic_users.id IS NULL" end @@ -256,13 +224,18 @@ class TopicTrackingState end def self.treat_as_new_topic_clause - User.where("GREATEST(CASE + User + .where( + "GREATEST(CASE WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at) ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, :default_duration)) END, u.created_at, :min_date)", - treat_as_new_topic_params - ).where_clause.ast.to_sql + treat_as_new_topic_params, + ) + .where_clause + .ast + .to_sql end def self.treat_as_new_topic_params @@ -271,7 +244,7 @@ class TopicTrackingState last_visit: User::NewTopicDuration::LAST_VISIT, always: User::NewTopicDuration::ALWAYS, default_duration: SiteSetting.default_other_new_topic_duration_minutes, - min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime + min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime, } end @@ -296,31 +269,32 @@ class TopicTrackingState sql = new_and_unread_sql(topic_id, user, tag_ids) sql = tags_included_wrapped_sql(sql) - report = DB.query( - sql + "\n\n LIMIT :max_topics", - { - user_id: user.id, - topic_id: topic_id, - min_new_topic_date: Time.at(SiteSetting.min_new_topics_time).to_datetime, - max_topics: TopicTrackingState::MAX_TOPICS, - } - .merge(treat_as_new_topic_params) - ) + report = + DB.query( + sql + "\n\n LIMIT :max_topics", + { + user_id: user.id, + topic_id: topic_id, + min_new_topic_date: Time.at(SiteSetting.min_new_topics_time).to_datetime, + max_topics: TopicTrackingState::MAX_TOPICS, + }.merge(treat_as_new_topic_params), + ) report end def self.new_and_unread_sql(topic_id, user, tag_ids) - sql = report_raw_sql( - topic_id: topic_id, - skip_unread: true, - skip_order: true, - staff: user.staff?, - admin: user.admin?, - whisperer: user.whisperer?, - user: user, - muted_tag_ids: tag_ids - ) + sql = + report_raw_sql( + topic_id: topic_id, + skip_unread: true, + skip_order: true, + staff: user.staff?, + admin: user.admin?, + whisperer: user.whisperer?, + user: user, + muted_tag_ids: tag_ids, + ) sql << "\nUNION ALL\n\n" @@ -333,13 +307,12 @@ class TopicTrackingState admin: user.admin?, whisperer: user.whisperer?, user: user, - muted_tag_ids: tag_ids + muted_tag_ids: tag_ids, ) end def self.tags_included_wrapped_sql(sql) - if SiteSetting.tagging_enabled && TopicTrackingState.include_tags_in_report? - return <<~SQL + return <<~SQL if SiteSetting.tagging_enabled && TopicTrackingState.include_tags_in_report? WITH tags_included_cte AS ( #{sql} ) @@ -350,7 +323,6 @@ class TopicTrackingState ) tags FROM tags_included_cte SQL - end sql end @@ -395,7 +367,9 @@ class TopicTrackingState new_filter_sql end - select_sql = select || " + select_sql = + select || + " DISTINCT topics.id as topic_id, u.id as user_id, topics.created_at, @@ -441,11 +415,13 @@ class TopicTrackingState tags_filter = "" - if muted_tag_ids.present? && ['always', 'only_muted'].include?(SiteSetting.remove_muted_tags_from_latest) - existing_tags_sql = "(select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id)" - muted_tags_array_sql = "ARRAY[#{muted_tag_ids.join(',')}]" + if muted_tag_ids.present? && + %w[always only_muted].include?(SiteSetting.remove_muted_tags_from_latest) + existing_tags_sql = + "(select array_agg(tag_id) from topic_tags where topic_tags.topic_id = topics.id)" + muted_tags_array_sql = "ARRAY[#{muted_tag_ids.join(",")}]" - if SiteSetting.remove_muted_tags_from_latest == 'always' + if SiteSetting.remove_muted_tags_from_latest == "always" tags_filter = <<~SQL NOT ( COALESCE(#{existing_tags_sql}, ARRAY[]::int[]) && #{muted_tags_array_sql} @@ -487,13 +463,9 @@ class TopicTrackingState ) SQL - if topic_id - sql << " AND topics.id = :topic_id" - end + sql << " AND topics.id = :topic_id" if topic_id - unless skip_order - sql << " ORDER BY topics.bumped_at DESC" - end + sql << " ORDER BY topics.bumped_at DESC" unless skip_order sql end @@ -503,7 +475,11 @@ class TopicTrackingState end def self.publish_read_indicator_on_write(topic_id, last_read_post_number, user_id) - topic = Topic.includes(:allowed_groups).select(:highest_post_number, :archetype, :id).find_by(id: topic_id) + topic = + Topic + .includes(:allowed_groups) + .select(:highest_post_number, :archetype, :id) + .find_by(id: topic_id) if topic&.private_message? groups = read_allowed_groups_of(topic) @@ -512,7 +488,11 @@ class TopicTrackingState end def self.publish_read_indicator_on_read(topic_id, last_read_post_number, user_id) - topic = Topic.includes(:allowed_groups).select(:highest_post_number, :archetype, :id).find_by(id: topic_id) + topic = + Topic + .includes(:allowed_groups) + .select(:highest_post_number, :archetype, :id) + .find_by(id: topic_id) if topic&.private_message? groups = read_allowed_groups_of(topic) @@ -523,14 +503,21 @@ class TopicTrackingState end def self.read_allowed_groups_of(topic) - topic.allowed_groups + topic + .allowed_groups .joins(:group_users) .where(publish_read_state: true) - .select('ARRAY_AGG(group_users.user_id) AS members', :name, :id) - .group('groups.id') + .select("ARRAY_AGG(group_users.user_id) AS members", :name, :id) + .group("groups.id") end - def self.update_topic_list_read_indicator(topic, groups, last_read_post_number, user_id, write_event) + def self.update_topic_list_read_indicator( + topic, + groups, + last_read_post_number, + user_id, + write_event + ) return unless last_read_post_number == topic.highest_post_number message = { topic_id: topic.id, show_indicator: write_event }.as_json groups_to_update = [] @@ -546,7 +533,11 @@ class TopicTrackingState end return if groups_to_update.empty? - MessageBus.publish("/private-messages/unread-indicator/#{topic.id}", message, user_ids: groups_to_update.flat_map(&:members)) + MessageBus.publish( + "/private-messages/unread-indicator/#{topic.id}", + message, + user_ids: groups_to_update.flat_map(&:members), + ) end def self.trigger_post_read_count_update(post, groups, last_read_post_number, user_id) diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb index 638fa4f891c..0e00f68f2de 100644 --- a/app/models/topic_user.rb +++ b/app/models/topic_user.rb @@ -2,7 +2,7 @@ class TopicUser < ActiveRecord::Base self.ignored_columns = [ - :highest_seen_post_number # Remove after 01 Jan 2022 + :highest_seen_post_number, # Remove after 01 Jan 2022 ] belongs_to :user @@ -11,20 +11,18 @@ class TopicUser < ActiveRecord::Base # used for serialization attr_accessor :post_action_data - scope :level, lambda { |topic_id, level| - where(topic_id: topic_id) - .where("COALESCE(topic_users.notification_level, :regular) >= :level", - regular: TopicUser.notification_levels[:regular], - level: TopicUser.notification_levels[level]) - } + scope :level, + lambda { |topic_id, level| + where(topic_id: topic_id).where( + "COALESCE(topic_users.notification_level, :regular) >= :level", + regular: TopicUser.notification_levels[:regular], + level: TopicUser.notification_levels[level], + ) + } - scope :tracking, lambda { |topic_id| - level(topic_id, :tracking) - } + scope :tracking, lambda { |topic_id| level(topic_id, :tracking) } - scope :watching, lambda { |topic_id| - level(topic_id, :watching) - } + scope :watching, lambda { |topic_id| level(topic_id, :watching) } def topic_bookmarks Bookmark.where(topic: topic, user: user) @@ -32,38 +30,62 @@ class TopicUser < ActiveRecord::Base # Class methods class << self - # Enums def notification_levels NotificationLevels.topic_levels end def notification_reasons - @notification_reasons ||= Enum.new(created_topic: 1, - user_changed: 2, - user_interacted: 3, - created_post: 4, - auto_watch: 5, - auto_watch_category: 6, - auto_mute_category: 7, - auto_track_category: 8, - plugin_changed: 9, - auto_watch_tag: 10, - auto_mute_tag: 11, - auto_track_tag: 12) + @notification_reasons ||= + Enum.new( + created_topic: 1, + user_changed: 2, + user_interacted: 3, + created_post: 4, + auto_watch: 5, + auto_watch_category: 6, + auto_mute_category: 7, + auto_track_category: 8, + plugin_changed: 9, + auto_watch_tag: 10, + auto_mute_tag: 11, + auto_track_tag: 12, + ) end def auto_notification(user_id, topic_id, reason, notification_level) - should_change = TopicUser - .where(user_id: user_id, topic_id: topic_id) - .where("notifications_reason_id IS NULL OR (notification_level < :max AND notification_level > :min)", max: notification_level, min: notification_levels[:regular]) - .exists? + should_change = + TopicUser + .where(user_id: user_id, topic_id: topic_id) + .where( + "notifications_reason_id IS NULL OR (notification_level < :max AND notification_level > :min)", + max: notification_level, + min: notification_levels[:regular], + ) + .exists? - change(user_id, topic_id, notification_level: notification_level, notifications_reason_id: reason) if should_change + if should_change + change( + user_id, + topic_id, + notification_level: notification_level, + notifications_reason_id: reason, + ) + end end - def auto_notification_for_staging(user_id, topic_id, reason, notification_level = notification_levels[:watching]) - change(user_id, topic_id, notification_level: notification_level, notifications_reason_id: reason) + def auto_notification_for_staging( + user_id, + topic_id, + reason, + notification_level = notification_levels[:watching] + ) + change( + user_id, + topic_id, + notification_level: notification_level, + notifications_reason_id: reason, + ) end def unwatch_categories!(user, category_ids) @@ -80,14 +102,15 @@ class TopicUser < ActiveRecord::Base WHERE t.id = tu.topic_id AND tu.notification_level <> :muted AND category_id IN (:category_ids) AND tu.user_id = :user_id SQL - DB.exec(sql, + DB.exec( + sql, watching: notification_levels[:watching], tracking: notification_levels[:tracking], regular: notification_levels[:regular], muted: notification_levels[:muted], category_ids: category_ids, user_id: user.id, - track_threshold: track_threshold + track_threshold: track_threshold, ) end @@ -119,9 +142,7 @@ class TopicUser < ActiveRecord::Base # it then creates the row instead. def change(user_id, topic_id, attrs) # For plugin compatibility, remove after 01 Jan 2022 - if attrs[:highest_seen_post_number] - attrs.delete(:highest_seen_post_number) - end + attrs.delete(:highest_seen_post_number) if attrs[:highest_seen_post_number] # Sometimes people pass objs instead of the ids. We can handle that. topic_id = topic_id.id if topic_id.is_a?(::Topic) @@ -143,15 +164,17 @@ class TopicUser < ActiveRecord::Base rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all([attrs_sql, *vals]) - if rows == 0 - create_missing_record(user_id, topic_id, attrs) - end + create_missing_record(user_id, topic_id, attrs) if rows == 0 end if attrs[:notification_level] - notification_level_change(user_id, topic_id, attrs[:notification_level], attrs[:notifications_reason_id]) + notification_level_change( + user_id, + topic_id, + attrs[:notification_level], + attrs[:notifications_reason_id], + ) end - rescue ActiveRecord::RecordNotUnique # In case of a race condition to insert, do nothing end @@ -161,67 +184,90 @@ class TopicUser < ActiveRecord::Base message[:notifications_reason_id] = reason_id if reason_id MessageBus.publish("/topic/#{topic_id}", message, user_ids: [user_id]) - DiscourseEvent.trigger(:topic_notification_level_changed, + DiscourseEvent.trigger( + :topic_notification_level_changed, notification_level, user_id, - topic_id + topic_id, ) - end def create_missing_record(user_id, topic_id, attrs) now = DateTime.now unless attrs[:notification_level] - category_notification_level = CategoryUser.where(user_id: user_id) - .where("category_id IN (SELECT category_id FROM topics WHERE id = :id)", id: topic_id) - .where("notification_level IN (:levels)", levels: [CategoryUser.notification_levels[:watching], - CategoryUser.notification_levels[:tracking]]) - .order("notification_level DESC") - .limit(1) - .pluck(:notification_level) - .first + category_notification_level = + CategoryUser + .where(user_id: user_id) + .where("category_id IN (SELECT category_id FROM topics WHERE id = :id)", id: topic_id) + .where( + "notification_level IN (:levels)", + levels: [ + CategoryUser.notification_levels[:watching], + CategoryUser.notification_levels[:tracking], + ], + ) + .order("notification_level DESC") + .limit(1) + .pluck(:notification_level) + .first - tag_notification_level = TagUser.where(user_id: user_id) - .where("tag_id IN (SELECT tag_id FROM topic_tags WHERE topic_id = :id)", id: topic_id) - .where("notification_level IN (:levels)", levels: [CategoryUser.notification_levels[:watching], - CategoryUser.notification_levels[:tracking]]) - .order("notification_level DESC") - .limit(1) - .pluck(:notification_level) - .first + tag_notification_level = + TagUser + .where(user_id: user_id) + .where("tag_id IN (SELECT tag_id FROM topic_tags WHERE topic_id = :id)", id: topic_id) + .where( + "notification_level IN (:levels)", + levels: [ + CategoryUser.notification_levels[:watching], + CategoryUser.notification_levels[:tracking], + ], + ) + .order("notification_level DESC") + .limit(1) + .pluck(:notification_level) + .first - if category_notification_level && !(tag_notification_level && (tag_notification_level > category_notification_level)) + if category_notification_level && + !(tag_notification_level && (tag_notification_level > category_notification_level)) attrs[:notification_level] = category_notification_level attrs[:notifications_changed_at] = DateTime.now - attrs[:notifications_reason_id] = category_notification_level == CategoryUser.notification_levels[:watching] ? - TopicUser.notification_reasons[:auto_watch_category] : + attrs[:notifications_reason_id] = ( + if category_notification_level == CategoryUser.notification_levels[:watching] + TopicUser.notification_reasons[:auto_watch_category] + else TopicUser.notification_reasons[:auto_track_category] - + end + ) elsif tag_notification_level attrs[:notification_level] = tag_notification_level attrs[:notifications_changed_at] = DateTime.now - attrs[:notifications_reason_id] = tag_notification_level == TagUser.notification_levels[:watching] ? - TopicUser.notification_reasons[:auto_watch_tag] : + attrs[:notifications_reason_id] = ( + if tag_notification_level == TagUser.notification_levels[:watching] + TopicUser.notification_reasons[:auto_watch_tag] + else TopicUser.notification_reasons[:auto_track_tag] + end + ) end - end unless attrs[:notification_level] if Topic.private_messages.where(id: topic_id).exists? && - Notification.where( - user_id: user_id, - topic_id: topic_id, - notification_type: Notification.types[:invited_to_private_message] - ).exists? - - group_notification_level = Group - .joins("LEFT OUTER JOIN group_users gu ON gu.group_id = groups.id AND gu.user_id = #{user_id}") - .joins("LEFT OUTER JOIN topic_allowed_groups tag ON tag.topic_id = #{topic_id}") - .where("gu.id IS NOT NULL AND tag.id IS NOT NULL") - .pluck(:default_notification_level) - .first + Notification.where( + user_id: user_id, + topic_id: topic_id, + notification_type: Notification.types[:invited_to_private_message], + ).exists? + group_notification_level = + Group + .joins( + "LEFT OUTER JOIN group_users gu ON gu.group_id = groups.id AND gu.user_id = #{user_id}", + ) + .joins("LEFT OUTER JOIN topic_allowed_groups tag ON tag.topic_id = #{topic_id}") + .where("gu.id IS NOT NULL AND tag.id IS NOT NULL") + .pluck(:default_notification_level) + .first if group_notification_level.present? attrs[:notification_level] = group_notification_level @@ -229,7 +275,8 @@ class TopicUser < ActiveRecord::Base attrs[:notification_level] = notification_levels[:watching] end else - auto_track_after = UserOption.where(user_id: user_id).pluck_first(:auto_track_topics_after_msecs) + auto_track_after = + UserOption.where(user_id: user_id).pluck_first(:auto_track_topics_after_msecs) auto_track_after ||= SiteSetting.default_other_auto_track_topics_after_msecs if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed].to_i || 0) @@ -238,12 +285,14 @@ class TopicUser < ActiveRecord::Base end end - TopicUser.create!(attrs.merge!( - user_id: user_id, - topic_id: topic_id, - first_visited_at: now , - last_visited_at: now - )) + TopicUser.create!( + attrs.merge!( + user_id: user_id, + topic_id: topic_id, + first_visited_at: now, + last_visited_at: now, + ), + ) DiscourseEvent.trigger(:topic_first_visited_by_user, topic_id, user_id) end @@ -252,9 +301,7 @@ class TopicUser < ActiveRecord::Base now = DateTime.now rows = TopicUser.where(topic_id: topic_id, user_id: user_id).update_all(last_visited_at: now) - if rows == 0 - change(user_id, topic_id, last_visited_at: now, first_visited_at: now) - end + change(user_id, topic_id, last_visited_at: now, first_visited_at: now) if rows == 0 end # Update the last read and the last seen post count, but only if it doesn't exist. @@ -289,7 +336,8 @@ class TopicUser < ActiveRecord::Base t.archetype SQL - INSERT_TOPIC_USER_SQL = "INSERT INTO topic_users (user_id, topic_id, last_read_post_number, last_visited_at, first_visited_at, notification_level) + INSERT_TOPIC_USER_SQL = + "INSERT INTO topic_users (user_id, topic_id, last_read_post_number, last_visited_at, first_visited_at, notification_level) SELECT :user_id, :topic_id, :post_number, :now, :now, :new_status FROM topics AS ft JOIN users u on u.id = :user_id @@ -309,7 +357,7 @@ class TopicUser < ActiveRecord::Base now: DateTime.now, msecs: msecs, tracking: notification_levels[:tracking], - threshold: SiteSetting.default_other_auto_track_topics_after_msecs + threshold: SiteSetting.default_other_auto_track_topics_after_msecs, } rows = DB.query(UPDATE_TOPIC_USER_SQL, args) @@ -327,23 +375,22 @@ class TopicUser < ActiveRecord::Base post_number: post_number, user: user, notification_level: after, - private_message: archetype == Archetype.private_message + private_message: archetype == Archetype.private_message, ) end - if new_posts_read > 0 - user.update_posts_read!(new_posts_read, mobile: opts[:mobile]) - end + user.update_posts_read!(new_posts_read, mobile: opts[:mobile]) if new_posts_read > 0 - if before != after - notification_level_change(user.id, topic_id, after, nil) - end + notification_level_change(user.id, topic_id, after, nil) if before != after end if rows.length == 0 # The user read at least one post in a topic that they haven't viewed before. args[:new_status] = notification_levels[:regular] - if (user.user_option.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs) == 0 + if ( + user.user_option.auto_track_topics_after_msecs || + SiteSetting.default_other_auto_track_topics_after_msecs + ) == 0 args[:new_status] = notification_levels[:tracking] end @@ -352,10 +399,7 @@ class TopicUser < ActiveRecord::Base post_number: post_number, user: user, notification_level: args[:new_status], - private_message: Topic.exists?( - archetype: Archetype.private_message, - id: topic_id - ) + private_message: Topic.exists?(archetype: Archetype.private_message, id: topic_id), ) user.update_posts_read!(new_posts_read, mobile: opts[:mobile]) @@ -387,14 +431,8 @@ class TopicUser < ActiveRecord::Base TopicTrackingState end - klass.publish_read( - topic_id, - post_number, - user, - notification_level - ) + klass.publish_read(topic_id, post_number, user, notification_level) end - end # Update the cached topic_user.liked column based on data @@ -441,16 +479,17 @@ class TopicUser < ActiveRecord::Base WHERE x.topic_id = tu.topic_id AND x.user_id = tu.user_id AND x.state != tu.#{action_type_name} SQL - if user_id - builder.where("tu2.user_id IN (:user_id)", user_id: user_id) - end + builder.where("tu2.user_id IN (:user_id)", user_id: user_id) if user_id - if topic_id - builder.where("tu2.topic_id IN (:topic_id)", topic_id: topic_id) - end + builder.where("tu2.topic_id IN (:topic_id)", topic_id: topic_id) if topic_id if post_id - builder.where("tu2.topic_id IN (SELECT topic_id FROM posts WHERE id IN (:post_id))", post_id: post_id) if !topic_id + if !topic_id + builder.where( + "tu2.topic_id IN (SELECT topic_id FROM posts WHERE id IN (:post_id))", + post_id: post_id, + ) + end builder.where(<<~SQL, post_id: post_id) tu2.user_id IN ( SELECT user_id FROM post_actions @@ -515,13 +554,10 @@ SQL ) SQL - if topic_id - builder.where("t.topic_id = :topic_id", topic_id: topic_id) - end + builder.where("t.topic_id = :topic_id", topic_id: topic_id) if topic_id builder.exec end - end # == Schema Information diff --git a/app/models/topic_view_item.rb b/app/models/topic_view_item.rb index d37e8ec7a62..4472ed4b1d6 100644 --- a/app/models/topic_view_item.rb +++ b/app/models/topic_view_item.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'ipaddr' +require "ipaddr" # awkward TopicView is taken class TopicViewItem < ActiveRecord::Base - self.table_name = 'topic_views' + self.table_name = "topic_views" belongs_to :user belongs_to :topic validates_presence_of :topic_id, :ip_address, :viewed_at @@ -24,7 +24,8 @@ class TopicViewItem < ActiveRecord::Base TopicViewItem.transaction do # this is called real frequently, working hard to avoid exceptions - sql = "INSERT INTO topic_views (topic_id, ip_address, viewed_at, user_id) + sql = + "INSERT INTO topic_views (topic_id, ip_address, viewed_at, user_id) SELECT :topic_id, :ip_address, :viewed_at, :user_id WHERE NOT EXISTS ( SELECT 1 FROM topic_views @@ -42,17 +43,18 @@ class TopicViewItem < ActiveRecord::Base result = builder.exec(topic_id: topic_id, ip_address: ip, viewed_at: at, user_id: user_id) - Topic.where(id: topic_id).update_all 'views = views + 1' + Topic.where(id: topic_id).update_all "views = views + 1" if result > 0 - UserStat.where(user_id: user_id).update_all 'topics_entered = topics_entered + 1' if user_id + if user_id + UserStat.where(user_id: user_id).update_all "topics_entered = topics_entered + 1" + end end # Update the views count in the parent, if it exists. end end end - end # == Schema Information diff --git a/app/models/translation_override.rb b/app/models/translation_override.rb index 932ac08fa66..73137243ed0 100644 --- a/app/models/translation_override.rb +++ b/app/models/translation_override.rb @@ -3,17 +3,17 @@ class TranslationOverride < ActiveRecord::Base # Allowlist i18n interpolation keys that can be included when customizing translations ALLOWED_CUSTOM_INTERPOLATION_KEYS = { - [ - "user_notifications.user_", - "user_notifications.only_reply_by_email", - "user_notifications.reply_by_email", - "user_notifications.visit_link_to_respond", - "user_notifications.header_instructions", - "user_notifications.pm_participants", - "unsubscribe_mailing_list", - "unsubscribe_link_and_mail", - "unsubscribe_link", - ] => %w{ + %w[ + user_notifications.user_ + user_notifications.only_reply_by_email + user_notifications.reply_by_email + user_notifications.visit_link_to_respond + user_notifications.header_instructions + user_notifications.pm_participants + unsubscribe_mailing_list + unsubscribe_link_and_mail + unsubscribe_link + ] => %w[ topic_title topic_title_url_encoded message @@ -34,12 +34,13 @@ class TranslationOverride < ActiveRecord::Base optional_pm optional_cat optional_tags - } + ], } include HasSanitizableFields include ActiveSupport::Deprecation::DeprecatedConstantAccessor - deprecate_constant 'CUSTOM_INTERPOLATION_KEYS_WHITELIST', 'TranslationOverride::ALLOWED_CUSTOM_INTERPOLATION_KEYS' + deprecate_constant "CUSTOM_INTERPOLATION_KEYS_WHITELIST", + "TranslationOverride::ALLOWED_CUSTOM_INTERPOLATION_KEYS" validates_uniqueness_of :translation_key, scope: :locale validates_presence_of :locale, :translation_key, :value @@ -50,10 +51,11 @@ class TranslationOverride < ActiveRecord::Base params = { locale: locale, translation_key: key } translation_override = find_or_initialize_by(params) - sanitized_value = translation_override.sanitize_field(value, additional_attributes: ['data-auto-route']) + sanitized_value = + translation_override.sanitize_field(value, additional_attributes: ["data-auto-route"]) data = { value: sanitized_value } - if key.end_with?('_MF') + if key.end_with?("_MF") _, filename = JsLocaleHelper.find_message_format_locale([locale], fallback_to_english: false) data[:compiled_js] = JsLocaleHelper.compile_message_format(filename, locale, sanitized_value) end @@ -74,22 +76,18 @@ class TranslationOverride < ActiveRecord::Base overrides = TranslationOverride.pluck(:locale, :translation_key) overrides = overrides.group_by(&:first).map { |k, a| [k, a.map(&:last)] } - overrides.each do |locale, keys| - clear_cached_keys!(locale, keys) - end + overrides.each { |locale, keys| clear_cached_keys!(locale, keys) } end def self.reload_locale! I18n.reload! ExtraLocalesController.clear_cache! - MessageBus.publish('/i18n-flush', refresh: true) + MessageBus.publish("/i18n-flush", refresh: true) end def self.clear_cached_keys!(locale, keys) should_clear_anon_cache = false - keys.each do |key| - should_clear_anon_cache |= expire_cache(locale, key) - end + keys.each { |key| should_clear_anon_cache |= expire_cache(locale, key) } Site.clear_anon_cache! if should_clear_anon_cache end @@ -99,9 +97,9 @@ class TranslationOverride < ActiveRecord::Base end def self.expire_cache(locale, key) - if key.starts_with?('post_action_types.') + if key.starts_with?("post_action_types.") ApplicationSerializer.expire_cache_fragment!("post_action_types_#{locale}") - elsif key.starts_with?('topic_flag_types.') + elsif key.starts_with?("topic_flag_types.") ApplicationSerializer.expire_cache_fragment!("post_action_flag_types_#{locale}") else return false @@ -119,9 +117,7 @@ class TranslationOverride < ActiveRecord::Base def check_interpolation_keys transformed_key = transform_pluralized_key(translation_key) - original_text = I18n.overrides_disabled do - I18n.t(transformed_key, locale: :en) - end + original_text = I18n.overrides_disabled { I18n.t(transformed_key, locale: :en) } if original_text original_interpolation_keys = I18nInterpolationKeysFinder.find(original_text) @@ -129,20 +125,21 @@ class TranslationOverride < ActiveRecord::Base custom_interpolation_keys = [] ALLOWED_CUSTOM_INTERPOLATION_KEYS.select do |keys, value| - if keys.any? { |key| transformed_key.start_with?(key) } - custom_interpolation_keys = value - end + custom_interpolation_keys = value if keys.any? { |key| transformed_key.start_with?(key) } end - invalid_keys = (original_interpolation_keys | new_interpolation_keys) - - original_interpolation_keys - - custom_interpolation_keys + invalid_keys = + (original_interpolation_keys | new_interpolation_keys) - original_interpolation_keys - + custom_interpolation_keys if invalid_keys.present? - self.errors.add(:base, I18n.t( - 'activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys', - keys: invalid_keys.join(', ') - )) + self.errors.add( + :base, + I18n.t( + "activerecord.errors.models.translation_overrides.attributes.value.invalid_interpolation_keys", + keys: invalid_keys.join(", "), + ), + ) false end @@ -151,7 +148,7 @@ class TranslationOverride < ActiveRecord::Base def transform_pluralized_key(key) match = key.match(/(.*)\.(zero|two|few|many)$/) - match ? match.to_a.second + '.other' : key + match ? match.to_a.second + ".other" : key end end diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb index 61970b2b9eb..8c2889d6fa7 100644 --- a/app/models/trust_level3_requirements.rb +++ b/app/models/trust_level3_requirements.rb @@ -3,13 +3,12 @@ # This class performs calculations to determine if a user qualifies for # the Leader (3) trust level. class TrustLevel3Requirements - class PenaltyCounts attr_reader :silenced, :suspended def initialize(user, row) - @silenced = row['silence_count'] || 0 - @suspended = row['suspend_count'] || 0 + @silenced = row["silence_count"] || 0 + @suspended = row["suspend_count"] || 0 # If penalty started more than 6 months ago and still continues, it will # not be selected by the query from 'penalty_counts'. @@ -27,19 +26,32 @@ class TrustLevel3Requirements LOW_WATER_MARK = 0.9 FORGIVENESS_PERIOD = 6.months - attr_accessor :days_visited, :min_days_visited, - :num_topics_replied_to, :min_topics_replied_to, - :topics_viewed, :min_topics_viewed, - :posts_read, :min_posts_read, - :topics_viewed_all_time, :min_topics_viewed_all_time, - :posts_read_all_time, :min_posts_read_all_time, - :num_flagged_posts, :max_flagged_posts, - :num_likes_given, :min_likes_given, - :num_likes_received, :min_likes_received, - :num_likes_received, :min_likes_received, - :num_likes_received_days, :min_likes_received_days, - :num_likes_received_users, :min_likes_received_users, - :trust_level_locked, :on_grace_period + attr_accessor :days_visited, + :min_days_visited, + :num_topics_replied_to, + :min_topics_replied_to, + :topics_viewed, + :min_topics_viewed, + :posts_read, + :min_posts_read, + :topics_viewed_all_time, + :min_topics_viewed_all_time, + :posts_read_all_time, + :min_posts_read_all_time, + :num_flagged_posts, + :max_flagged_posts, + :num_likes_given, + :min_likes_given, + :num_likes_received, + :min_likes_received, + :num_likes_received, + :min_likes_received, + :num_likes_received_days, + :min_likes_received_days, + :num_likes_received_users, + :min_likes_received_users, + :trust_level_locked, + :on_grace_period def initialize(user) @user = user @@ -48,18 +60,12 @@ class TrustLevel3Requirements def requirements_met? return false if trust_level_locked - (!@user.suspended?) && - (!@user.silenced?) && - penalty_counts.total == 0 && - days_visited >= min_days_visited && - num_topics_replied_to >= min_topics_replied_to && - topics_viewed >= min_topics_viewed && - posts_read >= min_posts_read && - num_flagged_posts <= max_flagged_posts && - num_flagged_by_users <= max_flagged_by_users && + (!@user.suspended?) && (!@user.silenced?) && penalty_counts.total == 0 && + days_visited >= min_days_visited && num_topics_replied_to >= min_topics_replied_to && + topics_viewed >= min_topics_viewed && posts_read >= min_posts_read && + num_flagged_posts <= max_flagged_posts && num_flagged_by_users <= max_flagged_by_users && topics_viewed_all_time >= min_topics_viewed_all_time && - posts_read_all_time >= min_posts_read_all_time && - num_likes_given >= min_likes_given && + posts_read_all_time >= min_posts_read_all_time && num_likes_given >= min_likes_given && num_likes_received >= min_likes_received && num_likes_received_users >= min_likes_received_users && num_likes_received_days >= min_likes_received_days @@ -69,14 +75,11 @@ class TrustLevel3Requirements return false if trust_level_locked return false if SiteSetting.default_trust_level > 2 - @user.suspended? || - @user.silenced? || - penalty_counts.total > 0 || + @user.suspended? || @user.silenced? || penalty_counts.total > 0 || days_visited < min_days_visited * LOW_WATER_MARK || num_topics_replied_to < min_topics_replied_to * LOW_WATER_MARK || topics_viewed < min_topics_viewed * LOW_WATER_MARK || - posts_read < min_posts_read * LOW_WATER_MARK || - num_flagged_posts > max_flagged_posts || + posts_read < min_posts_read * LOW_WATER_MARK || num_flagged_posts > max_flagged_posts || num_flagged_by_users > max_flagged_by_users || topics_viewed_all_time < min_topics_viewed_all_time || posts_read_all_time < min_posts_read_all_time || @@ -110,7 +113,7 @@ class TrustLevel3Requirements unsilence_user: UserHistory.actions[:unsilence_user], suspend_user: UserHistory.actions[:suspend_user], unsuspend_user: UserHistory.actions[:unsuspend_user], - since: FORGIVENESS_PERIOD.ago + since: FORGIVENESS_PERIOD.ago, } sql = <<~SQL @@ -151,31 +154,38 @@ class TrustLevel3Requirements end def topics_viewed_query - TopicViewItem.where(user_id: @user.id) + TopicViewItem + .where(user_id: @user.id) .joins(:topic) .where("topics.archetype <> ?", Archetype.private_message) .select("topic_id") end def topics_viewed - topics_viewed_query.where('viewed_at > ?', time_period.days.ago).count + topics_viewed_query.where("viewed_at > ?", time_period.days.ago).count end def min_topics_viewed [ - (TrustLevel3Requirements.num_topics_in_time_period.to_i * (SiteSetting.tl3_requires_topics_viewed.to_f / 100.0)).round, - SiteSetting.tl3_requires_topics_viewed_cap + ( + TrustLevel3Requirements.num_topics_in_time_period.to_i * + (SiteSetting.tl3_requires_topics_viewed.to_f / 100.0) + ).round, + SiteSetting.tl3_requires_topics_viewed_cap, ].min end def posts_read - @user.user_visits.where('visited_at > ?', time_period.days.ago).pluck(:posts_read).sum + @user.user_visits.where("visited_at > ?", time_period.days.ago).pluck(:posts_read).sum end def min_posts_read [ - (TrustLevel3Requirements.num_posts_in_time_period.to_i * (SiteSetting.tl3_requires_posts_read.to_f / 100.0)).round, - SiteSetting.tl3_requires_posts_read_cap + ( + TrustLevel3Requirements.num_posts_in_time_period.to_i * + (SiteSetting.tl3_requires_posts_read.to_f / 100.0) + ).round, + SiteSetting.tl3_requires_posts_read_cap, ].min end @@ -196,12 +206,14 @@ class TrustLevel3Requirements end def num_flagged_posts - PostAction.with_deleted + PostAction + .with_deleted .where(post_id: flagged_post_ids) .where.not(user_id: @user.id) .where.not(agreed_at: nil) .pluck(:post_id) - .uniq.count + .uniq + .count end def max_flagged_posts @@ -209,12 +221,15 @@ class TrustLevel3Requirements end def num_flagged_by_users - @_num_flagged_by_users ||= PostAction.with_deleted - .where(post_id: flagged_post_ids) - .where.not(user_id: @user.id) - .where.not(agreed_at: nil) - .pluck(:user_id) - .uniq.count + @_num_flagged_by_users ||= + PostAction + .with_deleted + .where(post_id: flagged_post_ids) + .where.not(user_id: @user.id) + .where.not(agreed_at: nil) + .pluck(:user_id) + .uniq + .count end def max_flagged_by_users @@ -222,7 +237,8 @@ class TrustLevel3Requirements end def num_likes_given - UserAction.where(user_id: @user.id, action_type: UserAction::LIKE) + UserAction + .where(user_id: @user.id, action_type: UserAction::LIKE) .where("user_actions.created_at > ?", time_period.days.ago) .joins(:target_topic) .where("topics.archetype <> ?", Archetype.private_message) @@ -234,7 +250,8 @@ class TrustLevel3Requirements end def num_likes_received_query - UserAction.where(user_id: @user.id, action_type: UserAction::WAS_LIKED) + UserAction + .where(user_id: @user.id, action_type: UserAction::WAS_LIKED) .where("user_actions.created_at > ?", time_period.days.ago) .joins(:target_topic) .where("topics.archetype <> ?", Archetype.private_message) @@ -250,7 +267,7 @@ class TrustLevel3Requirements def num_likes_received_days # don't do a COUNT(DISTINCT date(created_at)) here! - num_likes_received_query.pluck('date(user_actions.created_at)').uniq.size + num_likes_received_query.pluck("date(user_actions.created_at)").uniq.size end def min_likes_received_days @@ -275,28 +292,36 @@ class TrustLevel3Requirements CACHE_DURATION = 1.day.seconds - 60 NUM_TOPICS_KEY = "tl3_num_topics" - NUM_POSTS_KEY = "tl3_num_posts" + NUM_POSTS_KEY = "tl3_num_posts" def self.num_topics_in_time_period - Discourse.redis.get(NUM_TOPICS_KEY) || begin - count = Topic.listable_topics.visible.created_since(SiteSetting.tl3_time_period.days.ago).count - Discourse.redis.setex NUM_TOPICS_KEY, CACHE_DURATION, count - count - end + Discourse.redis.get(NUM_TOPICS_KEY) || + begin + count = + Topic.listable_topics.visible.created_since(SiteSetting.tl3_time_period.days.ago).count + Discourse.redis.setex NUM_TOPICS_KEY, CACHE_DURATION, count + count + end end def self.num_posts_in_time_period - Discourse.redis.get(NUM_POSTS_KEY) || begin - count = Post.public_posts.visible.created_since(SiteSetting.tl3_time_period.days.ago).count - Discourse.redis.setex NUM_POSTS_KEY, CACHE_DURATION, count - count - end + Discourse.redis.get(NUM_POSTS_KEY) || + begin + count = Post.public_posts.visible.created_since(SiteSetting.tl3_time_period.days.ago).count + Discourse.redis.setex NUM_POSTS_KEY, CACHE_DURATION, count + count + end end def flagged_post_ids - @_flagged_post_ids ||= @user.posts - .with_deleted - .where('created_at > ? AND (spam_count > 0 OR inappropriate_count > 0)', time_period.days.ago) - .pluck(:id) + @_flagged_post_ids ||= + @user + .posts + .with_deleted + .where( + "created_at > ? AND (spam_count > 0 OR inappropriate_count > 0)", + time_period.days.ago, + ) + .pluck(:id) end end diff --git a/app/models/trust_level_and_staff_and_disabled_setting.rb b/app/models/trust_level_and_staff_and_disabled_setting.rb index 3b1a4336c28..4edc2c9519f 100644 --- a/app/models/trust_level_and_staff_and_disabled_setting.rb +++ b/app/models/trust_level_and_staff_and_disabled_setting.rb @@ -6,12 +6,12 @@ class TrustLevelAndStaffAndDisabledSetting < TrustLevelAndStaffSetting end def self.valid_values - ['disabled'] + TrustLevel.valid_range.to_a + special_groups + ["disabled"] + TrustLevel.valid_range.to_a + special_groups end def self.translation(value) - if value == 'disabled' - I18n.t('site_settings.disabled') + if value == "disabled" + I18n.t("site_settings.disabled") else super end @@ -19,11 +19,11 @@ class TrustLevelAndStaffAndDisabledSetting < TrustLevelAndStaffSetting def self.matches?(value, user) case value - when 'disabled' + when "disabled" false - when 'staff' + when "staff" user.staff? - when 'admin' + when "admin" user.admin? else user.has_trust_level?(value.to_i) || user.staff? diff --git a/app/models/trust_level_and_staff_setting.rb b/app/models/trust_level_and_staff_setting.rb index a72f6346673..17cc8bead7d 100644 --- a/app/models/trust_level_and_staff_setting.rb +++ b/app/models/trust_level_and_staff_setting.rb @@ -2,9 +2,7 @@ class TrustLevelAndStaffSetting < TrustLevelSetting def self.valid_value?(val) - special_group?(val) || - (val.to_i.to_s == val.to_s && - valid_values.any? { |v| v == val.to_i }) + special_group?(val) || (val.to_i.to_s == val.to_s && valid_values.any? { |v| v == val.to_i }) end def self.valid_values @@ -16,7 +14,7 @@ class TrustLevelAndStaffSetting < TrustLevelSetting end def self.special_groups - ['staff', 'admin'] + %w[staff admin] end def self.translation(value) diff --git a/app/models/trust_level_setting.rb b/app/models/trust_level_setting.rb index fc48bf8a10d..e1d4f2c0ce1 100644 --- a/app/models/trust_level_setting.rb +++ b/app/models/trust_level_setting.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true class TrustLevelSetting < EnumSiteSetting - def self.valid_value?(val) - val.to_i.to_s == val.to_s && - valid_values.any? { |v| v == val.to_i } + val.to_i.to_s == val.to_s && valid_values.any? { |v| v == val.to_i } end def self.values - valid_values.map do |value| - { name: translation(value), value: value } - end + valid_values.map { |value| { name: translation(value), value: value } } end def self.valid_values @@ -18,11 +14,7 @@ class TrustLevelSetting < EnumSiteSetting end def self.translation(value) - I18n.t( - "js.trust_levels.detailed_name", - level: value, - name: TrustLevel.name(value) - ) + I18n.t("js.trust_levels.detailed_name", level: value, name: TrustLevel.name(value)) end private_class_method :valid_values diff --git a/app/models/unsubscribe_key.rb b/app/models/unsubscribe_key.rb index da81fdfb33c..fbc199e169d 100644 --- a/app/models/unsubscribe_key.rb +++ b/app/models/unsubscribe_key.rb @@ -7,9 +7,9 @@ class UnsubscribeKey < ActiveRecord::Base before_create :generate_random_key - ALL_TYPE = 'all' - DIGEST_TYPE = 'digest' - TOPIC_TYPE = 'topic' + ALL_TYPE = "all" + DIGEST_TYPE = "digest" + TOPIC_TYPE = "topic" class << self def create_key_for(user, type, post: nil) @@ -32,7 +32,7 @@ class UnsubscribeKey < ActiveRecord::Base strategies = { DIGEST_TYPE => EmailControllerHelper::DigestEmailUnsubscriber, TOPIC_TYPE => EmailControllerHelper::TopicEmailUnsubscriber, - ALL_TYPE => EmailControllerHelper::BaseEmailUnsubscriber + ALL_TYPE => EmailControllerHelper::BaseEmailUnsubscriber, } DiscoursePluginRegistry.email_unsubscribers.each do |unsubcriber| diff --git a/app/models/upload.rb b/app/models/upload.rb index ad004f6e668..964ef52d1dd 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -8,12 +8,12 @@ class Upload < ActiveRecord::Base SHA1_LENGTH = 40 SEEDED_ID_THRESHOLD = 0 - URL_REGEX ||= /(\/original\/\dX[\/\.\w]*\/(\h+)[\.\w]*)/ + URL_REGEX ||= %r{(/original/\dX[/\.\w]*/(\h+)[\.\w]*)} MAX_IDENTIFY_SECONDS = 5 DOMINANT_COLOR_COMMAND_TIMEOUT_SECONDS = 5 belongs_to :user - belongs_to :access_control_post, class_name: 'Post' + belongs_to :access_control_post, class_name: "Post" # when we access this post we don't care if the post # is deleted @@ -25,7 +25,7 @@ class Upload < ActiveRecord::Base has_many :optimized_images, dependent: :destroy has_many :user_uploads, dependent: :destroy has_many :upload_references, dependent: :destroy - has_many :posts, through: :upload_references, source: :target, source_type: 'Post' + has_many :posts, through: :upload_references, source: :target, source_type: "Post" has_many :topic_thumbnails attr_accessor :for_group_message @@ -44,7 +44,9 @@ class Upload < ActiveRecord::Base before_destroy do UserProfile.where(card_background_upload_id: self.id).update_all(card_background_upload_id: nil) - UserProfile.where(profile_background_upload_id: self.id).update_all(profile_background_upload_id: nil) + UserProfile.where(profile_background_upload_id: self.id).update_all( + profile_background_upload_id: nil, + ) end after_destroy do @@ -56,11 +58,7 @@ class Upload < ActiveRecord::Base scope :by_users, -> { where("uploads.id > ?", SEEDED_ID_THRESHOLD) } def self.verification_statuses - @verification_statuses ||= Enum.new( - unchecked: 1, - verified: 2, - invalid_etag: 3 - ) + @verification_statuses ||= Enum.new(unchecked: 1, verified: 2, invalid_etag: 3) end def self.add_unused_callback(&block) @@ -88,9 +86,9 @@ class Upload < ActiveRecord::Base end def self.with_no_non_post_relations - self - .joins("LEFT JOIN upload_references ur ON ur.upload_id = uploads.id AND ur.target_type != 'Post'") - .where("ur.upload_id IS NULL") + self.joins( + "LEFT JOIN upload_references ur ON ur.upload_id = uploads.id AND ur.target_type != 'Post'", + ).where("ur.upload_id IS NULL") end def initialize(*args) @@ -114,18 +112,14 @@ class Upload < ActiveRecord::Base return unless SiteSetting.create_thumbnails? opts ||= {} - if get_optimized_image(width, height, opts) - save(validate: false) - end + save(validate: false) if get_optimized_image(width, height, opts) end # this method attempts to correct old incorrect extensions def get_optimized_image(width, height, opts = nil) opts ||= {} - if (!extension || extension.length == 0) - fix_image_extension - end + fix_image_extension if (!extension || extension.length == 0) opts = opts.merge(raise_on_error: true) begin @@ -152,9 +146,7 @@ class Upload < ActiveRecord::Base File.read(original_path) ensure - if external_copy - File.unlink(external_copy.path) - end + File.unlink(external_copy.path) if external_copy end def fix_image_extension @@ -164,18 +156,28 @@ class Upload < ActiveRecord::Base # this is relatively cheap once cached original_path = Discourse.store.path_for(self) if original_path.blank? - external_copy = Discourse.store.download(self) rescue nil + external_copy = + begin + Discourse.store.download(self) + rescue StandardError + nil + end original_path = external_copy.try(:path) end - image_info = FastImage.new(original_path) rescue nil + image_info = + begin + FastImage.new(original_path) + rescue StandardError + nil + end new_extension = image_info&.type&.to_s || "unknown" if new_extension != self.extension self.update_columns(extension: new_extension) true end - rescue + rescue StandardError self.update_columns(extension: "unknown") true end @@ -211,7 +213,9 @@ class Upload < ActiveRecord::Base def self.consider_for_reuse(upload, post) return upload if !SiteSetting.secure_uploads? || upload.blank? || post.blank? - return nil if !upload.matching_access_control_post?(post) || upload.uploaded_before_secure_uploads_enabled? + if !upload.matching_access_control_post?(post) || upload.uploaded_before_secure_uploads_enabled? + return nil + end upload end @@ -220,7 +224,8 @@ class Upload < ActiveRecord::Base # have secure-uploads in the URL e.g. /t/secure-uploads-are-cool/223452 route = UrlHelper.rails_route_from_url(url) return false if route.blank? - route[:action] == "show_secure" && route[:controller] == "uploads" && FileHelper.is_supported_media?(url) + route[:action] == "show_secure" && route[:controller] == "uploads" && + FileHelper.is_supported_media?(url) rescue ActionController::RoutingError false end @@ -239,17 +244,14 @@ class Upload < ActiveRecord::Base controller: "uploads", action: "show_secure", path: uri.path[1..-1], - only_path: true + only_path: true, ) end def self.short_path(sha1:, extension:) @url_helpers ||= Rails.application.routes.url_helpers - @url_helpers.upload_short_path( - base62: self.base62_sha1(sha1), - extension: extension - ) + @url_helpers.upload_short_path(base62: self.base62_sha1(sha1), extension: extension) end def self.base62_sha1(sha1) @@ -261,7 +263,7 @@ class Upload < ActiveRecord::Base end def local? - !(url =~ /^(https?:)?\/\//) + !(url =~ %r{^(https?:)?//}) end def fix_dimensions! @@ -275,8 +277,19 @@ class Upload < ActiveRecord::Base end begin - if extension == 'svg' - w, h = Discourse::Utils.execute_command("identify", "-format", "%w %h", path, timeout: MAX_IDENTIFY_SECONDS).split(' ') rescue [0, 0] + if extension == "svg" + w, h = + begin + Discourse::Utils.execute_command( + "identify", + "-format", + "%w %h", + path, + timeout: MAX_IDENTIFY_SECONDS, + ).split(" ") + rescue StandardError + [0, 0] + end else w, h = FastImage.new(path, raise_on_failure: true).size end @@ -290,7 +303,7 @@ class Upload < ActiveRecord::Base width: width, height: height, thumbnail_width: thumbnail_width, - thumbnail_height: thumbnail_height + thumbnail_height: thumbnail_height, ) rescue => e Discourse.warn_exception(e, message: "Error getting image dimensions") @@ -337,9 +350,7 @@ class Upload < ActiveRecord::Base def calculate_dominant_color!(local_path = nil) color = nil - if !FileHelper.is_supported_image?("image.#{extension}") || extension == "svg" - color = "" - end + color = "" if !FileHelper.is_supported_image?("image.#{extension}") || extension == "svg" if color.nil? local_path ||= @@ -360,37 +371,39 @@ class Upload < ActiveRecord::Base color = "" end - color ||= begin - data = Discourse::Utils.execute_command( - "nice", - "-n", - "10", - "convert", - local_path, - "-resize", - "1x1", - "-define", - "histogram:unique-colors=true", - "-format", - "%c", - "histogram:info:", - timeout: DOMINANT_COLOR_COMMAND_TIMEOUT_SECONDS - ) + color ||= + begin + data = + Discourse::Utils.execute_command( + "nice", + "-n", + "10", + "convert", + local_path, + "-resize", + "1x1", + "-define", + "histogram:unique-colors=true", + "-format", + "%c", + "histogram:info:", + timeout: DOMINANT_COLOR_COMMAND_TIMEOUT_SECONDS, + ) - # Output format: - # 1: (110.873,116.226,93.8821) #6F745E srgb(43.4798%,45.5789%,36.8165%) + # Output format: + # 1: (110.873,116.226,93.8821) #6F745E srgb(43.4798%,45.5789%,36.8165%) - color = data[/#([0-9A-F]{6})/, 1] + color = data[/#([0-9A-F]{6})/, 1] - raise "Calculated dominant color but unable to parse output:\n#{data}" if color.nil? + raise "Calculated dominant color but unable to parse output:\n#{data}" if color.nil? - color - rescue Discourse::Utils::CommandError => e - # Timeout or unable to parse image - # This can happen due to bad user input - ignore and save - # an empty string to prevent re-evaluation - "" - end + color + rescue Discourse::Utils::CommandError => e + # Timeout or unable to parse image + # This can happen due to bad user input - ignore and save + # an empty string to prevent re-evaluation + "" + end end if persisted? @@ -401,23 +414,28 @@ class Upload < ActiveRecord::Base end def target_image_quality(local_path, test_quality) - @file_quality ||= Discourse::Utils.execute_command("identify", "-format", "%Q", local_path, timeout: MAX_IDENTIFY_SECONDS).to_i rescue 0 + @file_quality ||= + begin + Discourse::Utils.execute_command( + "identify", + "-format", + "%Q", + local_path, + timeout: MAX_IDENTIFY_SECONDS, + ).to_i + rescue StandardError + 0 + end - if @file_quality == 0 || @file_quality > test_quality - test_quality - end + test_quality if @file_quality == 0 || @file_quality > test_quality end def self.sha1_from_short_path(path) - if path =~ /(\/uploads\/short-url\/)([a-zA-Z0-9]+)(\..*)?/ - self.sha1_from_base62_encoded($2) - end + self.sha1_from_base62_encoded($2) if path =~ %r{(/uploads/short-url/)([a-zA-Z0-9]+)(\..*)?} end def self.sha1_from_short_url(url) - if url =~ /(upload:\/\/)?([a-zA-Z0-9]+)(\..*)?/ - self.sha1_from_base62_encoded($2) - end + self.sha1_from_base62_encoded($2) if url =~ %r{(upload://)?([a-zA-Z0-9]+)(\..*)?} end def self.sha1_from_base62_encoded(encoded_sha1) @@ -426,7 +444,7 @@ class Upload < ActiveRecord::Base if sha1.length > SHA1_LENGTH nil else - sha1.rjust(SHA1_LENGTH, '0') + sha1.rjust(SHA1_LENGTH, "0") end end @@ -457,7 +475,10 @@ class Upload < ActiveRecord::Base begin Discourse.store.update_upload_ACL(self) rescue Aws::S3::Errors::NotImplemented => err - Discourse.warn_exception(err, message: "The file store object storage provider does not support setting ACLs") + Discourse.warn_exception( + err, + message: "The file store object storage provider does not support setting ACLs", + ) end end @@ -468,7 +489,7 @@ class Upload < ActiveRecord::Base { secure: secure, security_last_changed_reason: reason + " | source: #{source}", - security_last_changed_at: Time.zone.now + security_last_changed_at: Time.zone.now, } end @@ -479,15 +500,17 @@ class Upload < ActiveRecord::Base if SiteSetting.migrate_to_new_scheme max_file_size_kb = [ SiteSetting.max_image_size_kb, - SiteSetting.max_attachment_size_kb + SiteSetting.max_attachment_size_kb, ].max.kilobytes local_store = FileStore::LocalStore.new db = RailsMultisite::ConnectionManagement.current_db - scope = Upload.by_users - .where("url NOT LIKE '%/original/_X/%' AND url LIKE ?", "%/uploads/#{db}%") - .order(id: :desc) + scope = + Upload + .by_users + .where("url NOT LIKE '%/original/_X/%' AND url LIKE ?", "%/uploads/#{db}%") + .order(id: :desc) scope = scope.limit(limit) if limit @@ -503,7 +526,7 @@ class Upload < ActiveRecord::Base # keep track of the url previous_url = upload.url.dup # where is the file currently stored? - external = previous_url =~ /^\/\// + external = previous_url =~ %r{^//} # download if external if external url = SiteSetting.scheme + ":" + previous_url @@ -511,12 +534,13 @@ class Upload < ActiveRecord::Base begin retries ||= 0 - file = FileHelper.download( - url, - max_file_size: max_file_size_kb, - tmp_file_name: "discourse", - follow_redirect: true - ) + file = + FileHelper.download( + url, + max_file_size: max_file_size_kb, + tmp_file_name: "discourse", + follow_redirect: true, + ) rescue OpenURI::HTTPError retry if (retries += 1) < 1 next @@ -527,9 +551,7 @@ class Upload < ActiveRecord::Base path = local_store.path_for(upload) end # compute SHA if missing - if upload.sha1.blank? - upload.sha1 = Upload.generate_digest(path) - end + upload.sha1 = Upload.generate_digest(path) if upload.sha1.blank? # store to new location & update the filesize File.open(path) do |f| @@ -543,7 +565,7 @@ class Upload < ActiveRecord::Base DbHelper.remap( previous_url, upload.url, - excluded_tables: %w{ + excluded_tables: %w[ posts post_search_data incoming_emails @@ -555,28 +577,32 @@ class Upload < ActiveRecord::Base user_emails draft_sequences optimized_images - } + ], ) - remap_scope ||= begin - Post.with_deleted - .where("raw ~ '/uploads/#{db}/\\d+/' OR raw ~ '/uploads/#{db}/original/(\\d|[a-z])/'") - .select(:id, :raw, :cooked) - .all - end + remap_scope ||= + begin + Post + .with_deleted + .where( + "raw ~ '/uploads/#{db}/\\d+/' OR raw ~ '/uploads/#{db}/original/(\\d|[a-z])/'", + ) + .select(:id, :raw, :cooked) + .all + end remap_scope.each do |post| post.raw.gsub!(previous_url, upload.url) post.cooked.gsub!(previous_url, upload.url) - Post.with_deleted.where(id: post.id).update_all(raw: post.raw, cooked: post.cooked) if post.changed? + if post.changed? + Post.with_deleted.where(id: post.id).update_all(raw: post.raw, cooked: post.cooked) + end end upload.optimized_images.find_each(&:destroy!) upload.rebake_posts_on_old_scheme # remove the old file (when local) - unless external - FileUtils.rm(path, force: true) - end + FileUtils.rm(path, force: true) unless external rescue => e problems << { upload: upload, ex: e } ensure @@ -595,21 +621,21 @@ class Upload < ActiveRecord::Base sha1s = [] - raw.scan(/\/(\h{40})/).each do |match| - sha1s << match[0] - end + raw.scan(/\/(\h{40})/).each { |match| sha1s << match[0] } - raw.scan(/\/([a-zA-Z0-9]+)/).each do |match| - sha1s << Upload.sha1_from_base62_encoded(match[0]) - end + raw + .scan(%r{/([a-zA-Z0-9]+)}) + .each { |match| sha1s << Upload.sha1_from_base62_encoded(match[0]) } Upload.where(sha1: sha1s.uniq).pluck(:id) end def self.backfill_dominant_colors!(count) - Upload.where(dominant_color: nil).order("id desc").first(count).each do |upload| - upload.calculate_dominant_color! - end + Upload + .where(dominant_color: nil) + .order("id desc") + .first(count) + .each { |upload| upload.calculate_dominant_color! } end private @@ -617,7 +643,6 @@ class Upload < ActiveRecord::Base def short_url_basename "#{Upload.base62_sha1(sha1)}#{extension.present? ? ".#{extension}" : ""}" end - end # == Schema Information diff --git a/app/models/upload_reference.rb b/app/models/upload_reference.rb index 58cf8f86d19..73de830f545 100644 --- a/app/models/upload_reference.rb +++ b/app/models/upload_reference.rb @@ -5,7 +5,9 @@ class UploadReference < ActiveRecord::Base belongs_to :target, polymorphic: true def self.ensure_exist!(upload_ids: [], target: nil, target_type: nil, target_id: nil) - raise "target OR target_type and target_id are required" if !target && !(target_type && target_id) + if !target && !(target_type && target_id) + raise "target OR target_type and target_id are required" + end if target.present? target_type = target.class @@ -16,22 +18,21 @@ class UploadReference < ActiveRecord::Base target_type = target_type.to_s if upload_ids.empty? - UploadReference - .where(target_type: target_type, target_id: target_id) - .delete_all + UploadReference.where(target_type: target_type, target_id: target_id).delete_all return end - rows = upload_ids.map do |upload_id| - { - upload_id: upload_id, - target_type: target_type, - target_id: target_id, - created_at: Time.zone.now, - updated_at: Time.zone.now, - } - end + rows = + upload_ids.map do |upload_id| + { + upload_id: upload_id, + target_type: target_type, + target_id: target_id, + created_at: Time.zone.now, + updated_at: Time.zone.now, + } + end UploadReference.transaction do |transaction| UploadReference diff --git a/app/models/user.rb b/app/models/user.rb index 54d358ec737..d0d9846a433 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -34,22 +34,40 @@ class User < ActiveRecord::Base 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 :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 has_many :invites, foreign_key: :invited_by_id, dependent: :destroy has_many :user_custom_fields, dependent: :destroy has_many :user_associated_groups, dependent: :destroy - has_many :pending_posts, -> { merge(Reviewable.pending) }, class_name: 'ReviewableQueuedPost', foreign_key: :created_by_id + has_many :pending_posts, + -> { merge(Reviewable.pending) }, + class_name: "ReviewableQueuedPost", + foreign_key: :created_by_id has_one :user_option, dependent: :destroy has_one :user_avatar, dependent: :destroy - has_one :primary_email, -> { where(primary: true) }, class_name: 'UserEmail', dependent: :destroy, autosave: true, validate: false + has_one :primary_email, + -> { where(primary: true) }, + class_name: "UserEmail", + dependent: :destroy, + autosave: true, + validate: false 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 + 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 has_one :invited_user, dependent: :destroy has_one :user_notification_schedule, dependent: :destroy @@ -61,8 +79,8 @@ class User < ActiveRecord::Base 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 + has_many :muted_user_records, class_name: "MutedUser", dependent: :delete_all + has_many :ignored_user_records, class_name: "IgnoredUser", dependent: :delete_all has_many :do_not_disturb_timings, dependent: :delete_all has_one :user_status, dependent: :destroy @@ -72,16 +90,22 @@ class User < ActiveRecord::Base has_many :post_timings has_many :directory_items has_many :email_logs - has_many :security_keys, -> { - where(enabled: true) - }, class_name: "UserSecurityKey" + has_many :security_keys, -> { where(enabled: true) }, class_name: "UserSecurityKey" has_many :badges, through: :user_badges - has_many :default_featured_user_badges, -> { - max_featured_rank = SiteSetting.max_favorite_badges > 0 ? SiteSetting.max_favorite_badges + 1 - : DEFAULT_FEATURED_BADGE_COUNT - for_enabled_badges.grouped_with_count.where("featured_rank <= ?", max_featured_rank) - }, class_name: "UserBadge" + has_many :default_featured_user_badges, + -> { + max_featured_rank = + ( + if SiteSetting.max_favorite_badges > 0 + SiteSetting.max_favorite_badges + 1 + else + DEFAULT_FEATURED_BADGE_COUNT + end + ) + for_enabled_badges.grouped_with_count.where("featured_rank <= ?", max_featured_rank) + }, + class_name: "UserBadge" has_many :topics_allowed, through: :topic_allowed_users, source: :topic has_many :groups, through: :group_users @@ -89,27 +113,32 @@ class User < ActiveRecord::Base has_many :associated_groups, through: :user_associated_groups, dependent: :destroy # deleted in user_second_factors relationship - has_many :totps, -> { - where(method: UserSecondFactor.methods[:totp], enabled: true) - }, class_name: "UserSecondFactor" + has_many :totps, + -> { where(method: UserSecondFactor.methods[:totp], enabled: true) }, + class_name: "UserSecondFactor" has_one :master_user, through: :anonymous_user_master has_one :shadow_user, through: :anonymous_user_shadow, source: :user has_one :profile_background_upload, through: :user_profile has_one :card_background_upload, through: :user_profile - belongs_to :approved_by, class_name: 'User' - belongs_to :primary_group, class_name: 'Group' - belongs_to :flair_group, class_name: 'Group' + belongs_to :approved_by, class_name: "User" + belongs_to :primary_group, class_name: "Group" + belongs_to :flair_group, class_name: "Group" has_many :muted_users, through: :muted_user_records has_many :ignored_users, through: :ignored_user_records - belongs_to :uploaded_avatar, class_name: 'Upload' + belongs_to :uploaded_avatar, class_name: "Upload" has_many :sidebar_section_links, dependent: :delete_all - has_many :category_sidebar_section_links, -> { where(linkable_type: "Category") }, class_name: 'SidebarSectionLink' - has_many :custom_sidebar_tags, through: :sidebar_section_links, source: :linkable, source_type: "Tag" + has_many :category_sidebar_section_links, + -> { where(linkable_type: "Category") }, + class_name: "SidebarSectionLink" + has_many :custom_sidebar_tags, + through: :sidebar_section_links, + source: :linkable, + source_type: "Tag" delegate :last_sent_email_address, to: :email_logs @@ -121,7 +150,8 @@ class User < ActiveRecord::Base validates :ip_address, allowed_ip_address: { on: :create, message: :signup_not_allowed } validates :primary_email, presence: true validates :validatable_user_fields_values, watched_words: true, unless: :custom_fields_clean? - validates_associated :primary_email, message: -> (_, user_email) { user_email[:value]&.errors[:email]&.first } + validates_associated :primary_email, + message: ->(_, user_email) { user_email[:value]&.errors[:email]&.first } after_initialize :add_trust_level @@ -137,17 +167,12 @@ class User < ActiveRecord::Base after_create :set_default_tags_preferences after_create :add_default_sidebar_section_links - after_update :update_default_sidebar_section_links, if: Proc.new { - self.saved_change_to_admin? - } + after_update :update_default_sidebar_section_links, if: Proc.new { self.saved_change_to_admin? } - after_update :add_default_sidebar_section_links, if: Proc.new { - self.saved_change_to_staged? - } + after_update :add_default_sidebar_section_links, if: Proc.new { self.saved_change_to_staged? } - after_update :trigger_user_updated_event, if: Proc.new { - self.human? && self.saved_change_to_uploaded_avatar_id? - } + after_update :trigger_user_updated_event, + if: Proc.new { self.human? && self.saved_change_to_uploaded_avatar_id? } after_update :trigger_user_automatic_group_refresh, if: :saved_change_to_staged? @@ -178,7 +203,10 @@ class User < ActiveRecord::Base # These tables don't have primary keys, so destroying them with activerecord is tricky: PostTiming.where(user_id: self.id).delete_all TopicViewItem.where(user_id: self.id).delete_all - UserAction.where('user_id = :user_id OR target_user_id = :user_id OR acting_user_id = :user_id', user_id: self.id).delete_all + 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 @@ -186,8 +214,9 @@ class User < ActiveRecord::Base # 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) + DirectoryItem + .where(user_id: self.id) + .where("period_type in (?)", DirectoryItem.period_types.values) .delete_all # our relationship filters on enabled, this makes sure everything is deleted @@ -216,70 +245,95 @@ class User < ActiveRecord::Base # Cache for user custom fields. Currently it is used to display quick search results attr_accessor :custom_data - scope :with_email, ->(email) do - joins(:user_emails).where("lower(user_emails.email) IN (?)", email) - end + scope :with_email, + ->(email) { joins(:user_emails).where("lower(user_emails.email) IN (?)", email) } - scope :with_primary_email, ->(email) do - joins(:user_emails).where("lower(user_emails.email) IN (?) AND user_emails.primary", email) - end + scope :with_primary_email, + ->(email) { + joins(:user_emails).where( + "lower(user_emails.email) IN (?) AND user_emails.primary", + email, + ) + } - scope :human_users, -> { where('users.id > 0') } + scope :human_users, -> { where("users.id > 0") } # excluding fake users like the system user or anonymous users - scope :real, -> { human_users.where('NOT EXISTS( + scope :real, + -> { + human_users.where( + "NOT EXISTS( SELECT 1 FROM anonymous_users a WHERE a.user_id = users.id - )') } + )", + ) + } # TODO-PERF: There is no indexes on any of these # and NotifyMailingListSubscribers does a select-all-and-loop # may want to create an index on (active, silence, suspended_till)? 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) } - 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 :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) } scope :not_staged, -> { where(staged: false) } - scope :filter_by_username, ->(filter) do - if filter.is_a?(Array) - where('username_lower ~* ?', "(#{filter.join('|')})") - else - where('username_lower ILIKE ?', "%#{filter}%") - end - end + scope :filter_by_username, + ->(filter) { + if filter.is_a?(Array) + where("username_lower ~* ?", "(#{filter.join("|")})") + else + where("username_lower ILIKE ?", "%#{filter}%") + end + } - scope :filter_by_username_or_email, ->(filter) do - if filter.is_a?(String) && filter =~ /.+@.+/ - # probably an email so try the bypass - if user_id = UserEmail.where("lower(email) = ?", filter.downcase).pluck_first(:user_id) - return where('users.id = ?', user_id) - end - end + scope :filter_by_username_or_email, + ->(filter) { + if filter.is_a?(String) && filter =~ /.+@.+/ + # probably an email so try the bypass + if user_id = UserEmail.where("lower(email) = ?", filter.downcase).pluck_first(:user_id) + return where("users.id = ?", user_id) + end + end - users = joins(:primary_email) + users = joins(:primary_email) - if filter.is_a?(Array) - users.where( - 'username_lower ~* :filter OR lower(user_emails.email) SIMILAR TO :filter', - filter: "(#{filter.join('|')})" - ) - else - users.where( - 'username_lower ILIKE :filter OR lower(user_emails.email) ILIKE :filter', - filter: "%#{filter}%" - ) - end - end + if filter.is_a?(Array) + users.where( + "username_lower ~* :filter OR lower(user_emails.email) SIMILAR TO :filter", + filter: "(#{filter.join("|")})", + ) + else + users.where( + "username_lower ILIKE :filter OR lower(user_emails.email) ILIKE :filter", + filter: "%#{filter}%", + ) + end + } - scope :watching_topic, ->(topic) do - 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)) - .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)) - .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") - end + scope :watching_topic, + ->(topic) { + 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, + ), + ) + .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, + ), + ) + .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", + ) + } module NewTopicDuration ALWAYS = -1 @@ -289,13 +343,14 @@ class User < ActiveRecord::Base MAX_STAFF_DELETE_POST_COUNT ||= 5 def self.user_tips - @user_tips ||= Enum.new( - first_notification: 1, - topic_timeline: 2, - post_menu: 3, - topic_notification_levels: 4, - suggested_topics: 5, - ) + @user_tips ||= + Enum.new( + first_notification: 1, + topic_timeline: 2, + post_menu: 3, + topic_notification_levels: 4, + suggested_topics: 5, + ) end def visible_sidebar_tags(user_guardian = nil) @@ -318,10 +373,18 @@ class User < ActiveRecord::Base 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) + return true if !username_exists?(lower) # 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 }) + email.present? && + User.joins(:user_emails).exists?( + staged: true, + username_lower: lower, + user_emails: { + primary: true, + email: email, + }, + ) end def self.reserved_username?(username) @@ -329,9 +392,11 @@ class User < ActiveRecord::Base return true if SiteSetting.here_mention == username - SiteSetting.reserved_usernames.unicode_normalize.split("|").any? do |reserved| - username.match?(/^#{Regexp.escape(reserved).gsub('\*', '.*')}$/) - end + SiteSetting + .reserved_usernames + .unicode_normalize + .split("|") + .any? { |reserved| username.match?(/^#{Regexp.escape(reserved).gsub('\*', ".*")}$/) } end def self.editable_user_custom_fields(by_staff: false) @@ -348,12 +413,12 @@ class User < ActiveRecord::Base fields.push(*DiscoursePluginRegistry.public_user_custom_fields) if SiteSetting.public_user_custom_fields.present? - fields.push(*SiteSetting.public_user_custom_fields.split('|')) + 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('|')) + fields.push(*SiteSetting.staff_user_custom_fields.split("|")) end fields.push(*DiscoursePluginRegistry.staff_user_custom_fields) @@ -386,7 +451,7 @@ class User < ActiveRecord::Base bookmarks.where(bookmarkable_type: type) end - EMAIL = %r{([^@]+)@([^\.]+)} + EMAIL = /([^@]+)@([^\.]+)/ FROM_STAGED = "from_staged" def self.new_from_params(params) @@ -417,7 +482,7 @@ class User < ActiveRecord::Base end def self.find_by_username_or_email(username_or_email) - if username_or_email.include?('@') + if username_or_email.include?("@") find_by_email(username_or_email) else find_by_username(username_or_email) @@ -445,10 +510,7 @@ class User < ActiveRecord::Base end def group_granted_trust_level - GroupUser - .where(user_id: id) - .includes(:group) - .maximum("groups.grant_trust_level") + GroupUser.where(user_id: id).includes(:group).maximum("groups.grant_trust_level") end def visible_groups @@ -477,10 +539,10 @@ class User < ActiveRecord::Base Jobs.enqueue( :send_system_message, user_id: id, - message_type: 'welcome_staff', + message_type: "welcome_staff", message_options: { - role: role.to_s - } + role: role.to_s, + }, ) end @@ -501,7 +563,8 @@ class User < ActiveRecord::Base end def invited_by - used_invite = Invite.with_deleted.joins(:invited_users).where("invited_users.user_id = ?", self.id).first + used_invite = + Invite.with_deleted.joins(:invited_users).where("invited_users.user_id = ?", self.id).first used_invite.try(:invited_by) end @@ -605,7 +668,7 @@ class User < ActiveRecord::Base args = { user_id: self.id, seen_notification_id: self.seen_notification_id, - private_message: Notification.types[:private_message] + private_message: Notification.types[:private_message], } DB.query_single(<<~SQL, args).first @@ -632,9 +695,10 @@ class User < ActiveRecord::Base end def unread_notifications - @unread_notifications ||= begin - # perf critical, much more efficient than AR - sql = <<~SQL + @unread_notifications ||= + begin + # perf critical, much more efficient than AR + sql = <<~SQL SELECT COUNT(*) FROM ( SELECT 1 FROM notifications n @@ -648,17 +712,21 @@ class User < ActiveRecord::Base ) 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 + DB.query_single( + sql, + user_id: id, + seen_notification_id: seen_notification_id, + limit: User.max_unread_notifications, + )[ + 0 + ].to_i + end end def all_unread_notifications_count - @all_unread_notifications_count ||= begin - sql = <<~SQL + @all_unread_notifications_count ||= + begin + sql = <<~SQL SELECT COUNT(*) FROM ( SELECT 1 FROM notifications n @@ -671,12 +739,15 @@ class User < ActiveRecord::Base ) 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 + DB.query_single( + sql, + user_id: id, + seen_notification_id: seen_notification_id, + limit: User.max_unread_notifications, + )[ + 0 + ].to_i + end end def total_unread_notifications @@ -684,10 +755,7 @@ class User < ActiveRecord::Base end def reviewable_count - Reviewable.list_for( - self, - include_claimed_by_others: !redesigned_user_menu_enabled? - ).count + Reviewable.list_for(self, include_claimed_by_others: !redesigned_user_menu_enabled?).count end def saw_notification_id(notification_id) @@ -704,9 +772,7 @@ class User < ActiveRecord::Base def bump_last_seen_notification! query = self.notifications.visible - if seen_notification_id - query = query.where("notifications.id > ?", seen_notification_id) - end + 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 @@ -718,9 +784,7 @@ class User < ActiveRecord::Base def bump_last_seen_reviewable! query = Reviewable.unseen_list_for(self, preload: false) - if last_seen_reviewable_id - query = query.where("reviewables.id > ?", last_seen_reviewable_id) - end + query = query.where("reviewables.id > ?", last_seen_reviewable_id) if last_seen_reviewable_id max_reviewable_id = query.maximum(:id) if max_reviewable_id @@ -732,7 +796,7 @@ class User < ActiveRecord::Base def publish_reviewable_counts(extra_data = nil) data = { reviewable_count: self.reviewable_count, - unseen_reviewable_count: Reviewable.unseen_reviewable_count(self) + unseen_reviewable_count: Reviewable.unseen_reviewable_count(self), } data.merge!(extra_data) if extra_data.present? MessageBus.publish("/reviewable_counts/#{self.id}", data, user_ids: [self.id]) @@ -741,10 +805,13 @@ class User < ActiveRecord::Base TRACK_FIRST_NOTIFICATION_READ_DURATION = 1.week.to_i def read_first_notification? - if (trust_level > TrustLevel[1] || - (first_seen_at.present? && first_seen_at < TRACK_FIRST_NOTIFICATION_READ_DURATION.seconds.ago) || - user_option.skip_new_user_tips) - + if ( + trust_level > TrustLevel[1] || + ( + first_seen_at.present? && + first_seen_at < TRACK_FIRST_NOTIFICATION_READ_DURATION.seconds.ago + ) || user_option.skip_new_user_tips + ) return true end @@ -755,7 +822,7 @@ class User < ActiveRecord::Base return if !self.allow_live_notifications? # publish last notification json with the message so we can apply an update - notification = notifications.visible.order('notifications.created_at desc').first + notification = notifications.visible.order("notifications.created_at desc").first json = NotificationSerializer.new(notification).as_json if notification sql = (<<~SQL) @@ -783,9 +850,7 @@ class User < ActiveRecord::Base ) AS y SQL - recent = DB.query(sql, user_id: id).map! do |r| - [r.id, r.read] - end + recent = DB.query(sql, user_id: id).map! { |r| [r.id, r.read] } payload = { unread_notifications: unread_notifications, @@ -800,7 +865,9 @@ class User < ActiveRecord::Base if self.redesigned_user_menu_enabled? 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 + payload[ + :new_personal_messages_notifications_count + ] = new_personal_messages_notifications_count end MessageBus.publish("/notification/#{id}", payload, user_ids: [id]) @@ -815,24 +882,26 @@ class User < ActiveRecord::Base payload = { description: status.description, emoji: status.emoji, - ends_at: status.ends_at&.iso8601 + ends_at: status.ends_at&.iso8601, } else payload = nil end - MessageBus.publish("/user-status", { id => payload }, group_ids: [Group::AUTO_GROUPS[:trust_level_0]]) + MessageBus.publish( + "/user-status", + { id => payload }, + group_ids: [Group::AUTO_GROUPS[:trust_level_0]], + ) end def password=(password) # special case for passwordless accounts - unless password.blank? - @raw_password = password - end + @raw_password = password unless password.blank? end def password - '' # so that validator doesn't complain that a password attribute doesn't exist + "" # so that validator doesn't complain that a password attribute doesn't exist end # Indicate that this is NOT a passwordless account for the purposes of validation @@ -862,14 +931,15 @@ class User < ActiveRecord::Base end 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) + !staff? && trust_level < TrustLevel[2] && + ( + trust_level == TrustLevel[0] || self.first_post_created_at.nil? || + self.first_post_created_at >= 24.hours.ago + ) end def new_user? - (created_at >= 24.hours.ago || trust_level == TrustLevel[0]) && - trust_level < TrustLevel[2] && + (created_at >= 24.hours.ago || trust_level == TrustLevel[0]) && trust_level < TrustLevel[2] && !staff? end @@ -883,7 +953,11 @@ class User < ActiveRecord::Base 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) + user_visits.create!( + visited_at: date, + posts_read: opts[:posts_read] || 0, + mobile: opts[:mobile] || false, + ) end def visit_record_for(date) @@ -898,9 +972,7 @@ class User < ActiveRecord::Base 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) + UserOption.where(user_id: self.id, timezone: nil).update_all(timezone: timezone) end def update_posts_read!(num_posts, opts = {}) @@ -927,7 +999,6 @@ class User < ActiveRecord::Base 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 @@ -979,9 +1050,10 @@ class User < ActiveRecord::Base return true if SiteSetting.active_user_rate_limit_secs <= 0 Discourse.redis.set( - last_seen_redis_key(user_id, now), "1", + last_seen_redis_key(user_id, now), + "1", nx: true, - ex: SiteSetting.active_user_rate_limit_secs + ex: SiteSetting.active_user_rate_limit_secs, ) end @@ -1015,9 +1087,12 @@ class User < ActiveRecord::Base end 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 + username + .each_char + .reduce(0) do |result, char| + [((result << 5) - result) + char.ord].pack("L").unpack("l").first + end + .abs end def self.default_template(username) @@ -1042,10 +1117,11 @@ class User < ActiveRecord::Base # 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 - url = +"#{Discourse.base_path}#{url}" unless url =~ /^https?:\/\// + url = +"#{Discourse.base_path}#{url}" unless url =~ %r{^https?://} 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! "{first_letter}", + UrlHelper.encode_component(normalized_username.grapheme_clusters.first) url.gsub! "{hostname}", Discourse.current_hostname url else @@ -1064,7 +1140,7 @@ class User < ActiveRecord::Base 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 + color.map { |c| c.to_s(16).rjust(2, "0") }.join end end @@ -1077,8 +1153,8 @@ class User < ActiveRecord::Base end def avatar_template - use_small_logo = is_system_user? && - SiteSetting.logo_small && SiteSetting.use_site_small_logo_as_system_avatar + 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) @@ -1110,7 +1186,10 @@ class User < ActiveRecord::Base end def flags_given_count - PostAction.where(user_id: id, post_action_type_id: PostActionType.flag_types_without_custom.values).count + PostAction.where( + user_id: id, + post_action_type_id: PostActionType.flag_types_without_custom.values, + ).count end def warnings_received_count @@ -1118,7 +1197,10 @@ class User < ActiveRecord::Base end def flags_received_count - posts.includes(:post_actions).where('post_actions.post_action_type_id' => PostActionType.flag_types_without_custom.values).count + posts + .includes(:post_actions) + .where("post_actions.post_action_type_id" => PostActionType.flag_types_without_custom.values) + .count end def private_topics_count @@ -1134,7 +1216,7 @@ class User < ActiveRecord::Base 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 = since_reply.where("id > ?", last_action_in_topic) if last_action_in_topic (since_reply.count >= SiteSetting.newuser_max_replies_per_topic) end @@ -1144,9 +1226,10 @@ class User < ActiveRecord::Base Reviewable.where(created_by_id: id).delete_all - posts.order("post_number desc").limit(batch_size).each do |p| - PostDestroyer.new(guardian.user, p).destroy - end + posts + .order("post_number desc") + .limit(batch_size) + .each { |p| PostDestroyer.new(guardian.user, p).destroy } end def suspended? @@ -1158,7 +1241,7 @@ class User < ActiveRecord::Base end def silenced_record - UserHistory.for(self, :silence_user).order('id DESC').first + UserHistory.for(self, :silence_user).order("id DESC").first end def silence_reason @@ -1174,7 +1257,7 @@ class User < ActiveRecord::Base end def suspend_record - UserHistory.for(self, :suspend_user).order('id DESC').first + UserHistory.for(self, :suspend_user).order("id DESC").first end def full_suspend_reason @@ -1201,9 +1284,11 @@ class User < ActiveRecord::Base end end - I18n.t(message, - date: I18n.l(suspended_till, format: :date_only), - reason: Rack::Utils.escape_html(suspend_reason)) + I18n.t( + message, + date: I18n.l(suspended_till, format: :date_only), + reason: Rack::Utils.escape_html(suspend_reason), + ) end def suspended_forever? @@ -1213,16 +1298,14 @@ class User < ActiveRecord::Base # Use this helper to determine if the user has a particular trust level. # Takes into account admin, etc. def has_trust_level?(level) - unless TrustLevel.valid?(level) - raise InvalidTrustLevel.new("Invalid trust level #{level}") - end + raise InvalidTrustLevel.new("Invalid trust level #{level}") unless TrustLevel.valid?(level) admin? || moderator? || staged? || TrustLevel.compare(trust_level, level) end def has_trust_level_or_staff?(level) - return admin? if level.to_s == 'admin' - return staff? if level.to_s == 'staff' + return admin? if level.to_s == "admin" + return staff? if level.to_s == "staff" has_trust_level?(level.to_i) end @@ -1236,13 +1319,12 @@ class User < ActiveRecord::Base end def username_format_validator - UsernameValidator.perform_validation(self, 'username') + UsernameValidator.perform_validation(self, "username") end def email_confirmed? - email_tokens.where(email: email, confirmed: true).present? || - email_tokens.empty? || - single_sign_on_record&.external_email&.downcase == email + email_tokens.where(email: email, confirmed: true).present? || email_tokens.empty? || + single_sign_on_record&.external_email&.downcase == email end def activate @@ -1297,11 +1379,16 @@ class User < ActiveRecord::Base end 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') + 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.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 @@ -1316,7 +1403,7 @@ class User < ActiveRecord::Base secure_categories.references(:categories) end - cats.pluck('categories.id').sort + cats.pluck("categories.id").sort end def topic_create_allowed_category_ids @@ -1327,21 +1414,24 @@ class User < ActiveRecord::Base 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) + 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) + 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 + 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 @@ -1351,7 +1441,12 @@ class User < ActiveRecord::Base end def find_email - last_sent_email_address.present? && EmailAddressValidator.valid_value?(last_sent_email_address) ? last_sent_email_address : email + if last_sent_email_address.present? && + EmailAddressValidator.valid_value?(last_sent_email_address) + last_sent_email_address + else + email + end end def tl3_requirements @@ -1361,8 +1456,9 @@ class User < ActiveRecord::Base 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) + 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? @@ -1373,10 +1469,8 @@ class User < ActiveRecord::Base avatar = user_avatar || create_user_avatar - if self.primary_email.present? && - SiteSetting.automatically_download_gravatars? && - !avatar.last_gravatar_download_attempt - + 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 @@ -1395,10 +1489,7 @@ class User < ActiveRecord::Base Discourse.authenticators.each do |authenticator| account_description = authenticator.description_for_user(self) unless account_description.empty? - result << { - name: authenticator.name, - description: account_description, - } + result << { name: authenticator.name, description: account_description } end end @@ -1408,14 +1499,12 @@ class User < ActiveRecord::Base USER_FIELD_PREFIX ||= "user_field_" def user_fields(field_ids = nil) - if field_ids.nil? - field_ids = (@all_user_field_ids ||= UserField.pluck(:id)) - end + field_ids = (@all_user_field_ids ||= UserField.pluck(:id)) if field_ids.nil? @user_fields_cache ||= {} # Memoize based on requested fields - @user_fields_cache[field_ids.join(':')] ||= {}.tap do |hash| + @user_fields_cache[field_ids.join(":")] ||= {}.tap do |hash| field_ids.each do |fid| # The hash keys are strings for backwards compatibility hash[fid.to_s] = custom_fields["#{USER_FIELD_PREFIX}#{fid}"] @@ -1439,16 +1528,14 @@ class User < ActiveRecord::Base def validatable_user_fields # 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) + @public_user_field_ids ||= + UserField.public_fields.where.not(field_type: "multiselect").pluck(:id) user_fields(@public_user_field_ids) end def number_of_deleted_posts - Post.with_deleted - .where(user_id: self.id) - .where.not(deleted_at: nil) - .count + Post.with_deleted.where(user_id: self.id).where.not(deleted_at: nil).count end def number_of_flagged_posts @@ -1456,14 +1543,12 @@ class User < ActiveRecord::Base end def number_of_rejected_posts - ReviewableQueuedPost - .rejected - .where(created_by_id: self.id) - .count + ReviewableQueuedPost.rejected.where(created_by_id: self.id).count end def number_of_flags_given - PostAction.where(user_id: self.id) + PostAction + .where(user_id: self.id) .where(disagreed_at: nil) .where(post_action_type_id: PostActionType.notify_flag_type_ids) .count @@ -1487,9 +1572,7 @@ class User < ActiveRecord::Base end def anonymous? - SiteSetting.allow_anonymous_posting && - trust_level >= 1 && - !!anonymous_user_master + SiteSetting.allow_anonymous_posting && trust_level >= 1 && !!anonymous_user_master end def is_singular_admin? @@ -1504,25 +1587,23 @@ class User < ActiveRecord::Base def logged_in DiscourseEvent.trigger(:user_logged_in, self) - if !self.seen_before? - DiscourseEvent.trigger(:user_first_logged_in, self) - end + DiscourseEvent.trigger(:user_first_logged_in, self) if !self.seen_before? end def set_automatic_groups return if !active || staged || !email_confirmed? - Group.where(automatic: false) + Group + .where(automatic: false) .where("LENGTH(COALESCE(automatic_membership_email_domains, '')) > 0") .each do |group| + domains = group.automatic_membership_email_domains.gsub(".", '\.') - 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) + 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 - end @belonging_to_group_ids = nil end @@ -1540,7 +1621,10 @@ class User < ActiveRecord::Base 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) } + 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 @@ -1557,16 +1641,21 @@ class User < ActiveRecord::Base end def unconfirmed_emails - self.email_change_requests.where.not(change_state: EmailChangeRequest.states[:complete]).pluck(:new_email) + self + .email_change_requests + .where.not(change_state: EmailChangeRequest.states[:complete]) + .pluck(:new_email) end 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) + 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 @@ -1575,7 +1664,8 @@ class User < ActiveRecord::Base end def recent_time_read - @recent_time_read ||= self.user_visits.where('visited_at >= ?', RECENT_TIME_READ_THRESHOLD.ago).sum(:time_read) + @recent_time_read ||= + self.user_visits.where("visited_at >= ?", RECENT_TIME_READ_THRESHOLD.ago).sum(:time_read) end def from_staged? @@ -1588,7 +1678,8 @@ class User < ActiveRecord::Base 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.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.pluck_first(:title) @@ -1661,7 +1752,7 @@ class User < ActiveRecord::Base def active_do_not_disturb_timings now = Time.zone.now - do_not_disturb_timings.where('starts_at <= ? AND ends_at > ?', now, now) + do_not_disturb_timings.where("starts_at <= ? AND ends_at > ?", now, now) end def do_not_disturb_until @@ -1698,12 +1789,7 @@ class User < ActiveRecord::Base end def set_status!(description, emoji, ends_at = nil) - status = { - description: description, - emoji: emoji, - set_at: Time.zone.now, - ends_at: ends_at - } + status = { description: description, emoji: emoji, set_at: Time.zone.now, ends_at: ends_at } if user_status user_status.update!(status) @@ -1730,7 +1816,7 @@ class User < ActiveRecord::Base def expire_old_email_tokens if saved_change_to_password_hash? && !saved_change_to_id? - email_tokens.where('not expired').update_all(expired: true) + email_tokens.where("not expired").update_all(expired: true) end end @@ -1785,7 +1871,12 @@ class User < ActiveRecord::Base def hash_password(password, salt) raise StandardError.new("password is too long") if password.size > User.max_password_length - Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm) + Pbkdf2.hash_password( + password, + salt, + Rails.configuration.pbkdf2_iterations, + Rails.configuration.pbkdf2_algorithm, + ) end def add_trust_level @@ -1815,25 +1906,22 @@ class User < ActiveRecord::Base end def username_validator - username_format_validator || begin - if will_save_change_to_username? - existing = DB.query( - USERNAME_EXISTS_SQL, - username: self.class.normalize_username(username) - ) + username_format_validator || + begin + 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 + user_id = existing.select { |u| u.is_user }.first&.id + same_user = user_id && user_id == self.id - if existing.present? && !same_user - errors.add(:username, I18n.t(:'user.username.unique')) - end + errors.add(:username, I18n.t(:"user.username.unique")) if existing.present? && !same_user - if confirm_password?(username) || confirm_password?(username.downcase) - errors.add(:username, :same_as_password) + if confirm_password?(username) || confirm_password?(username.downcase) + errors.add(:username, :same_as_password) + end end end - end end def name_validator @@ -1858,14 +1946,14 @@ class User < ActiveRecord::Base # * default_categories_watching_first_post # * default_categories_normal # * default_categories_muted - %w{watching watching_first_post tracking normal muted}.each do |setting| + %w[watching watching_first_post tracking normal muted].each do |setting| category_ids = SiteSetting.get("default_categories_#{setting}").split("|").map(&:to_i) category_ids.each do |category_id| next if category_id == 0 values << { user_id: self.id, category_id: category_id, - notification_level: CategoryUser.notification_levels[setting.to_sym] + notification_level: CategoryUser.notification_levels[setting.to_sym], } end end @@ -1885,19 +1973,22 @@ class User < ActiveRecord::Base # * default_tags_tracking # * default_tags_watching_first_post # * default_tags_muted - %w{watching watching_first_post tracking muted}.each do |setting| + %w[watching watching_first_post tracking muted].each do |setting| tag_names = SiteSetting.get("default_tags_#{setting}").split("|") now = Time.zone.now - Tag.where(name: tag_names).pluck(:id).each do |tag_id| - values << { - user_id: self.id, - tag_id: tag_id, - notification_level: TagUser.notification_levels[setting.to_sym], - created_at: now, - updated_at: now - } - end + Tag + .where(name: tag_names) + .pluck(:id) + .each do |tag_id| + values << { + user_id: self.id, + tag_id: tag_id, + notification_level: TagUser.notification_levels[setting.to_sym], + created_at: now, + updated_at: now, + } + end end TagUser.insert_all!(values) if values.present? @@ -1912,20 +2003,24 @@ class User < ActiveRecord::Base .where(active: false) .where("created_at < ?", SiteSetting.purge_unactivated_users_grace_period_days.days.ago) .where("NOT admin AND NOT moderator") - .where("NOT EXISTS + .where( + "NOT EXISTS (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) - ") - .where("NOT EXISTS + ", + ) + .where( + "NOT EXISTS (SELECT 1 FROM posts p WHERE p.user_id = users.id LIMIT 1) - ") + ", + ) .limit(200) .find_each do |user| - begin - destroyer.destroy(user, context: I18n.t(:purge_reason)) - rescue Discourse::InvalidAccess - # keep going + begin + destroyer.destroy(user, context: I18n.t(:purge_reason)) + rescue Discourse::InvalidAccess + # keep going + end end - end end def match_primary_group_changes @@ -1935,16 +2030,15 @@ class User < ActiveRecord::Base self.title = primary_group&.title end - if flair_group_id == primary_group_id_was - self.flair_group_id = primary_group&.id - end + self.flair_group_id = primary_group&.id if flair_group_id == primary_group_id_was end def self.first_login_admin_id - User.where(admin: true) + User + .where(admin: true) .human_users .joins(:user_auth_tokens) - .order('user_auth_tokens.created_at') + .order("user_auth_tokens.created_at") .pluck_first(:id) end @@ -1958,15 +2052,18 @@ class User < ActiveRecord::Base categories_to_update = SiteSetting.default_sidebar_categories.split("|") if update - filtered_default_category_ids = Category.secured(self.guardian).where(id: categories_to_update).pluck(:id) - existing_category_ids = SidebarSectionLink.where(user: self, linkable_type: 'Category').pluck(:linkable_id) + filtered_default_category_ids = + Category.secured(self.guardian).where(id: categories_to_update).pluck(:id) + existing_category_ids = + SidebarSectionLink.where(user: self, linkable_type: "Category").pluck(:linkable_id) - categories_to_update = existing_category_ids + (filtered_default_category_ids & self.secure_category_ids) + categories_to_update = + existing_category_ids + (filtered_default_category_ids & self.secure_category_ids) end SidebarSectionLinksUpdater.update_category_section_links( self, - category_ids: categories_to_update + category_ids: categories_to_update, ) end @@ -1975,18 +2072,24 @@ class User < ActiveRecord::Base if update default_tag_ids = Tag.where(name: tags_to_update).pluck(:id) - filtered_default_tags = DiscourseTagging.filter_visible(Tag, self.guardian).where(id: default_tag_ids).pluck(:name) + filtered_default_tags = + DiscourseTagging + .filter_visible(Tag, self.guardian) + .where(id: default_tag_ids) + .pluck(:name) - existing_tag_ids = SidebarSectionLink.where(user: self, linkable_type: 'Tag').pluck(:linkable_id) - existing_tags = DiscourseTagging.filter_visible(Tag, self.guardian).where(id: existing_tag_ids).pluck(:name) + existing_tag_ids = + SidebarSectionLink.where(user: self, linkable_type: "Tag").pluck(:linkable_id) + existing_tags = + DiscourseTagging + .filter_visible(Tag, self.guardian) + .where(id: existing_tag_ids) + .pluck(:name) tags_to_update = existing_tags + (filtered_default_tags & DiscourseTagging.hidden_tag_names) end - SidebarSectionLinksUpdater.update_tag_section_links( - self, - tag_names: tags_to_update - ) + SidebarSectionLinksUpdater.update_tag_section_links(self, tag_names: tags_to_update) end end @@ -2003,9 +2106,7 @@ class User < ActiveRecord::Base end def trigger_user_automatic_group_refresh - if !staged - Group.user_trust_level_change!(id, trust_level) - end + Group.user_trust_level_change!(id, trust_level) if !staged true end @@ -2016,12 +2117,14 @@ class User < ActiveRecord::Base def check_if_title_is_badged_granted if title_changed? && !new_record? && user_profile - badge_matching_title = title && badges.find do |badge| - badge.allow_title? && (badge.display_name == title || badge.name == title) - end + badge_matching_title = + title && + badges.find do |badge| + badge.allow_title? && (badge.display_name == title || badge.name == title) + end user_profile.update( badge_granted_title: badge_matching_title.present?, - granted_title_badge_id: badge_matching_title&.id + granted_title_badge_id: badge_matching_title&.id, ) end end @@ -2032,9 +2135,7 @@ class User < ActiveRecord::Base def update_previous_visit(timestamp) update_visit_record!(timestamp.to_date) - if previous_visit_at_update_required?(timestamp) - update_column(:previous_visit_at, last_seen_at) - end + update_column(:previous_visit_at, last_seen_at) if previous_visit_at_update_required?(timestamp) end def trigger_user_created_event @@ -2048,16 +2149,14 @@ class User < ActiveRecord::Base end def set_skip_validate_email - if self.primary_email - self.primary_email.skip_validate_email = !should_validate_email_address? - end + self.primary_email.skip_validate_email = !should_validate_email_address? if self.primary_email true end def check_site_contact_username if (saved_change_to_admin? || saved_change_to_moderator?) && - self.username == SiteSetting.site_contact_username && !staff? + self.username == SiteSetting.site_contact_username && !staff? SiteSetting.set_and_log(:site_contact_username, SiteSetting.defaults[:site_contact_username]) end end diff --git a/app/models/user_action.rb b/app/models/user_action.rb index 25eabac3ff4..d605324de47 100644 --- a/app/models/user_action.rb +++ b/app/models/user_action.rb @@ -21,59 +21,58 @@ class UserAction < ActiveRecord::Base SOLVED = 15 ASSIGNED = 16 - ORDER = Hash[*[ - GOT_PRIVATE_MESSAGE, - NEW_PRIVATE_MESSAGE, - NEW_TOPIC, - REPLY, - RESPONSE, - LIKE, - WAS_LIKED, - MENTION, - QUOTE, - EDIT, - SOLVED, - ASSIGNED, - ].each_with_index.to_a.flatten] + ORDER = + Hash[ + *[ + GOT_PRIVATE_MESSAGE, + NEW_PRIVATE_MESSAGE, + NEW_TOPIC, + REPLY, + RESPONSE, + LIKE, + WAS_LIKED, + MENTION, + QUOTE, + EDIT, + SOLVED, + ASSIGNED, + ].each_with_index.to_a.flatten + ] USER_ACTED_TYPES = [LIKE, NEW_TOPIC, REPLY, NEW_PRIVATE_MESSAGE] def self.types - @types ||= Enum.new( - like: 1, - was_liked: 2, - # NOTE: Previously type 3 was bookmark but this was removed when we - # changed to using the Bookmark model. - new_topic: 4, - reply: 5, - response: 6, - mention: 7, - quote: 9, - edit: 11, - new_private_message: 12, - got_private_message: 13, - solved: 15, - assigned: 16) + @types ||= + Enum.new( + like: 1, + was_liked: 2, + # NOTE: Previously type 3 was bookmark but this was removed when we + # changed to using the Bookmark model. + new_topic: 4, + reply: 5, + response: 6, + mention: 7, + quote: 9, + edit: 11, + new_private_message: 12, + got_private_message: 13, + solved: 15, + assigned: 16, + ) end def self.private_types - @private_types ||= [ - WAS_LIKED, - RESPONSE, - MENTION, - QUOTE, - EDIT - ] + @private_types ||= [WAS_LIKED, RESPONSE, MENTION, QUOTE, EDIT] end def self.last_action_in_topic(user_id, topic_id) - UserAction.where(user_id: user_id, - target_topic_id: topic_id, - action_type: [RESPONSE, MENTION, QUOTE]).order('created_at DESC').pluck_first(:target_post_id) + UserAction + .where(user_id: user_id, target_topic_id: topic_id, action_type: [RESPONSE, MENTION, QUOTE]) + .order("created_at DESC") + .pluck_first(:target_post_id) end def self.stats(user_id, guardian) - # Sam: I tried this in AR and it got complex builder = DB.build <<~SQL @@ -87,7 +86,7 @@ class UserAction < ActiveRecord::Base GROUP BY action_type SQL - builder.where('a.user_id = :user_id', user_id: user_id) + builder.where("a.user_id = :user_id", user_id: user_id) apply_common_filters(builder, user_id, guardian) @@ -128,23 +127,20 @@ class UserAction < ActiveRecord::Base result = { all: all, mine: mine, unread: unread } - DB.query(sql, user_id: user_id).each do |row| - (result[:groups] ||= []) << { name: row.name, count: row.count.to_i } - end + DB + .query(sql, user_id: user_id) + .each { |row| (result[:groups] ||= []) << { name: row.name, count: row.count.to_i } } result - end def self.count_daily_engaged_users(start_date = nil, end_date = nil) - result = select(:user_id) - .distinct - .where(action_type: USER_ACTED_TYPES) + result = select(:user_id).distinct.where(action_type: USER_ACTED_TYPES) if start_date && end_date - result = result.group('date(created_at)') - result = result.where('created_at > ? AND created_at < ?', start_date, end_date) - result = result.order('date(created_at)') + result = result.group("date(created_at)") + result = result.where("created_at > ? AND created_at < ?", start_date, end_date) + result = result.order("date(created_at)") end result.count @@ -154,28 +150,29 @@ class UserAction < ActiveRecord::Base stream(action_id: action_id, guardian: guardian).first end - NULL_QUEUED_STREAM_COLS = %i{ - cooked - uploaded_avatar_id - acting_name - acting_username - acting_user_id - target_name - target_username - target_user_id - post_number - post_id - deleted - hidden - post_type - action_type - action_code - action_code_who - action_code_path - topic_closed - topic_id - topic_archived - }.map! { |s| "NULL as #{s}" }.join(", ") + NULL_QUEUED_STREAM_COLS = + %i[ + cooked + uploaded_avatar_id + acting_name + acting_username + acting_user_id + target_name + target_username + target_user_id + post_number + post_id + deleted + hidden + post_type + action_type + action_code + action_code_who + action_code_path + topic_closed + topic_id + topic_archived + ].map! { |s| "NULL as #{s}" }.join(", ") def self.stream(opts = nil) opts ||= {} @@ -191,13 +188,10 @@ class UserAction < ActiveRecord::Base # Acting user columns. Can be extended by plugins to include custom avatar # columns - acting_cols = [ - 'u.id AS acting_user_id', - 'u.name AS acting_name' - ] + acting_cols = ["u.id AS acting_user_id", "u.name AS acting_name"] UserLookup.lookup_columns.each do |c| - next if c == :id || c['.'] + next if c == :id || c["."] acting_cols << "u.#{c} AS acting_#{c}" end @@ -213,7 +207,7 @@ class UserAction < ActiveRecord::Base p.reply_to_post_number, pu.username, pu.name, pu.id user_id, pu.uploaded_avatar_id, - #{acting_cols.join(', ')}, + #{acting_cols.join(", ")}, coalesce(p.cooked, p2.cooked) cooked, CASE WHEN coalesce(p.deleted_at, p2.deleted_at, t.deleted_at) IS NULL THEN false ELSE true END deleted, p.hidden, @@ -245,11 +239,14 @@ class UserAction < ActiveRecord::Base builder.where("a.id = :id", id: action_id.to_i) else builder.where("a.user_id = :user_id", user_id: user_id.to_i) - builder.where("a.action_type in (:action_types)", action_types: action_types) if action_types && action_types.length > 0 + if action_types && action_types.length > 0 + builder.where("a.action_type in (:action_types)", action_types: action_types) + end if acting_username - builder.where("u.username_lower = :acting_username", - acting_username: acting_username.downcase + builder.where( + "u.username_lower = :acting_username", + acting_username: acting_username.downcase, ) end @@ -257,17 +254,14 @@ class UserAction < ActiveRecord::Base builder.where("a.action_type <> :mention_type", mention_type: UserAction::MENTION) end - builder - .order_by("a.created_at desc") - .offset(offset.to_i) - .limit(limit.to_i) + builder.order_by("a.created_at desc").offset(offset.to_i).limit(limit.to_i) end builder.query end def self.log_action!(hash) - required_parameters = [:action_type, :user_id, :acting_user_id] + required_parameters = %i[action_type user_id acting_user_id] required_parameters << :target_post_id required_parameters << :target_topic_id @@ -284,18 +278,14 @@ class UserAction < ActiveRecord::Base action = self.new(hash) - if hash[:created_at] - action.created_at = hash[:created_at] - end + action.created_at = hash[:created_at] if hash[:created_at] action.save! user_id = hash[:user_id] topic = Topic.includes(:category).find_by(id: hash[:target_topic_id]) - if topic && !topic.private_message? - update_like_count(user_id, hash[:action_type], 1) - end + update_like_count(user_id, hash[:action_type], 1) if topic && !topic.private_message? group_ids = nil if topic && topic.category && topic.category.read_restricted @@ -304,11 +294,15 @@ class UserAction < ActiveRecord::Base end if action.user - MessageBus.publish("/u/#{action.user.username_lower}", action.id, user_ids: [user_id], group_ids: group_ids) + MessageBus.publish( + "/u/#{action.user.username_lower}", + action.id, + user_ids: [user_id], + group_ids: group_ids, + ) end action - rescue ActiveRecord::RecordNotUnique # can happen, don't care already logged raise ActiveRecord::Rollback @@ -317,7 +311,14 @@ class UserAction < ActiveRecord::Base end def self.remove_action!(hash) - require_parameters(hash, :action_type, :user_id, :acting_user_id, :target_topic_id, :target_post_id) + require_parameters( + hash, + :action_type, + :user_id, + :acting_user_id, + :target_topic_id, + :target_post_id, + ) if action = UserAction.find_by(hash.except(:created_at)) action.destroy MessageBus.publish("/user/#{hash[:user_id]}", user_action_id: action.id, remove: true) @@ -329,7 +330,6 @@ class UserAction < ActiveRecord::Base end def self.synchronize_target_topic_ids(post_ids = nil, limit: nil) - # nuke all dupes, using magic builder = DB.build <<~SQL DELETE FROM user_actions USING user_actions ua2 @@ -345,19 +345,15 @@ class UserAction < ActiveRecord::Base user_actions.id > ua2.id SQL - if limit - builder.where(<<~SQL, limit: limit) + builder.where(<<~SQL, limit: limit) if limit user_actions.target_post_id IN ( SELECT target_post_id FROM user_actions WHERE created_at > :limit ) SQL - end - if post_ids - builder.where("user_actions.target_post_id in (:post_ids)", post_ids: post_ids) - end + builder.where("user_actions.target_post_id in (:post_ids)", post_ids: post_ids) if post_ids builder.exec @@ -368,19 +364,15 @@ class UserAction < ActiveRecord::Base SQL builder.where("target_topic_id <> (select topic_id from posts where posts.id = target_post_id)") - if post_ids - builder.where("target_post_id in (:post_ids)", post_ids: post_ids) - end + builder.where("target_post_id in (:post_ids)", post_ids: post_ids) if post_ids - if limit - builder.where(<<~SQL, limit: limit) + builder.where(<<~SQL, limit: limit) if limit target_post_id IN ( SELECT target_post_id FROM user_actions WHERE created_at > :limit ) SQL - end builder.exec end @@ -407,11 +399,17 @@ class UserAction < ActiveRecord::Base current_user_id = -2 current_user_id = guardian.user.id if guardian.user - builder.where("NOT COALESCE(p.hidden, false) OR p.user_id = :current_user_id", current_user_id: current_user_id) + builder.where( + "NOT COALESCE(p.hidden, false) OR p.user_id = :current_user_id", + current_user_id: current_user_id, + ) end visible_post_types = Topic.visible_post_types(guardian.user) - builder.where("COALESCE(p.post_type, p2.post_type) IN (:visible_post_types)", visible_post_types: visible_post_types) + builder.where( + "COALESCE(p.post_type, p2.post_type) IN (:visible_post_types)", + visible_post_types: visible_post_types, + ) unless (guardian.user && guardian.user.id == user_id) || guardian.is_staff? builder.where("t.visible") @@ -423,7 +421,7 @@ class UserAction < ActiveRecord::Base def self.filter_private_messages(builder, user_id, guardian, ignore_private_messages = false) if !guardian.can_see_private_messages?(user_id) || ignore_private_messages || !guardian.user - builder.where("t.archetype <> :private_message", private_message: Archetype::private_message) + builder.where("t.archetype <> :private_message", private_message: Archetype.private_message) else unless guardian.is_admin? sql = <<~SQL @@ -438,7 +436,11 @@ class UserAction < ActiveRecord::Base ) SQL - builder.where(sql, private_message: Archetype::private_message, current_user_id: guardian.user.id) + builder.where( + sql, + private_message: Archetype.private_message, + current_user_id: guardian.user.id, + ) end end builder @@ -448,9 +450,12 @@ class UserAction < ActiveRecord::Base unless guardian.is_admin? allowed = guardian.secure_category_ids if allowed.present? - builder.where("( c.read_restricted IS NULL OR + builder.where( + "( c.read_restricted IS NULL OR NOT c.read_restricted OR - (c.read_restricted and c.id in (:cats)) )", cats: guardian.secure_category_ids) + (c.read_restricted and c.id in (:cats)) )", + cats: guardian.secure_category_ids, + ) else builder.where("(c.read_restricted IS NULL OR NOT c.read_restricted)") end @@ -459,9 +464,7 @@ class UserAction < ActiveRecord::Base end def self.require_parameters(data, *params) - params.each do |p| - raise Discourse::InvalidParameters.new(p) if data[p].nil? - end + params.each { |p| raise Discourse::InvalidParameters.new(p) if data[p].nil? } end end diff --git a/app/models/user_api_key.rb b/app/models/user_api_key.rb index d0238781bc7..59815a5c70c 100644 --- a/app/models/user_api_key.rb +++ b/app/models/user_api_key.rb @@ -2,7 +2,7 @@ class UserApiKey < ActiveRecord::Base self.ignored_columns = [ - "scopes" # TODO(2020-12-18): remove + "scopes", # TODO(2020-12-18): remove ] REVOKE_MATCHER = RouteMatcher.new(actions: "user_api_keys#revoke", methods: :post, params: [:id]) @@ -23,7 +23,9 @@ class UserApiKey < ActiveRecord::Base end def key - raise ApiKey::KeyAccessError.new "API key is only accessible immediately after creation" unless key_available? + unless key_available? + raise ApiKey::KeyAccessError.new "API key is only accessible immediately after creation" + end @key end @@ -41,7 +43,7 @@ class UserApiKey < ActiveRecord::Base # invalidate old dupe api key for client if needed UserApiKey .where(client_id: client_id, user_id: self.user_id) - .where('id <> ?', self.id) + .where("id <> ?", self.id) .destroy_all update_args[:client_id] = client_id @@ -59,8 +61,7 @@ class UserApiKey < ActiveRecord::Base end def has_push? - scopes.any? { |s| s.name == "push" || s.name == "notifications" } && - push_url.present? && + scopes.any? { |s| s.name == "push" || s.name == "notifications" } && push_url.present? && SiteSetting.allowed_user_api_push_urls.include?(push_url) end @@ -69,8 +70,9 @@ class UserApiKey < ActiveRecord::Base end def self.invalid_auth_redirect?(auth_redirect) - SiteSetting.allowed_user_api_auth_redirects - .split('|') + SiteSetting + .allowed_user_api_auth_redirects + .split("|") .none? { |u| WildcardUrlChecker.check_url(u, auth_redirect) } end diff --git a/app/models/user_api_key_scope.rb b/app/models/user_api_key_scope.rb index d2f0c408a36..0ea94279649 100644 --- a/app/models/user_api_key_scope.rb +++ b/app/models/user_api_key_scope.rb @@ -2,26 +2,33 @@ class UserApiKeyScope < ActiveRecord::Base SCOPES = { - read: [ RouteMatcher.new(methods: :get) ], - write: [ RouteMatcher.new(methods: [:get, :post, :patch, :put, :delete]) ], - message_bus: [ RouteMatcher.new(methods: :post, actions: 'message_bus') ], + read: [RouteMatcher.new(methods: :get)], + write: [RouteMatcher.new(methods: %i[get post patch put delete])], + message_bus: [RouteMatcher.new(methods: :post, actions: "message_bus")], push: [], one_time_password: [], notifications: [ - RouteMatcher.new(methods: :post, actions: 'message_bus'), - RouteMatcher.new(methods: :get, actions: 'notifications#index'), - RouteMatcher.new(methods: :put, actions: 'notifications#mark_read') + RouteMatcher.new(methods: :post, actions: "message_bus"), + RouteMatcher.new(methods: :get, actions: "notifications#index"), + RouteMatcher.new(methods: :put, actions: "notifications#mark_read"), ], session_info: [ - RouteMatcher.new(methods: :get, actions: 'session#current'), - RouteMatcher.new(methods: :get, actions: 'users#topic_tracking_state') + RouteMatcher.new(methods: :get, actions: "session#current"), + RouteMatcher.new(methods: :get, actions: "users#topic_tracking_state"), + ], + bookmarks_calendar: [ + RouteMatcher.new( + methods: :get, + actions: "users#bookmarks", + formats: :ics, + params: %i[username], + ), ], - bookmarks_calendar: [ RouteMatcher.new(methods: :get, actions: 'users#bookmarks', formats: :ics, params: %i[username]) ], user_status: [ - RouteMatcher.new(methods: :get, actions: 'user_status#get'), - RouteMatcher.new(methods: :put, actions: 'user_status#set'), - RouteMatcher.new(methods: :delete, actions: 'user_status#clear') - ] + RouteMatcher.new(methods: :get, actions: "user_status#get"), + RouteMatcher.new(methods: :put, actions: "user_status#set"), + RouteMatcher.new(methods: :delete, actions: "user_status#clear"), + ], } def self.all_scopes diff --git a/app/models/user_archived_message.rb b/app/models/user_archived_message.rb index 2410f50e2e2..787209c702c 100644 --- a/app/models/user_archived_message.rb +++ b/app/models/user_archived_message.rb @@ -7,11 +7,15 @@ class UserArchivedMessage < ActiveRecord::Base def self.move_to_inbox!(user_id, topic) topic_id = topic.id - return if (TopicUser.where( - user_id: user_id, - topic_id: topic_id, - notification_level: TopicUser.notification_levels[:muted] - ).exists?) + if ( + TopicUser.where( + user_id: user_id, + topic_id: topic_id, + notification_level: TopicUser.notification_levels[:muted], + ).exists? + ) + return + end UserArchivedMessage.where(user_id: user_id, topic_id: topic_id).destroy_all trigger(:move_to_inbox, user_id, topic_id) @@ -29,9 +33,7 @@ class UserArchivedMessage < ActiveRecord::Base def self.trigger(event, user_id, topic_id) user = User.find_by(id: user_id) topic = Topic.find_by(id: topic_id) - if user && topic - DiscourseEvent.trigger(event, user: user, topic: topic) - end + DiscourseEvent.trigger(event, user: user, topic: topic) if user && topic end end diff --git a/app/models/user_associated_group.rb b/app/models/user_associated_group.rb index 1413efb7c67..815309d1aaa 100644 --- a/app/models/user_associated_group.rb +++ b/app/models/user_associated_group.rb @@ -4,7 +4,7 @@ class UserAssociatedGroup < ActiveRecord::Base belongs_to :user belongs_to :associated_group - after_commit :add_to_associated_groups, on: [:create, :update] + after_commit :add_to_associated_groups, on: %i[create update] before_destroy :remove_from_associated_groups def add_to_associated_groups @@ -14,7 +14,9 @@ class UserAssociatedGroup < ActiveRecord::Base end def remove_from_associated_groups - Group.where("NOT EXISTS( + Group + .where( + "NOT EXISTS( SELECT 1 FROM user_associated_groups uag JOIN group_associated_groups gag @@ -22,9 +24,11 @@ class UserAssociatedGroup < ActiveRecord::Base WHERE uag.user_id = :user_id AND uag.id != :uag_id AND gag.group_id = groups.id - )", uag_id: id, user_id: user_id).each do |group| - group.remove_automatically(user, subject: associated_group.label) - end + )", + uag_id: id, + user_id: user_id, + ) + .each { |group| group.remove_automatically(user, subject: associated_group.label) } end end diff --git a/app/models/user_auth_token.rb b/app/models/user_auth_token.rb index 6566371b206..97eb0372ff7 100644 --- a/app/models/user_auth_token.rb +++ b/app/models/user_auth_token.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'digest/sha1' +require "digest/sha1" class UserAuthToken < ActiveRecord::Base belongs_to :user @@ -11,13 +11,13 @@ class UserAuthToken < ActiveRecord::Base MAX_SESSION_COUNT = 60 - USER_ACTIONS = ['generate'] + USER_ACTIONS = ["generate"] attr_accessor :unhashed_auth_token before_destroy do UserAuthToken.log_verbose( - action: 'destroy', + action: "destroy", user_auth_token_id: self.id, user_id: self.user_id, user_agent: self.user_agent, @@ -31,9 +31,7 @@ class UserAuthToken < ActiveRecord::Base end def self.log_verbose(info) - if SiteSetting.verbose_auth_token_logging - log(info) - end + log(info) if SiteSetting.verbose_auth_token_logging end RAD_PER_DEG = Math::PI / 180 @@ -49,8 +47,10 @@ class UserAuthToken < ActiveRecord::Base lat1_rad, lon1_rad = loc1[0] * RAD_PER_DEG, loc1[1] * RAD_PER_DEG lat2_rad, lon2_rad = loc2[0] * RAD_PER_DEG, loc2[1] * RAD_PER_DEG - a = Math.sin((lat2_rad - lat1_rad) / 2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin((lon2_rad - lon1_rad) / 2)**2 - c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a)) + a = + Math.sin((lat2_rad - lat1_rad) / 2)**2 + + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin((lon2_rad - lon1_rad) / 2)**2 + c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) c * EARTH_RADIUS_KM end @@ -72,21 +72,29 @@ class UserAuthToken < ActiveRecord::Base end end - def self.generate!(user_id: , user_agent: nil, client_ip: nil, path: nil, staff: nil, impersonate: false) + def self.generate!( + user_id:, + user_agent: nil, + client_ip: nil, + path: nil, + staff: nil, + impersonate: false + ) token = SecureRandom.hex(16) hashed_token = hash_token(token) - user_auth_token = UserAuthToken.create!( - user_id: user_id, - user_agent: user_agent, - client_ip: client_ip, - auth_token: hashed_token, - prev_auth_token: hashed_token, - rotated_at: Time.zone.now - ) + user_auth_token = + UserAuthToken.create!( + user_id: user_id, + user_agent: user_agent, + client_ip: client_ip, + auth_token: hashed_token, + prev_auth_token: hashed_token, + rotated_at: Time.zone.now, + ) user_auth_token.unhashed_auth_token = token log( - action: 'generate', + action: "generate", user_auth_token_id: user_auth_token.id, user_id: user_id, user_agent: user_agent, @@ -96,10 +104,12 @@ class UserAuthToken < ActiveRecord::Base ) if staff && !impersonate - Jobs.enqueue(:suspicious_login, + Jobs.enqueue( + :suspicious_login, user_id: user_id, client_ip: client_ip, - user_agent: user_agent) + user_agent: user_agent, + ) end user_auth_token @@ -111,12 +121,15 @@ class UserAuthToken < ActiveRecord::Base token = hash_token(unhashed_token) expire_before = SiteSetting.maximum_session_age.hours.ago - user_token = find_by("(auth_token = :token OR + user_token = + find_by( + "(auth_token = :token OR prev_auth_token = :token) AND rotated_at > :expire_before", - token: token, expire_before: expire_before) + token: token, + expire_before: expire_before, + ) if !user_token - log_verbose( action: "miss token", user_id: nil, @@ -129,11 +142,13 @@ class UserAuthToken < ActiveRecord::Base return nil end - if user_token.auth_token != token && user_token.prev_auth_token == token && user_token.auth_token_seen - changed_rows = UserAuthToken - .where("rotated_at < ?", 1.minute.ago) - .where(id: user_token.id, prev_auth_token: token) - .update_all(auth_token_seen: false) + if user_token.auth_token != token && user_token.prev_auth_token == token && + user_token.auth_token_seen + changed_rows = + UserAuthToken + .where("rotated_at < ?", 1.minute.ago) + .where(id: user_token.id, prev_auth_token: token) + .update_all(auth_token_seen: false) # not updating AR model cause we want to give it one more req # with wrong cookie @@ -144,15 +159,17 @@ class UserAuthToken < ActiveRecord::Base auth_token: user_token.auth_token, user_agent: opts && opts[:user_agent], path: opts && opts[:path], - client_ip: opts && opts[:client_ip] + client_ip: opts && opts[:client_ip], ) end if mark_seen && user_token && !user_token.auth_token_seen && user_token.auth_token == token # we must protect against concurrency issues here - changed_rows = UserAuthToken - .where(id: user_token.id, auth_token: token) - .update_all(auth_token_seen: true, seen_at: Time.zone.now) + changed_rows = + UserAuthToken.where(id: user_token.id, auth_token: token).update_all( + auth_token_seen: true, + seen_at: Time.zone.now, + ) if changed_rows == 1 # not doing a reload so we don't risk loading a rotated token @@ -179,15 +196,17 @@ class UserAuthToken < ActiveRecord::Base end def self.cleanup! - if SiteSetting.verbose_auth_token_logging - UserAuthTokenLog.where('created_at < :time', - time: SiteSetting.maximum_session_age.hours.ago - ROTATE_TIME).delete_all + UserAuthTokenLog.where( + "created_at < :time", + time: SiteSetting.maximum_session_age.hours.ago - ROTATE_TIME, + ).delete_all end - where('rotated_at < :time', - time: SiteSetting.maximum_session_age.hours.ago - ROTATE_TIME).delete_all - + where( + "rotated_at < :time", + time: SiteSetting.maximum_session_age.hours.ago - ROTATE_TIME, + ).delete_all end def rotate!(info = nil) @@ -196,7 +215,9 @@ class UserAuthToken < ActiveRecord::Base token = SecureRandom.hex(16) - result = DB.exec(" + result = + DB.exec( + " UPDATE user_auth_tokens SET auth_token_seen = false, @@ -207,13 +228,14 @@ class UserAuthToken < ActiveRecord::Base auth_token = :new_token, rotated_at = :now WHERE id = :id AND (auth_token_seen or rotated_at < :safeguard_time) -", id: self.id, - user_agent: user_agent, - client_ip: client_ip&.to_s, - now: Time.zone.now, - new_token: UserAuthToken.hash_token(token), - safeguard_time: 30.seconds.ago - ) +", + id: self.id, + user_agent: user_agent, + client_ip: client_ip&.to_s, + now: Time.zone.now, + new_token: UserAuthToken.hash_token(token), + safeguard_time: 30.seconds.ago, + ) if result > 0 reload @@ -226,20 +248,21 @@ class UserAuthToken < ActiveRecord::Base auth_token: auth_token, user_agent: user_agent, client_ip: client_ip, - path: info && info[:path] + path: info && info[:path], ) true else false end - end def self.enforce_session_count_limit!(user_id) - tokens_to_destroy = where(user_id: user_id). - where('rotated_at > ?', SiteSetting.maximum_session_age.hours.ago). - order("rotated_at DESC").offset(MAX_SESSION_COUNT) + tokens_to_destroy = + where(user_id: user_id) + .where("rotated_at > ?", SiteSetting.maximum_session_age.hours.ago) + .order("rotated_at DESC") + .offset(MAX_SESSION_COUNT) tokens_to_destroy.delete_all # Returns the number of deleted rows end diff --git a/app/models/user_avatar.rb b/app/models/user_avatar.rb index 349509d0149..20f0e1d21bb 100644 --- a/app/models/user_avatar.rb +++ b/app/models/user_avatar.rb @@ -2,8 +2,8 @@ class UserAvatar < ActiveRecord::Base belongs_to :user - belongs_to :gravatar_upload, class_name: 'Upload' - belongs_to :custom_upload, class_name: 'Upload' + belongs_to :gravatar_upload, class_name: "Upload" + belongs_to :custom_upload, class_name: "Upload" has_many :upload_references, as: :target, dependent: :destroy after_save do @@ -14,7 +14,7 @@ class UserAvatar < ActiveRecord::Base end @@custom_user_gravatar_email_hash = { - Discourse::SYSTEM_USER_ID => User.email_hash("info@discourse.org") + Discourse::SYSTEM_USER_ID => User.email_hash("info@discourse.org"), } def self.register_custom_user_gravatar_email_hash(user_id, email) @@ -36,33 +36,36 @@ class UserAvatar < ActiveRecord::Base return if user.blank? || user.primary_email.blank? email_hash = @@custom_user_gravatar_email_hash[user_id] || user.email_hash - gravatar_url = "https://#{SiteSetting.gravatar_base_url}/avatar/#{email_hash}.png?s=#{max}&d=404&reset_cache=#{SecureRandom.urlsafe_base64(5)}" + gravatar_url = + "https://#{SiteSetting.gravatar_base_url}/avatar/#{email_hash}.png?s=#{max}&d=404&reset_cache=#{SecureRandom.urlsafe_base64(5)}" if SiteSetting.verbose_upload_logging Rails.logger.warn("Verbose Upload Logging: Downloading gravatar from #{gravatar_url}") end # follow redirects in case gravatar change rules on us - tempfile = FileHelper.download( - gravatar_url, - max_file_size: SiteSetting.max_image_size_kb.kilobytes, - tmp_file_name: "gravatar", - skip_rate_limit: true, - verbose: false, - follow_redirect: true - ) + tempfile = + FileHelper.download( + gravatar_url, + max_file_size: SiteSetting.max_image_size_kb.kilobytes, + tmp_file_name: "gravatar", + skip_rate_limit: true, + verbose: false, + follow_redirect: true, + ) if tempfile ext = File.extname(tempfile) - ext = '.png' if ext.blank? + ext = ".png" if ext.blank? - upload = UploadCreator.new( - tempfile, - "gravatar#{ext}", - origin: gravatar_url, - type: "avatar", - for_gravatar: true - ).create_for(user_id) + upload = + UploadCreator.new( + tempfile, + "gravatar#{ext}", + origin: gravatar_url, + type: "avatar", + for_gravatar: true, + ).create_for(user_id) if gravatar_upload_id != upload.id User.transaction do @@ -75,9 +78,7 @@ class UserAvatar < ActiveRecord::Base end end rescue OpenURI::HTTPError => e - if e.io&.status[0].to_i != 404 - raise e - end + raise e if e.io&.status[0].to_i != 404 ensure tempfile&.close! end @@ -111,19 +112,26 @@ class UserAvatar < ActiveRecord::Base Rails.logger.warn("Verbose Upload Logging: Downloading sso-avatar from #{avatar_url}") end - tempfile = FileHelper.download( - avatar_url, - max_file_size: SiteSetting.max_image_size_kb.kilobytes, - tmp_file_name: "sso-avatar", - follow_redirect: true - ) + tempfile = + FileHelper.download( + avatar_url, + max_file_size: SiteSetting.max_image_size_kb.kilobytes, + tmp_file_name: "sso-avatar", + follow_redirect: true, + ) return unless tempfile ext = FastImage.type(tempfile).to_s tempfile.rewind - upload = UploadCreator.new(tempfile, "external-avatar." + ext, origin: avatar_url, type: "avatar").create_for(user.id) + upload = + UploadCreator.new( + tempfile, + "external-avatar." + ext, + origin: avatar_url, + type: "avatar", + ).create_for(user.id) user.create_user_avatar! unless user.user_avatar @@ -132,13 +140,10 @@ class UserAvatar < ActiveRecord::Base override_gravatar = !options || options[:override_gravatar] if user.uploaded_avatar_id.nil? || - !user.user_avatar.contains_upload?(user.uploaded_avatar_id) || - override_gravatar - + !user.user_avatar.contains_upload?(user.uploaded_avatar_id) || override_gravatar user.update!(uploaded_avatar_id: upload.id) end end - rescue Net::ReadTimeout, OpenURI::HTTPError # skip saving, we are not connected to the net ensure diff --git a/app/models/user_badge.rb b/app/models/user_badge.rb index ec820a18089..dd919156314 100644 --- a/app/models/user_badge.rb +++ b/app/models/user_badge.rb @@ -3,49 +3,49 @@ class UserBadge < ActiveRecord::Base belongs_to :badge belongs_to :user - belongs_to :granted_by, class_name: 'User' + belongs_to :granted_by, class_name: "User" belongs_to :notification, dependent: :destroy belongs_to :post - BOOLEAN_ATTRIBUTES = %w(is_favorite) + BOOLEAN_ATTRIBUTES = %w[is_favorite] - scope :grouped_with_count, -> { - group(:badge_id, :user_id) - .select_for_grouping - .order('MAX(featured_rank) ASC') - .includes(:user, :granted_by, { badge: :badge_type }, post: :topic) - } + scope :grouped_with_count, + -> { + group(:badge_id, :user_id) + .select_for_grouping + .order("MAX(featured_rank) ASC") + .includes(:user, :granted_by, { badge: :badge_type }, post: :topic) + } - scope :select_for_grouping, -> { - select( - UserBadge.attribute_names.map do |name| - operation = BOOLEAN_ATTRIBUTES.include?(name) ? "BOOL_OR" : "MAX" - "#{operation}(user_badges.#{name}) AS #{name}" - end, - 'COUNT(*) AS "count"' - ) - } + scope :select_for_grouping, + -> { + select( + UserBadge.attribute_names.map do |name| + operation = BOOLEAN_ATTRIBUTES.include?(name) ? "BOOL_OR" : "MAX" + "#{operation}(user_badges.#{name}) AS #{name}" + end, + 'COUNT(*) AS "count"', + ) + } - scope :for_enabled_badges, -> { where('user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)') } + scope :for_enabled_badges, + -> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") } - validates :badge_id, - presence: true, - uniqueness: { scope: :user_id }, - if: :single_grant_badge? + validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge? validates :user_id, presence: true validates :granted_at, presence: true validates :granted_by, presence: true after_create do - Badge.increment_counter 'grant_count', self.badge_id + Badge.increment_counter "grant_count", self.badge_id UserStat.update_distinct_badge_count self.user_id UserBadge.update_featured_ranks! self.user_id self.trigger_user_badge_granted_event end after_destroy do - Badge.decrement_counter 'grant_count', self.badge_id + Badge.decrement_counter "grant_count", self.badge_id UserStat.update_distinct_badge_count self.user_id UserBadge.update_featured_ranks! self.user_id DiscourseEvent.trigger(:user_badge_removed, self.badge_id, self.user_id) diff --git a/app/models/user_badges.rb b/app/models/user_badges.rb index 4b0f8588307..65de5a66162 100644 --- a/app/models/user_badges.rb +++ b/app/models/user_badges.rb @@ -2,7 +2,7 @@ # view model for user badges class UserBadges - alias :read_attribute_for_serialization :send + alias read_attribute_for_serialization send attr_accessor :user_badges, :username, :grant_count diff --git a/app/models/user_custom_field.rb b/app/models/user_custom_field.rb index a4f757536e8..92255cc5648 100644 --- a/app/models/user_custom_field.rb +++ b/app/models/user_custom_field.rb @@ -3,7 +3,12 @@ class UserCustomField < ActiveRecord::Base belongs_to :user - scope :searchable, -> { joins("INNER JOIN user_fields ON user_fields.id = REPLACE(user_custom_fields.name, 'user_field_', '')::INTEGER AND user_fields.searchable IS TRUE AND user_custom_fields.name like 'user_field_%'") } + scope :searchable, + -> { + joins( + "INNER JOIN user_fields ON user_fields.id = REPLACE(user_custom_fields.name, 'user_field_', '')::INTEGER AND user_fields.searchable IS TRUE AND user_custom_fields.name like 'user_field_%'", + ) + } end # == Schema Information diff --git a/app/models/user_email.rb b/app/models/user_email.rb index 50ee27318b2..ff175a9bfd9 100644 --- a/app/models/user_email.rb +++ b/app/models/user_email.rb @@ -12,22 +12,23 @@ class UserEmail < ActiveRecord::Base validates :email, presence: true validates :email, email: true, if: :validate_email? - validates :primary, uniqueness: { scope: [:user_id] }, if: [:user_id, :primary] + validates :primary, uniqueness: { scope: [:user_id] }, if: %i[user_id primary] validate :user_id_not_changed, if: :primary validate :unique_email, if: :validate_unique_email? scope :secondary, -> { where(primary: false) } - before_save ->() { destroy_email_tokens(self.email_was) }, if: :will_save_change_to_email? + before_save -> { destroy_email_tokens(self.email_was) }, if: :will_save_change_to_email? after_destroy { destroy_email_tokens(self.email) } def normalize_email - self.normalized_email = if self.email.present? - username, domain = self.email.split('@', 2) - username = username.gsub('.', '').gsub(/\+.*/, '') - "#{username}@#{domain}" - end + self.normalized_email = + if self.email.present? + username, domain = self.email.split("@", 2) + username = username.gsub(".", "").gsub(/\+.*/, "") + "#{username}@#{domain}" + end end private @@ -50,19 +51,26 @@ class UserEmail < ActiveRecord::Base end def unique_email - email_exists = if SiteSetting.normalize_emails? - self.class.where("lower(email) = ? OR lower(normalized_email) = ?", email, normalized_email).exists? - else - self.class.where("lower(email) = ?", email).exists? - end + email_exists = + if SiteSetting.normalize_emails? + self + .class + .where("lower(email) = ? OR lower(normalized_email) = ?", email, normalized_email) + .exists? + else + self.class.where("lower(email) = ?", email).exists? + end self.errors.add(:email, :taken) if email_exists end def user_id_not_changed if self.will_save_change_to_user_id? && self.persisted? - self.errors.add(:user_id, I18n.t( - 'active_record.errors.model.user_email.attributes.user_id.reassigning_primary_email') + self.errors.add( + :user_id, + I18n.t( + "active_record.errors.model.user_email.attributes.user_id.reassigning_primary_email", + ), ) end end diff --git a/app/models/user_export.rb b/app/models/user_export.rb index 35059147250..32fcca5f75a 100644 --- a/app/models/user_export.rb +++ b/app/models/user_export.rb @@ -16,22 +16,31 @@ class UserExport < ActiveRecord::Base DESTROY_CREATED_BEFORE = 2.days.ago def self.remove_old_exports - UserExport.where('created_at < ?', DESTROY_CREATED_BEFORE).find_each do |user_export| - UserExport.transaction do - begin - Post.where(topic_id: user_export.topic_id).find_each { |p| p.destroy! } - user_export.destroy! - rescue => e - Rails.logger.warn("Failed to remove user_export record with id #{user_export.id}: #{e.message}\n#{e.backtrace.join("\n")}") + UserExport + .where("created_at < ?", DESTROY_CREATED_BEFORE) + .find_each do |user_export| + UserExport.transaction do + begin + Post.where(topic_id: user_export.topic_id).find_each { |p| p.destroy! } + user_export.destroy! + rescue => e + Rails.logger.warn( + "Failed to remove user_export record with id #{user_export.id}: #{e.message}\n#{e.backtrace.join("\n")}", + ) + end end end - end end def self.base_directory - File.join(Rails.root, "public", "uploads", "csv_exports", RailsMultisite::ConnectionManagement.current_db) + File.join( + Rails.root, + "public", + "uploads", + "csv_exports", + RailsMultisite::ConnectionManagement.current_db, + ) end - end # == Schema Information diff --git a/app/models/user_field.rb b/app/models/user_field.rb index a872af740f5..3d492ab28ed 100644 --- a/app/models/user_field.rb +++ b/app/models/user_field.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserField < ActiveRecord::Base - include AnonCacheInvalidator include HasSanitizableFields @@ -21,14 +20,16 @@ class UserField < ActiveRecord::Base end def queue_index_search - SearchIndexer.queue_users_reindex(UserCustomField.where(name: "user_field_#{self.id}").pluck(:user_id)) + SearchIndexer.queue_users_reindex( + UserCustomField.where(name: "user_field_#{self.id}").pluck(:user_id), + ) end private def sanitize_description if description_changed? - self.description = sanitize_field(self.description, additional_attributes: ['target']) + self.description = sanitize_field(self.description, additional_attributes: ["target"]) end end end diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 1ddde529e94..e3a98fe9b27 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -4,8 +4,8 @@ # like deleting users, changing site settings, dismissing notifications, etc. # Use other classes, like StaffActionLogger, to log records to this table. class UserHistory < ActiveRecord::Base - belongs_to :acting_user, class_name: 'User' - belongs_to :target_user, class_name: 'User' + belongs_to :acting_user, class_name: "User" + belongs_to :target_user, class_name: "User" belongs_to :post belongs_to :topic @@ -18,200 +18,201 @@ class UserHistory < ActiveRecord::Base before_save :set_admin_only def self.actions - @actions ||= Enum.new( - delete_user: 1, - change_trust_level: 2, - change_site_setting: 3, - change_theme: 4, - delete_theme: 5, - checked_for_custom_avatar: 6, # not used anymore - notified_about_avatar: 7, - notified_about_sequential_replies: 8, - notified_about_dominating_topic: 9, - suspend_user: 10, - unsuspend_user: 11, - facebook_no_email: 12, # not used anymore - grant_badge: 13, - revoke_badge: 14, - auto_trust_level_change: 15, - check_email: 16, - delete_post: 17, - delete_topic: 18, - impersonate: 19, - roll_up: 20, - change_username: 21, - custom: 22, - custom_staff: 23, - anonymize_user: 24, - reviewed_post: 25, - change_category_settings: 26, - delete_category: 27, - create_category: 28, - change_site_text: 29, - silence_user: 30, - unsilence_user: 31, - grant_admin: 32, - revoke_admin: 33, - grant_moderation: 34, - revoke_moderation: 35, - backup_create: 36, - rate_limited_like: 37, # not used anymore - revoke_email: 38, - deactivate_user: 39, - wizard_step: 40, - lock_trust_level: 41, - unlock_trust_level: 42, - activate_user: 43, - change_readonly_mode: 44, - backup_download: 45, - backup_destroy: 46, - notified_about_get_a_room: 47, - change_name: 48, - post_locked: 49, - post_unlocked: 50, - check_personal_message: 51, - disabled_second_factor: 52, - post_edit: 53, - topic_published: 54, - recover_topic: 55, - post_approved: 56, - create_badge: 57, - change_badge: 58, - delete_badge: 59, - removed_silence_user: 60, - removed_suspend_user: 61, - removed_unsilence_user: 62, - removed_unsuspend_user: 63, - post_rejected: 64, - merge_user: 65, - entity_export: 66, - change_password: 67, - topic_timestamps_changed: 68, - approve_user: 69, - web_hook_create: 70, - web_hook_update: 71, - web_hook_destroy: 72, - embeddable_host_create: 73, - embeddable_host_update: 74, - embeddable_host_destroy: 75, - web_hook_deactivate: 76, - change_theme_setting: 77, - disable_theme_component: 78, - enable_theme_component: 79, - api_key_create: 80, - api_key_update: 81, - api_key_destroy: 82, - revoke_title: 83, - change_title: 84, - override_upload_secure_status: 85, - page_published: 86, - page_unpublished: 87, - add_email: 88, - update_email: 89, - destroy_email: 90, - topic_closed: 91, - topic_opened: 92, - topic_archived: 93, - topic_unarchived: 94, - post_staff_note_create: 95, - post_staff_note_destroy: 96, - watched_word_create: 97, - watched_word_destroy: 98, - delete_group: 99 - ) + @actions ||= + Enum.new( + delete_user: 1, + change_trust_level: 2, + change_site_setting: 3, + change_theme: 4, + delete_theme: 5, + checked_for_custom_avatar: 6, # not used anymore + notified_about_avatar: 7, + notified_about_sequential_replies: 8, + notified_about_dominating_topic: 9, + suspend_user: 10, + unsuspend_user: 11, + facebook_no_email: 12, # not used anymore + grant_badge: 13, + revoke_badge: 14, + auto_trust_level_change: 15, + check_email: 16, + delete_post: 17, + delete_topic: 18, + impersonate: 19, + roll_up: 20, + change_username: 21, + custom: 22, + custom_staff: 23, + anonymize_user: 24, + reviewed_post: 25, + change_category_settings: 26, + delete_category: 27, + create_category: 28, + change_site_text: 29, + silence_user: 30, + unsilence_user: 31, + grant_admin: 32, + revoke_admin: 33, + grant_moderation: 34, + revoke_moderation: 35, + backup_create: 36, + rate_limited_like: 37, # not used anymore + revoke_email: 38, + deactivate_user: 39, + wizard_step: 40, + lock_trust_level: 41, + unlock_trust_level: 42, + activate_user: 43, + change_readonly_mode: 44, + backup_download: 45, + backup_destroy: 46, + notified_about_get_a_room: 47, + change_name: 48, + post_locked: 49, + post_unlocked: 50, + check_personal_message: 51, + disabled_second_factor: 52, + post_edit: 53, + topic_published: 54, + recover_topic: 55, + post_approved: 56, + create_badge: 57, + change_badge: 58, + delete_badge: 59, + removed_silence_user: 60, + removed_suspend_user: 61, + removed_unsilence_user: 62, + removed_unsuspend_user: 63, + post_rejected: 64, + merge_user: 65, + entity_export: 66, + change_password: 67, + topic_timestamps_changed: 68, + approve_user: 69, + web_hook_create: 70, + web_hook_update: 71, + web_hook_destroy: 72, + embeddable_host_create: 73, + embeddable_host_update: 74, + embeddable_host_destroy: 75, + web_hook_deactivate: 76, + change_theme_setting: 77, + disable_theme_component: 78, + enable_theme_component: 79, + api_key_create: 80, + api_key_update: 81, + api_key_destroy: 82, + revoke_title: 83, + change_title: 84, + override_upload_secure_status: 85, + page_published: 86, + page_unpublished: 87, + add_email: 88, + update_email: 89, + destroy_email: 90, + topic_closed: 91, + topic_opened: 92, + topic_archived: 93, + topic_unarchived: 94, + post_staff_note_create: 95, + post_staff_note_destroy: 96, + watched_word_create: 97, + watched_word_destroy: 98, + delete_group: 99, + ) end # Staff actions is a subset of all actions, used to audit actions taken by staff users. def self.staff_actions - @staff_actions ||= [ - :delete_user, - :change_trust_level, - :change_site_setting, - :change_theme, - :delete_theme, - :change_site_text, - :suspend_user, - :unsuspend_user, - :removed_suspend_user, - :removed_unsuspend_user, - :grant_badge, - :revoke_badge, - :check_email, - :delete_post, - :delete_topic, - :impersonate, - :roll_up, - :change_username, - :custom_staff, - :anonymize_user, - :reviewed_post, - :change_category_settings, - :delete_category, - :create_category, - :silence_user, - :unsilence_user, - :removed_silence_user, - :removed_unsilence_user, - :grant_admin, - :revoke_admin, - :grant_moderation, - :revoke_moderation, - :backup_create, - :revoke_email, - :deactivate_user, - :lock_trust_level, - :unlock_trust_level, - :activate_user, - :change_readonly_mode, - :backup_download, - :backup_destroy, - :post_locked, - :post_unlocked, - :check_personal_message, - :disabled_second_factor, - :post_edit, - :topic_published, - :recover_topic, - :post_approved, - :create_badge, - :change_badge, - :delete_badge, - :post_rejected, - :merge_user, - :entity_export, - :change_name, - :topic_timestamps_changed, - :approve_user, - :web_hook_create, - :web_hook_update, - :web_hook_destroy, - :web_hook_deactivate, - :embeddable_host_create, - :embeddable_host_update, - :embeddable_host_destroy, - :change_theme_setting, - :disable_theme_component, - :enable_theme_component, - :revoke_title, - :change_title, - :api_key_create, - :api_key_update, - :api_key_destroy, - :override_upload_secure_status, - :page_published, - :page_unpublished, - :add_email, - :update_email, - :destroy_email, - :topic_closed, - :topic_opened, - :topic_archived, - :topic_unarchived, - :post_staff_note_create, - :post_staff_note_destroy, - :watched_word_create, - :watched_word_destroy, - :delete_group + @staff_actions ||= %i[ + delete_user + change_trust_level + change_site_setting + change_theme + delete_theme + change_site_text + suspend_user + unsuspend_user + removed_suspend_user + removed_unsuspend_user + grant_badge + revoke_badge + check_email + delete_post + delete_topic + impersonate + roll_up + change_username + custom_staff + anonymize_user + reviewed_post + change_category_settings + delete_category + create_category + silence_user + unsilence_user + removed_silence_user + removed_unsilence_user + grant_admin + revoke_admin + grant_moderation + revoke_moderation + backup_create + revoke_email + deactivate_user + lock_trust_level + unlock_trust_level + activate_user + change_readonly_mode + backup_download + backup_destroy + post_locked + post_unlocked + check_personal_message + disabled_second_factor + post_edit + topic_published + recover_topic + post_approved + create_badge + change_badge + delete_badge + post_rejected + merge_user + entity_export + change_name + topic_timestamps_changed + approve_user + web_hook_create + web_hook_update + web_hook_destroy + web_hook_deactivate + embeddable_host_create + embeddable_host_update + embeddable_host_destroy + change_theme_setting + disable_theme_component + enable_theme_component + revoke_title + change_title + api_key_create + api_key_update + api_key_destroy + override_upload_secure_status + page_published + page_unpublished + add_email + update_email + destroy_email + topic_closed + topic_opened + topic_archived + topic_unarchived + post_staff_note_create + post_staff_note_destroy + watched_word_create + watched_word_destroy + delete_group ] end @@ -228,7 +229,7 @@ class UserHistory < ActiveRecord::Base query = query.where(action: filters[:action_id]) if filters[:action_id].present? query = query.where(custom_type: filters[:custom_type]) if filters[:custom_type].present? - [:acting_user, :target_user].each do |key| + %i[acting_user target_user].each do |key| if filters[key] && (obj_id = User.where(username_lower: filters[key].downcase).pluck(:id)) query = query.where("#{key}_id = ?", obj_id) end @@ -249,7 +250,7 @@ class UserHistory < ActiveRecord::Base end def self.staff_filters - [:action_id, :custom_type, :acting_user, :target_user, :subject, :action_name] + %i[action_id custom_type acting_user target_user subject action_name] end def self.staff_action_records(viewer, opts = nil) @@ -262,11 +263,12 @@ class UserHistory < ActiveRecord::Base opts[:action_id] = self.actions[opts[:action_name].to_sym] if opts[:action_name] end - query = self - .with_filters(opts.slice(*staff_filters)) - .only_staff_actions - .order('id DESC') - .includes(:acting_user, :target_user) + query = + self + .with_filters(opts.slice(*staff_filters)) + .only_staff_actions + .order("id DESC") + .includes(:acting_user, :target_user) query = query.where(admin_only: false) unless viewer && viewer.admin? query end diff --git a/app/models/user_notification_schedule.rb b/app/models/user_notification_schedule.rb index b80b4406a53..4a2bfac0fef 100644 --- a/app/models/user_notification_schedule.rb +++ b/app/models/user_notification_schedule.rb @@ -3,17 +3,17 @@ class UserNotificationSchedule < ActiveRecord::Base belongs_to :user - DEFAULT = -> { + DEFAULT = -> do attrs = { enabled: false } 7.times do |n| attrs["day_#{n}_start_time".to_sym] = 480 attrs["day_#{n}_end_time".to_sym] = 1020 end attrs - }.call + end.call validate :has_valid_times - validates :enabled, inclusion: { in: [ true, false ] } + validates :enabled, inclusion: { in: [true, false] } scope :enabled, -> { where(enabled: true) } @@ -37,9 +37,7 @@ class UserNotificationSchedule < ActiveRecord::Base errors.add(start_key, "is invalid") end - if self[end_key].nil? || self[end_key] > 1440 - errors.add(end_key, "is invalid") - end + errors.add(end_key, "is invalid") if self[end_key].nil? || self[end_key] > 1440 if self[start_key] && self[end_key] && self[start_key] > self[end_key] errors.add(start_key, "is after end time") diff --git a/app/models/user_option.rb b/app/models/user_option.rb index 1bf0062d202..70d80d80522 100644 --- a/app/models/user_option.rb +++ b/app/models/user_option.rb @@ -11,10 +11,15 @@ class UserOption < ActiveRecord::Base after_save :update_tracked_topics - scope :human_users, -> { where('user_id > 0') } + scope :human_users, -> { where("user_id > 0") } enum default_calendar: { none_selected: 0, ics: 1, google: 2 }, _scopes: false - enum sidebar_list_destination: { none_selected: 0, default: 0, unread_new: 1 }, _prefix: "sidebar_list" + enum sidebar_list_destination: { + none_selected: 0, + default: 0, + unread_new: 1, + }, + _prefix: "sidebar_list" def self.ensure_consistency! sql = <<~SQL @@ -23,9 +28,7 @@ class UserOption < ActiveRecord::Base WHERE o.user_id IS NULL SQL - DB.query_single(sql).each do |id| - UserOption.create(user_id: id) - end + DB.query_single(sql).each { |id| UserOption.create(user_id: id) } end def self.previous_replies_type @@ -33,7 +36,8 @@ class UserOption < ActiveRecord::Base end def self.like_notification_frequency_type - @like_notification_frequency_type ||= Enum.new(always: 0, first_time_and_daily: 1, first_time: 2, never: 3) + @like_notification_frequency_type ||= + Enum.new(always: 0, first_time_and_daily: 1, first_time: 2, never: 3) end def self.text_sizes @@ -70,7 +74,8 @@ class UserOption < ActiveRecord::Base self.new_topic_duration_minutes = SiteSetting.default_other_new_topic_duration_minutes self.auto_track_topics_after_msecs = SiteSetting.default_other_auto_track_topics_after_msecs - self.notification_level_when_replying = SiteSetting.default_other_notification_level_when_replying + self.notification_level_when_replying = + SiteSetting.default_other_notification_level_when_replying self.like_notification_frequency = SiteSetting.default_other_like_notification_frequency @@ -111,7 +116,12 @@ class UserOption < ActiveRecord::Base Discourse.redis.expire(key, delay) # delay the update - Jobs.enqueue_in(delay / 2, :update_top_redirection, user_id: self.user_id, redirected_at: Time.zone.now.to_s) + Jobs.enqueue_in( + delay / 2, + :update_top_redirection, + user_id: self.user_id, + redirected_at: Time.zone.now.to_s, + ) end def should_be_redirected_to_top @@ -133,16 +143,10 @@ class UserOption < ActiveRecord::Base if !user.seen_before? || (user.trust_level == 0 && !redirected_to_top_yet?) update_last_redirected_to_top! - return { - reason: I18n.t('redirected_to_top_reasons.new_user'), - period: period - } + return { reason: I18n.t("redirected_to_top_reasons.new_user"), period: period } elsif user.last_seen_at < 1.month.ago update_last_redirected_to_top! - return { - reason: I18n.t('redirected_to_top_reasons.not_seen_in_a_month'), - period: period - } + return { reason: I18n.t("redirected_to_top_reasons.not_seen_in_a_month"), period: period } end # don't redirect to top @@ -150,7 +154,8 @@ class UserOption < ActiveRecord::Base end def treat_as_new_topic_start_date - duration = new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i + duration = + new_topic_duration_minutes || SiteSetting.default_other_new_topic_duration_minutes.to_i times = [ case duration when User::NewTopicDuration::ALWAYS @@ -161,7 +166,7 @@ class UserOption < ActiveRecord::Base duration.minutes.ago end, user.created_at, - Time.at(SiteSetting.min_new_topics_time).to_datetime + Time.at(SiteSetting.min_new_topics_time).to_datetime, ] times.max @@ -169,14 +174,22 @@ class UserOption < ActiveRecord::Base def homepage case homepage_id - when 1 then "latest" - when 2 then "categories" - when 3 then "unread" - when 4 then "new" - when 5 then "top" - when 6 then "bookmarks" - when 7 then "unseen" - else SiteSetting.homepage + when 1 + "latest" + when 2 + "categories" + when 3 + "unread" + when 4 + "new" + when 5 + "top" + when 6 + "bookmarks" + when 7 + "unseen" + else + SiteSetting.homepage end end @@ -197,9 +210,7 @@ class UserOption < ActiveRecord::Base end def unsubscribed_from_all? - !mailing_list_mode && - !email_digests && - email_level == UserOption.email_level_types[:never] && + !mailing_list_mode && !email_digests && email_level == UserOption.email_level_types[:never] && email_messages_level == UserOption.email_level_types[:never] end @@ -208,14 +219,16 @@ class UserOption < ActiveRecord::Base end def self.user_tzinfo(user_id) - timezone = UserOption.where(user_id: user_id).pluck(:timezone).first || 'UTC' + timezone = UserOption.where(user_id: user_id).pluck(:timezone).first || "UTC" tzinfo = nil begin tzinfo = ActiveSupport::TimeZone.find_tzinfo(timezone) rescue TZInfo::InvalidTimezoneIdentifier - Rails.logger.warn("#{User.find_by(id: user_id)&.username} has the timezone #{timezone} set, we do not know how to parse it in Rails, fallback to UTC") - tzinfo = ActiveSupport::TimeZone.find_tzinfo('UTC') + Rails.logger.warn( + "#{User.find_by(id: user_id)&.username} has the timezone #{timezone} set, we do not know how to parse it in Rails, fallback to UTC", + ) + tzinfo = ActiveSupport::TimeZone.find_tzinfo("UTC") end tzinfo @@ -227,7 +240,6 @@ class UserOption < ActiveRecord::Base return unless saved_change_to_auto_track_topics_after_msecs? TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call end - end # == Schema Information diff --git a/app/models/user_profile.rb b/app/models/user_profile.rb index e3136c65261..2ed6d1b0279 100644 --- a/app/models/user_profile.rb +++ b/app/models/user_profile.rb @@ -7,13 +7,19 @@ class UserProfile < ActiveRecord::Base belongs_to :card_background_upload, class_name: "Upload" belongs_to :profile_background_upload, class_name: "Upload" belongs_to :granted_title_badge, class_name: "Badge" - belongs_to :featured_topic, class_name: 'Topic' + belongs_to :featured_topic, class_name: "Topic" has_many :upload_references, as: :target, dependent: :destroy has_many :user_profile_views, dependent: :destroy validates :bio_raw, length: { maximum: 3000 }, watched_words: true - validates :website, url: true, length: { maximum: 3000 }, allow_blank: true, if: :validate_website? + validates :website, + url: true, + length: { + maximum: 3000, + }, + allow_blank: true, + if: :validate_website? validates :location, length: { maximum: 3000 }, watched_words: true validates :user, presence: true @@ -26,8 +32,11 @@ class UserProfile < ActiveRecord::Base after_save :pull_hotlinked_image after_save do - if saved_change_to_profile_background_upload_id? || saved_change_to_card_background_upload_id? || saved_change_to_bio_raw? - upload_ids = [self.profile_background_upload_id, self.card_background_upload_id] + Upload.extract_upload_ids(self.bio_raw) + if saved_change_to_profile_background_upload_id? || + saved_change_to_card_background_upload_id? || saved_change_to_bio_raw? + upload_ids = + [self.profile_background_upload_id, self.card_background_upload_id] + + Upload.extract_upload_ids(self.bio_raw) UploadReference.ensure_exist!(upload_ids: upload_ids, target: self) end end @@ -36,13 +45,15 @@ class UserProfile < ActiveRecord::Base def bio_excerpt(length = 350, opts = {}) return nil if bio_cooked.blank? - excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/
    $/, '') + excerpt = PrettyText.excerpt(bio_cooked, length, opts).sub(/
    $/, "") return excerpt if excerpt.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?) PrettyText.strip_links(excerpt) end def bio_processed - return bio_cooked if bio_cooked.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?) + if bio_cooked.blank? || (user.has_trust_level?(TrustLevel[1]) && !user.suspended?) + return bio_cooked + end PrettyText.strip_links(bio_cooked) end @@ -73,14 +84,16 @@ class UserProfile < ActiveRecord::Base def self.rebake_old(limit) problems = [] - UserProfile.where('bio_cooked_version IS NULL OR bio_cooked_version < ?', BAKED_VERSION) - .limit(limit).each do |p| - begin - p.rebake! - rescue => e - problems << { profile: p, ex: e } + UserProfile + .where("bio_cooked_version IS NULL OR bio_cooked_version < ?", BAKED_VERSION) + .limit(limit) + .each do |p| + begin + p.rebake! + rescue => e + problems << { profile: p, ex: e } + end end - end problems end @@ -90,15 +103,18 @@ class UserProfile < ActiveRecord::Base def self.import_url_for_user(background_url, user, options = nil) if SiteSetting.verbose_upload_logging - Rails.logger.warn("Verbose Upload Logging: Downloading profile background from #{background_url}") + Rails.logger.warn( + "Verbose Upload Logging: Downloading profile background from #{background_url}", + ) end - tempfile = FileHelper.download( - background_url, - max_file_size: SiteSetting.max_image_size_kb.kilobytes, - tmp_file_name: "sso-profile-background", - follow_redirect: true - ) + tempfile = + FileHelper.download( + background_url, + max_file_size: SiteSetting.max_image_size_kb.kilobytes, + tmp_file_name: "sso-profile-background", + follow_redirect: true, + ) return unless tempfile @@ -108,14 +124,19 @@ class UserProfile < ActiveRecord::Base is_card_background = !options || options[:is_card_background] type = is_card_background ? "card_background" : "profile_background" - upload = UploadCreator.new(tempfile, "external-profile-background." + ext, origin: background_url, type: type).create_for(user.id) + upload = + UploadCreator.new( + tempfile, + "external-profile-background." + ext, + origin: background_url, + type: type, + ).create_for(user.id) if (is_card_background) user.user_profile.upload_card_background(upload) else user.user_profile.upload_profile_background(upload) end - rescue Net::ReadTimeout, OpenURI::HTTPError # skip saving, we are not connected to the net ensure @@ -133,7 +154,7 @@ class UserProfile < ActiveRecord::Base Jobs.enqueue_in( SiteSetting.editing_grace_period, :pull_user_profile_hotlinked_images, - user_id: self.user_id + user_id: self.user_id, ) end end @@ -146,7 +167,10 @@ class UserProfile < ActiveRecord::Base def cooked if self.bio_raw.present? - PrettyText.cook(self.bio_raw, omit_nofollow: user.has_trust_level?(TrustLevel[3]) && !SiteSetting.tl3_links_no_follow) + PrettyText.cook( + self.bio_raw, + omit_nofollow: user.has_trust_level?(TrustLevel[3]) && !SiteSetting.tl3_links_no_follow, + ) else nil end @@ -171,11 +195,20 @@ class UserProfile < ActiveRecord::Base allowed_domains = SiteSetting.allowed_user_website_domains return if (allowed_domains.blank? || self.website.blank?) - domain = begin - URI.parse(self.website).host - rescue URI::Error + domain = + begin + URI.parse(self.website).host + rescue URI::Error + end + unless allowed_domains.split("|").include?(domain) + self.errors.add :base, + ( + I18n.t( + "user.website.domain_not_allowed", + domains: allowed_domains.split("|").join(", "), + ) + ) end - self.errors.add :base, (I18n.t('user.website.domain_not_allowed', domains: allowed_domains.split('|').join(", "))) unless allowed_domains.split('|').include?(domain) end def validate_website? diff --git a/app/models/user_profile_view.rb b/app/models/user_profile_view.rb index 5bc6acbe145..0648b75eb0b 100644 --- a/app/models/user_profile_view.rb +++ b/app/models/user_profile_view.rb @@ -16,11 +16,13 @@ class UserProfileView < ActiveRecord::Base redis_key << ":ip-#{ip}" end - if skip_redis || Discourse.redis.setnx(redis_key, '1') - skip_redis || Discourse.redis.expire(redis_key, SiteSetting.user_profile_view_duration_hours.hours) + if skip_redis || Discourse.redis.setnx(redis_key, "1") + skip_redis || + Discourse.redis.expire(redis_key, SiteSetting.user_profile_view_duration_hours.hours) self.transaction do - sql = "INSERT INTO user_profile_views (user_profile_id, ip_address, viewed_at, user_id) + sql = + "INSERT INTO user_profile_views (user_profile_id, ip_address, viewed_at, user_id) SELECT :user_profile_id, :ip_address, :viewed_at, :user_id WHERE NOT EXISTS ( SELECT 1 FROM user_profile_views @@ -30,16 +32,24 @@ class UserProfileView < ActiveRecord::Base builder = DB.build(sql) if !user_id - builder.where("viewed_at = :viewed_at AND ip_address = :ip_address AND user_profile_id = :user_profile_id AND user_id IS NULL") + builder.where( + "viewed_at = :viewed_at AND ip_address = :ip_address AND user_profile_id = :user_profile_id AND user_id IS NULL", + ) else - builder.where("viewed_at = :viewed_at AND user_id = :user_id AND user_profile_id = :user_profile_id") + builder.where( + "viewed_at = :viewed_at AND user_id = :user_id AND user_profile_id = :user_profile_id", + ) end - result = builder.exec(user_profile_id: user_profile_id, ip_address: ip, viewed_at: at, user_id: user_id) + result = + builder.exec( + user_profile_id: user_profile_id, + ip_address: ip, + viewed_at: at, + user_id: user_id, + ) - if result > 0 - UserProfile.find(user_profile_id).increment!(:views) - end + UserProfile.find(user_profile_id).increment!(:views) if result > 0 end end end @@ -47,8 +57,10 @@ class UserProfileView < ActiveRecord::Base def self.profile_views_by_day(start_date, end_date, group_id = nil) profile_views = self.where("viewed_at >= ? AND viewed_at < ?", start_date, end_date + 1.day) if group_id - profile_views = profile_views.joins("INNER JOIN users ON users.id = user_profile_views.user_id") - profile_views = profile_views.joins("INNER JOIN group_users ON group_users.user_id = users.id") + profile_views = + profile_views.joins("INNER JOIN users ON users.id = user_profile_views.user_id") + profile_views = + profile_views.joins("INNER JOIN group_users ON group_users.user_id = users.id") profile_views = profile_views.where("group_users.group_id = ?", group_id) end profile_views.group("date(viewed_at)").order("date(viewed_at)").count diff --git a/app/models/user_search.rb b/app/models/user_search.rb index baa25dfffac..7b21ff6b7fe 100644 --- a/app/models/user_search.rb +++ b/app/models/user_search.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserSearch - MAX_SIZE_PRIORITY_MENTION ||= 500 def initialize(term, opts = {}) @@ -31,17 +30,16 @@ class UserSearch users = users.not_suspended unless @searching_user&.staff? if @groups - users = users - .joins(:group_users) - .where("group_users.group_id IN (?)", @groups.map(&:id)) + users = users.joins(:group_users).where("group_users.group_id IN (?)", @groups.map(&:id)) end # Only show users who have access to private topic if @topic_allowed_users == "true" && @topic&.category&.read_restricted - users = users - .references(:categories) - .includes(:secure_categories) - .where("users.admin OR categories.id = ?", @topic.category_id) + users = + users + .references(:categories) + .includes(:secure_categories) + .where("users.admin OR categories.id = ?", @topic.category_id) end users @@ -70,27 +68,27 @@ class UserSearch exact_matches = scoped_users.where(username_lower: @term) # don't pollute mentions with users who haven't shown up in over a year - exact_matches = exact_matches.where('last_seen_at > ?', 1.year.ago) if @topic_id || @category_id + exact_matches = exact_matches.where("last_seen_at > ?", 1.year.ago) if @topic_id || + @category_id - exact_matches - .limit(@limit) - .pluck(:id) - .each { |id| users << id } + exact_matches.limit(@limit).pluck(:id).each { |id| users << id } end return users.to_a if users.size >= @limit # 2. in topic if @topic_id - in_topic = filtered_by_term_users - .where('users.id IN (SELECT user_id FROM posts WHERE topic_id = ? AND post_type = ? AND deleted_at IS NULL)', @topic_id, Post.types[:regular]) + in_topic = + filtered_by_term_users.where( + "users.id IN (SELECT user_id FROM posts WHERE topic_id = ? AND post_type = ? AND deleted_at IS NULL)", + @topic_id, + Post.types[:regular], + ) - if @searching_user.present? - in_topic = in_topic.where('users.id <> ?', @searching_user.id) - end + in_topic = in_topic.where("users.id <> ?", @searching_user.id) if @searching_user.present? in_topic - .order('last_seen_at DESC NULLS LAST') + .order("last_seen_at DESC NULLS LAST") .limit(@limit - users.size) .pluck(:id) .each { |id| users << id } @@ -131,8 +129,7 @@ class UserSearch category_groups = category_groups.members_visible_groups(@searching_user) end - in_category = filtered_by_term_users - .where(<<~SQL, category_groups.pluck(:id)) + in_category = filtered_by_term_users.where(<<~SQL, category_groups.pluck(:id)) users.id IN ( SELECT gu.user_id FROM group_users gu @@ -142,11 +139,11 @@ class UserSearch SQL if @searching_user.present? - in_category = in_category.where('users.id <> ?', @searching_user.id) + in_category = in_category.where("users.id <> ?", @searching_user.id) end in_category - .order('last_seen_at DESC NULLS LAST') + .order("last_seen_at DESC NULLS LAST") .limit(@limit - users.size) .pluck(:id) .each { |id| users << id } @@ -157,7 +154,7 @@ class UserSearch # 4. global matches if @term.present? filtered_by_term_users - .order('last_seen_at DESC NULLS LAST') + .order("last_seen_at DESC NULLS LAST") .limit(@limit - users.size) .pluck(:id) .each { |id| users << id } @@ -166,7 +163,7 @@ class UserSearch # 5. last seen users (for search auto-suggestions) if @last_seen_users scoped_users - .order('last_seen_at DESC NULLS LAST') + .order("last_seen_at DESC NULLS LAST") .limit(@limit - users.size) .pluck(:id) .each { |id| users << id } @@ -179,16 +176,15 @@ class UserSearch ids = search_ids return User.where("0=1") if ids.empty? - results = User.joins("JOIN (SELECT unnest uid, row_number() OVER () AS rn + results = + User.joins( + "JOIN (SELECT unnest uid, row_number() OVER () AS rn FROM unnest('{#{ids.join(",")}}'::int[]) - ) x on uid = users.id") - .order("rn") + ) x on uid = users.id", + ).order("rn") - if SiteSetting.enable_user_status - results = results.includes(:user_status) - end + results = results.includes(:user_status) if SiteSetting.enable_user_status results end - end diff --git a/app/models/user_second_factor.rb b/app/models/user_second_factor.rb index 7846f27ad86..3c71c6b65e7 100644 --- a/app/models/user_second_factor.rb +++ b/app/models/user_second_factor.rb @@ -4,24 +4,14 @@ class UserSecondFactor < ActiveRecord::Base include SecondFactorManager belongs_to :user - scope :backup_codes, -> do - where(method: UserSecondFactor.methods[:backup_codes], enabled: true) - end + scope :backup_codes, -> { where(method: UserSecondFactor.methods[:backup_codes], enabled: true) } - scope :totps, -> do - where(method: UserSecondFactor.methods[:totp], enabled: true) - end + scope :totps, -> { where(method: UserSecondFactor.methods[:totp], enabled: true) } - scope :all_totps, -> do - where(method: UserSecondFactor.methods[:totp]) - end + scope :all_totps, -> { where(method: UserSecondFactor.methods[:totp]) } def self.methods - @methods ||= Enum.new( - totp: 1, - backup_codes: 2, - security_key: 3, - ) + @methods ||= Enum.new(totp: 1, backup_codes: 2, security_key: 3) end def totp_object @@ -31,7 +21,6 @@ class UserSecondFactor < ActiveRecord::Base def totp_provisioning_uri totp_object.provisioning_uri(user.email) end - end # == Schema Information diff --git a/app/models/user_security_key.rb b/app/models/user_security_key.rb index 229dbbdf968..5447ee23adc 100644 --- a/app/models/user_security_key.rb +++ b/app/models/user_security_key.rb @@ -3,16 +3,11 @@ class UserSecurityKey < ActiveRecord::Base belongs_to :user - scope :second_factors, -> do - where(factor_type: UserSecurityKey.factor_types[:second_factor], enabled: true) - end + scope :second_factors, + -> { where(factor_type: UserSecurityKey.factor_types[:second_factor], enabled: true) } def self.factor_types - @factor_types ||= Enum.new( - second_factor: 0, - first_factor: 1, - multi_factor: 2, - ) + @factor_types ||= Enum.new(second_factor: 0, first_factor: 1, multi_factor: 2) end end diff --git a/app/models/user_stat.rb b/app/models/user_stat.rb index e6a86c91152..894a888c1f4 100644 --- a/app/models/user_stat.rb +++ b/app/models/user_stat.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true class UserStat < ActiveRecord::Base - belongs_to :user after_save :trigger_badges @@ -21,7 +20,8 @@ class UserStat < ActiveRecord::Base def self.update_first_unread_pm(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) whisperers_group_ids = SiteSetting.whispers_allowed_group_ids - DB.exec(<<~SQL, archetype: Archetype.private_message, now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, last_seen: last_seen, limit: limit, whisperers_group_ids: whisperers_group_ids) + DB.exec( + <<~SQL, UPDATE user_stats us SET first_unread_pm_at = COALESCE(Z.min_date, :now) FROM ( @@ -37,11 +37,11 @@ class UserStat < ActiveRecord::Base INNER JOIN topics t ON t.id = tau.topic_id INNER JOIN users u ON u.id = tau.user_id LEFT JOIN topic_users tu ON t.id = tu.topic_id AND tu.user_id = tau.user_id - #{whisperers_group_ids.present? ? 'LEFT JOIN group_users gu ON gu.group_id IN (:whisperers_group_ids) AND gu.user_id = u.id' : ''} + #{whisperers_group_ids.present? ? "LEFT JOIN group_users gu ON gu.group_id IN (:whisperers_group_ids) AND gu.user_id = u.id" : ""} WHERE t.deleted_at IS NULL AND t.archetype = :archetype AND tu.last_read_post_number < CASE - WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? 'OR gu.id IS NOT NULL' : ''} + WHEN u.admin OR u.moderator #{whisperers_group_ids.present? ? "OR gu.id IS NOT NULL" : ""} THEN t.highest_staff_post_number ELSE t.highest_post_number END @@ -67,6 +67,12 @@ class UserStat < ActiveRecord::Base ) AS Z WHERE us.user_id = Z.user_id SQL + archetype: Archetype.private_message, + now: UPDATE_UNREAD_MINUTES_AGO.minutes.ago, + last_seen: last_seen, + limit: limit, + whisperers_group_ids: whisperers_group_ids, + ) end def self.update_first_unread(last_seen, limit: UPDATE_UNREAD_USERS_LIMIT) @@ -140,14 +146,14 @@ class UserStat < ActiveRecord::Base end def self.reset_bounce_scores - UserStat.where("reset_bounce_score_after < now()") + UserStat + .where("reset_bounce_score_after < now()") .where("bounce_score > 0") .update_all(bounce_score: 0) end # Updates the denormalized view counts for all users def self.update_view_counts(last_seen = 1.hour.ago) - # NOTE: we only update the counts for users we have seen in the last hour # this avoids a very expensive query that may run on the entire user base # we also ensure we only touch the table if data changes @@ -210,7 +216,8 @@ class UserStat < ActiveRecord::Base def self.update_draft_count(user_id = nil) if user_id.present? - draft_count, has_topic_draft = DB.query_single <<~SQL, user_id: user_id, new_topic: Draft::NEW_TOPIC + draft_count, has_topic_draft = + DB.query_single <<~SQL, user_id: user_id, new_topic: Draft::NEW_TOPIC UPDATE user_stats SET draft_count = (SELECT COUNT(*) FROM drafts WHERE user_id = :user_id) WHERE user_id = :user_id @@ -219,11 +226,8 @@ class UserStat < ActiveRecord::Base MessageBus.publish( "/user-drafts/#{user_id}", - { - draft_count: draft_count, - has_topic_draft: !!has_topic_draft - }, - user_ids: [user_id] + { draft_count: draft_count, has_topic_draft: !!has_topic_draft }, + user_ids: [user_id], ) else DB.exec <<~SQL @@ -249,7 +253,7 @@ class UserStat < ActiveRecord::Base AND topics.user_id <> posts.user_id AND posts.deleted_at IS NULL AND topics.deleted_at IS NULL AND topics.archetype <> 'private_message' - #{start_time.nil? ? '' : 'AND posts.created_at > ?'} + #{start_time.nil? ? "" : "AND posts.created_at > ?"} SQL if start_time.nil? DB.query_single(sql, self.user_id).first @@ -303,7 +307,7 @@ class UserStat < ActiveRecord::Base "/u/#{user.username_lower}/counters", { pending_posts_count: pending_posts_count }, user_ids: [user.id], - group_ids: [Group::AUTO_GROUPS[:staff]] + group_ids: [Group::AUTO_GROUPS[:staff]], ) end diff --git a/app/models/user_summary.rb b/app/models/user_summary.rb index f30507117d0..c5e4c93fee0 100644 --- a/app/models/user_summary.rb +++ b/app/models/user_summary.rb @@ -3,11 +3,10 @@ # ViewModel used on Summary tab on User page class UserSummary - MAX_SUMMARY_RESULTS = 6 MAX_BADGES = 6 - alias :read_attribute_for_serialization :send + alias read_attribute_for_serialization send def initialize(user, guardian) @user = user @@ -20,14 +19,14 @@ class UserSummary .listable_topics .visible .where(user: @user) - .order('like_count DESC, created_at DESC') + .order("like_count DESC, created_at DESC") .limit(MAX_SUMMARY_RESULTS) end def replies post_query - .where('post_number > 1') - .order('posts.like_count DESC, posts.created_at DESC') + .where("post_number > 1") + .order("posts.like_count DESC, posts.created_at DESC") .limit(MAX_SUMMARY_RESULTS) end @@ -36,11 +35,11 @@ class UserSummary .joins(:topic, :post) .where(posts: { user_id: @user.id }) .includes(:topic, :post) - .where('posts.post_type IN (?)', Topic.visible_post_types(@guardian && @guardian.user)) + .where("posts.post_type IN (?)", Topic.visible_post_types(@guardian && @guardian.user)) .merge(Topic.listable_topics.visible.secured(@guardian)) .where(user: @user) .where(internal: false, reflection: false, quote: false) - .order('clicks DESC, topic_links.created_at DESC') + .order("clicks DESC, topic_links.created_at DESC") .limit(MAX_SUMMARY_RESULTS) end @@ -50,14 +49,15 @@ class UserSummary def most_liked_by_users likers = {} - UserAction.joins(:target_topic, :target_post) + UserAction + .joins(:target_topic, :target_post) .merge(Topic.listable_topics.visible.secured(@guardian)) .where(user: @user) .where(action_type: UserAction::WAS_LIKED) .group(:acting_user_id) - .order('COUNT(*) DESC') + .order("COUNT(*) DESC") .limit(MAX_SUMMARY_RESULTS) - .pluck('acting_user_id, COUNT(*)') + .pluck("acting_user_id, COUNT(*)") .each { |l| likers[l[0]] = l[1] } user_counts(likers) @@ -65,14 +65,15 @@ class UserSummary def most_liked_users liked_users = {} - UserAction.joins(:target_topic, :target_post) + UserAction + .joins(:target_topic, :target_post) .merge(Topic.listable_topics.visible.secured(@guardian)) .where(action_type: UserAction::WAS_LIKED) .where(acting_user_id: @user.id) .group(:user_id) - .order('COUNT(*) DESC') + .order("COUNT(*) DESC") .limit(MAX_SUMMARY_RESULTS) - .pluck('user_actions.user_id, COUNT(*)') + .pluck("user_actions.user_id, COUNT(*)") .each { |l| liked_users[l[0]] = l[1] } user_counts(liked_users) @@ -84,12 +85,14 @@ class UserSummary replied_users = {} post_query - .joins('JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number') - .where('replies.user_id <> ?', @user.id) - .group('replies.user_id') - .order('COUNT(*) DESC') + .joins( + "JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number", + ) + .where("replies.user_id <> ?", @user.id) + .group("replies.user_id") + .order("COUNT(*) DESC") .limit(MAX_SUMMARY_RESULTS) - .pluck('replies.user_id, COUNT(*)') + .pluck("replies.user_id, COUNT(*)") .each { |r| replied_users[r[0]] = r[1] } user_counts(replied_users) @@ -121,44 +124,42 @@ class UserSummary class CategoryWithCounts < OpenStruct include ActiveModel::SerializerSupport - KEYS = [:id, :name, :color, :text_color, :slug, :read_restricted, :parent_category_id] + KEYS = %i[id name color text_color slug read_restricted parent_category_id] end def top_categories - post_count_query = post_query.group('topics.category_id') + post_count_query = post_query.group("topics.category_id") top_categories = {} - Category.where(id: post_count_query.order("count(*) DESC").limit(MAX_SUMMARY_RESULTS).pluck('category_id')) + Category + .where( + id: post_count_query.order("count(*) DESC").limit(MAX_SUMMARY_RESULTS).pluck("category_id"), + ) .pluck(:id, :name, :color, :text_color, :slug, :read_restricted, :parent_category_id) .each do |c| top_categories[c[0].to_i] = CategoryWithCounts.new( - Hash[CategoryWithCounts::KEYS.zip(c)].merge( - topic_count: 0, - post_count: 0 - ) + Hash[CategoryWithCounts::KEYS.zip(c)].merge(topic_count: 0, post_count: 0), ) end - post_count_query.where('post_number > 1') - .where('topics.category_id in (?)', top_categories.keys) - .pluck('category_id, COUNT(*)') - .each do |r| - top_categories[r[0].to_i].post_count = r[1] - end + post_count_query + .where("post_number > 1") + .where("topics.category_id in (?)", top_categories.keys) + .pluck("category_id, COUNT(*)") + .each { |r| top_categories[r[0].to_i].post_count = r[1] } - Topic.listable_topics.visible.secured(@guardian) - .where('topics.category_id in (?)', top_categories.keys) + Topic + .listable_topics + .visible + .secured(@guardian) + .where("topics.category_id in (?)", top_categories.keys) .where(user: @user) - .group('topics.category_id') - .pluck('category_id, COUNT(*)') - .each do |r| - top_categories[r[0].to_i].topic_count = r[1] - end + .group("topics.category_id") + .pluck("category_id, COUNT(*)") + .each { |r| top_categories[r[0].to_i].topic_count = r[1] } - top_categories.values.sort_by do |r| - -(r[:post_count] + r[:topic_count]) - end + top_categories.values.sort_by { |r| -(r[:post_count] + r[:topic_count]) } end delegate :likes_given, @@ -171,37 +172,42 @@ class UserSummary :time_read, to: :user_stat -protected + protected def user_counts(user_hash) user_ids = user_hash.keys lookup = UserLookup.new(user_ids) - user_ids.map do |user_id| - lookup_hash = lookup[user_id] + user_ids + .map do |user_id| + lookup_hash = lookup[user_id] - if lookup_hash.present? - primary_group = lookup.primary_groups[user_id] - flair_group = lookup.flair_groups[user_id] + if lookup_hash.present? + primary_group = lookup.primary_groups[user_id] + flair_group = lookup.flair_groups[user_id] - UserWithCount.new( - lookup_hash.attributes.merge( - count: user_hash[user_id], - primary_group: primary_group, - flair_group: flair_group + UserWithCount.new( + lookup_hash.attributes.merge( + count: user_hash[user_id], + primary_group: primary_group, + flair_group: flair_group, + ), ) - ) + end end - end.compact.sort_by { |u| -u[:count] } + .compact + .sort_by { |u| -u[:count] } end def post_query Post .joins(:topic) .includes(:topic) - .where('posts.post_type IN (?)', Topic.visible_post_types(@guardian&.user, include_moderator_actions: false)) + .where( + "posts.post_type IN (?)", + Topic.visible_post_types(@guardian&.user, include_moderator_actions: false), + ) .merge(Topic.listable_topics.visible.secured(@guardian)) .where(user: @user) end - end diff --git a/app/models/user_visit.rb b/app/models/user_visit.rb index ca08bc67477..57353a91a9d 100644 --- a/app/models/user_visit.rb +++ b/app/models/user_visit.rb @@ -2,7 +2,7 @@ class UserVisit < ActiveRecord::Base def self.counts_by_day_query(start_date, end_date, group_id = nil) - result = where('visited_at >= ? and visited_at <= ?', start_date.to_date, end_date.to_date) + result = where("visited_at >= ? and visited_at <= ?", start_date.to_date, end_date.to_date) if group_id result = result.joins("INNER JOIN users ON users.id = user_visits.user_id") diff --git a/app/models/user_warning.rb b/app/models/user_warning.rb index b11bff29949..11602880416 100644 --- a/app/models/user_warning.rb +++ b/app/models/user_warning.rb @@ -3,7 +3,7 @@ class UserWarning < ActiveRecord::Base belongs_to :user belongs_to :topic - belongs_to :created_by, class_name: 'User' + belongs_to :created_by, class_name: "User" end # == Schema Information diff --git a/app/models/username_validator.rb b/app/models/username_validator.rb index 747eb2f777e..8c82653b205 100644 --- a/app/models/username_validator.rb +++ b/app/models/username_validator.rb @@ -54,16 +54,14 @@ class UsernameValidator def username_present? return unless errors.empty? - if username.blank? - self.errors << I18n.t(:'user.username.blank') - end + self.errors << I18n.t(:"user.username.blank") if username.blank? end def username_length_min? return unless errors.empty? if username_grapheme_clusters.size < User.username_length.begin - self.errors << I18n.t(:'user.username.short', min: User.username_length.begin) + self.errors << I18n.t(:"user.username.short", min: User.username_length.begin) end end @@ -71,9 +69,9 @@ class UsernameValidator return unless errors.empty? if username_grapheme_clusters.size > User.username_length.end - self.errors << I18n.t(:'user.username.long', max: User.username_length.end) + self.errors << I18n.t(:"user.username.long", max: User.username_length.end) elsif username.length > MAX_CHARS - self.errors << I18n.t(:'user.username.too_long') + self.errors << I18n.t(:"user.username.too_long") end end @@ -81,7 +79,7 @@ class UsernameValidator return unless errors.empty? if self.class.invalid_char_pattern.match?(username) - self.errors << I18n.t(:'user.username.characters') + self.errors << I18n.t(:"user.username.characters") end end @@ -89,7 +87,7 @@ class UsernameValidator return unless errors.empty? && self.class.char_allowlist_exists? if username.chars.any? { |c| !self.class.allowed_char?(c) } - self.errors << I18n.t(:'user.username.characters') + self.errors << I18n.t(:"user.username.characters") end end @@ -97,7 +95,7 @@ class UsernameValidator return unless errors.empty? if INVALID_LEADING_CHAR_PATTERN.match?(username_grapheme_clusters.first) - self.errors << I18n.t(:'user.username.must_begin_with_alphanumeric_or_underscore') + self.errors << I18n.t(:"user.username.must_begin_with_alphanumeric_or_underscore") end end @@ -105,7 +103,7 @@ class UsernameValidator return unless errors.empty? if INVALID_TRAILING_CHAR_PATTERN.match?(username_grapheme_clusters.last) - self.errors << I18n.t(:'user.username.must_end_with_alphanumeric') + self.errors << I18n.t(:"user.username.must_end_with_alphanumeric") end end @@ -113,7 +111,7 @@ class UsernameValidator return unless errors.empty? if REPEATED_SPECIAL_CHAR_PATTERN.match?(username) - self.errors << I18n.t(:'user.username.must_not_contain_two_special_chars_in_seq') + self.errors << I18n.t(:"user.username.must_not_contain_two_special_chars_in_seq") end end @@ -121,7 +119,7 @@ class UsernameValidator return unless errors.empty? if CONFUSING_EXTENSIONS.match?(username) - self.errors << I18n.t(:'user.username.must_not_end_with_confusing_suffix') + self.errors << I18n.t(:"user.username.must_not_end_with_confusing_suffix") end end diff --git a/app/models/watched_word.rb b/app/models/watched_word.rb index e396544e87d..e493f50e416 100644 --- a/app/models/watched_word.rb +++ b/app/models/watched_word.rb @@ -1,30 +1,31 @@ # frozen_string_literal: true class WatchedWord < ActiveRecord::Base - def self.actions - @actions ||= Enum.new( - block: 1, - censor: 2, - require_approval: 3, - flag: 4, - link: 8, - replace: 5, - tag: 6, - silence: 7, - ) + @actions ||= + Enum.new( + block: 1, + censor: 2, + require_approval: 3, + flag: 4, + link: 8, + replace: 5, + tag: 6, + silence: 7, + ) end MAX_WORDS_PER_ACTION = 2000 before_validation do self.word = self.class.normalize_word(self.word) - if self.action == WatchedWord.actions[:link] && !(self.replacement =~ /^https?:\/\//) - self.replacement = "#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}" + if self.action == WatchedWord.actions[:link] && !(self.replacement =~ %r{^https?://}) + self.replacement = + "#{Discourse.base_url}#{self.replacement&.starts_with?("/") ? "" : "/"}#{self.replacement}" end end - validates :word, presence: true, uniqueness: true, length: { maximum: 100 } + validates :word, presence: true, uniqueness: true, length: { maximum: 100 } validates :action, presence: true validate :replacement_is_url, if: -> { action == WatchedWord.actions[:link] } @@ -36,26 +37,28 @@ class WatchedWord < ActiveRecord::Base end end - after_save :clear_cache + after_save :clear_cache after_destroy :clear_cache scope :by_action, -> { order("action ASC, word ASC") } - scope :for, ->(word:) do - where("(word ILIKE :word AND case_sensitive = 'f') OR (word LIKE :word AND case_sensitive = 't')", word: word) - end + scope :for, + ->(word:) { + where( + "(word ILIKE :word AND case_sensitive = 'f') OR (word LIKE :word AND case_sensitive = 't')", + word: word, + ) + } def self.normalize_word(w) - w.strip.squeeze('*') + w.strip.squeeze("*") end def replacement_is_url - if !(replacement =~ URI::regexp) - errors.add(:base, :invalid_url) - end + errors.add(:base, :invalid_url) if !(replacement =~ URI.regexp) end def replacement_is_tag_list - tag_list = replacement&.split(',') + tag_list = replacement&.split(",") tags = Tag.where(name: tag_list) if (tag_list.blank? || tags.empty? || tag_list.size != tags.size) errors.add(:base, :invalid_tag_list) diff --git a/app/models/web_crawler_request.rb b/app/models/web_crawler_request.rb index 7f7ceff9874..c1660f65555 100644 --- a/app/models/web_crawler_request.rb +++ b/app/models/web_crawler_request.rb @@ -16,8 +16,9 @@ class WebCrawlerRequest < ActiveRecord::Base end def self.write_cache!(user_agent, count, date) - where(id: request_id(date: date, user_agent: user_agent)) - .update_all(["count = count + ?", count]) + where(id: request_id(date: date, user_agent: user_agent)).update_all( + ["count = count + ?", count], + ) end protected @@ -25,14 +26,13 @@ class WebCrawlerRequest < ActiveRecord::Base def self.request_id(date:, user_agent:, retries: 0) id = where(date: date, user_agent: user_agent).pluck_first(:id) id ||= create!({ date: date, user_agent: user_agent }.merge(count: 0)).id - rescue # primary key violation + rescue StandardError # primary key violation if retries == 0 request_id(date: date, user_agent: user_agent, retries: 1) else raise end end - end # == Schema Information diff --git a/app/models/web_hook.rb b/app/models/web_hook.rb index d14a0dddad2..7ebfd01de10 100644 --- a/app/models/web_hook.rb +++ b/app/models/web_hook.rb @@ -8,9 +8,9 @@ class WebHook < ActiveRecord::Base has_many :web_hook_events, dependent: :destroy - default_scope { order('id ASC') } + default_scope { order("id ASC") } - validates :payload_url, presence: true, format: URI::regexp(%w(http https)) + validates :payload_url, presence: true, format: URI.regexp(%w[http https]) validates :secret, length: { minimum: 12 }, allow_blank: true validates_presence_of :content_type validates_presence_of :last_delivery_status @@ -24,15 +24,11 @@ class WebHook < ActiveRecord::Base end def self.content_types - @content_types ||= Enum.new('application/json' => 1, - 'application/x-www-form-urlencoded' => 2) + @content_types ||= Enum.new("application/json" => 1, "application/x-www-form-urlencoded" => 2) end def self.last_delivery_statuses - @last_delivery_statuses ||= Enum.new(inactive: 1, - failed: 2, - successful: 3, - disabled: 4) + @last_delivery_statuses ||= Enum.new(inactive: 1, failed: 2, successful: 3, disabled: 4) end def self.default_event_types @@ -44,7 +40,8 @@ class WebHook < ActiveRecord::Base end def self.active_web_hooks(type) - WebHook.where(active: true) + WebHook + .where(active: true) .joins(:web_hook_event_types) .where("web_hooks.wildcard_web_hook = ? OR web_hook_event_types.name = ?", true, type.to_s) .distinct @@ -52,9 +49,10 @@ class WebHook < ActiveRecord::Base def self.enqueue_hooks(type, event, opts = {}) active_web_hooks(type).each do |web_hook| - Jobs.enqueue(:emit_web_hook_event, opts.merge( - web_hook_id: web_hook.id, event_name: event.to_s, event_type: type.to_s - )) + Jobs.enqueue( + :emit_web_hook_event, + opts.merge(web_hook_id: web_hook.id, event_name: event.to_s, event_type: type.to_s), + ) end end @@ -62,39 +60,40 @@ class WebHook < ActiveRecord::Base if active_web_hooks(type).exists? payload = WebHook.generate_payload(type, object, serializer) - WebHook.enqueue_hooks(type, event, opts.merge( - id: object.id, - payload: payload - ) - ) + WebHook.enqueue_hooks(type, event, opts.merge(id: object.id, payload: payload)) end end def self.enqueue_topic_hooks(event, topic, payload = nil) - if active_web_hooks('topic').exists? && topic.present? - payload ||= begin - topic_view = TopicView.new(topic.id, Discourse.system_user) - WebHook.generate_payload(:topic, topic_view, WebHookTopicViewSerializer) - end + if active_web_hooks("topic").exists? && topic.present? + payload ||= + begin + topic_view = TopicView.new(topic.id, Discourse.system_user) + WebHook.generate_payload(:topic, topic_view, WebHookTopicViewSerializer) + end - WebHook.enqueue_hooks(:topic, event, + WebHook.enqueue_hooks( + :topic, + event, id: topic.id, category_id: topic.category_id, tag_ids: topic.tags.pluck(:id), - payload: payload + payload: payload, ) end end def self.enqueue_post_hooks(event, post, payload = nil) - if active_web_hooks('post').exists? && post.present? + if active_web_hooks("post").exists? && post.present? payload ||= WebHook.generate_payload(:post, post) - WebHook.enqueue_hooks(:post, event, + WebHook.enqueue_hooks( + :post, + event, id: post.id, category_id: post.topic&.category_id, tag_ids: post.topic&.tags&.pluck(:id), - payload: payload + payload: payload, ) end end @@ -103,10 +102,7 @@ class WebHook < ActiveRecord::Base serializer ||= TagSerializer if type == :tag serializer ||= "WebHook#{type.capitalize}Serializer".constantize - serializer.new(object, - scope: self.guardian, - root: false - ).to_json + serializer.new(object, scope: self.guardian, root: false).to_json end private @@ -121,15 +117,14 @@ class WebHook < ActiveRecord::Base return if payload_url.blank? uri = URI(payload_url.strip) - allowed = begin - FinalDestination::SSRFDetector.lookup_and_filter_ips(uri.hostname).present? - rescue FinalDestination::SSRFDetector::DisallowedIpError - false - end + allowed = + begin + FinalDestination::SSRFDetector.lookup_and_filter_ips(uri.hostname).present? + rescue FinalDestination::SSRFDetector::DisallowedIpError + false + end - if !allowed - self.errors.add(:base, I18n.t("webhooks.payload_url.blocked_or_internal")) - end + self.errors.add(:base, I18n.t("webhooks.payload_url.blocked_or_internal")) if !allowed end end diff --git a/app/models/web_hook_event.rb b/app/models/web_hook_event.rb index 9e7ac9cc601..d9a53041f29 100644 --- a/app/models/web_hook_event.rb +++ b/app/models/web_hook_event.rb @@ -5,12 +5,10 @@ class WebHookEvent < ActiveRecord::Base after_save :update_web_hook_delivery_status - default_scope { order('created_at DESC') } + default_scope { order("created_at DESC") } def self.purge_old - where( - 'created_at < ?', SiteSetting.retain_web_hook_events_period_days.days.ago - ).delete_all + where("created_at < ?", SiteSetting.retain_web_hook_events_period_days.days.ago).delete_all end def update_web_hook_delivery_status diff --git a/app/models/web_hook_event_type.rb b/app/models/web_hook_event_type.rb index a8852aeeb97..df3274802b4 100644 --- a/app/models/web_hook_event_type.rb +++ b/app/models/web_hook_event_type.rb @@ -18,18 +18,21 @@ class WebHookEventType < ActiveRecord::Base has_and_belongs_to_many :web_hooks - default_scope { order('id ASC') } + default_scope { order("id ASC") } validates :name, presence: true, uniqueness: true def self.active ids_to_exclude = [] - ids_to_exclude << SOLVED unless defined?(SiteSetting.solved_enabled) && SiteSetting.solved_enabled - ids_to_exclude << ASSIGN unless defined?(SiteSetting.assign_enabled) && SiteSetting.assign_enabled + unless defined?(SiteSetting.solved_enabled) && SiteSetting.solved_enabled + ids_to_exclude << SOLVED + end + unless defined?(SiteSetting.assign_enabled) && SiteSetting.assign_enabled + ids_to_exclude << ASSIGN + end self.where.not(id: ids_to_exclude) end - end # == Schema Information diff --git a/app/serializers/about_serializer.rb b/app/serializers/about_serializer.rb index f55bdd34767..7a26822d299 100644 --- a/app/serializers/about_serializer.rb +++ b/app/serializers/about_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class AboutSerializer < ApplicationSerializer - class UserAboutSerializer < BasicUserSerializer attributes :title, :last_seen_at end diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb index 525036512bf..e82bf8ac346 100644 --- a/app/serializers/admin_detailed_user_serializer.rb +++ b/app/serializers/admin_detailed_user_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class AdminDetailedUserSerializer < AdminUserSerializer - attributes :moderator, :can_grant_admin, :can_revoke_admin, @@ -108,11 +107,11 @@ class AdminDetailedUserSerializer < AdminUserSerializer def next_penalty step_number = penalty_counts.total - steps = SiteSetting.penalty_step_hours.split('|') + steps = SiteSetting.penalty_step_hours.split("|") step_number = [step_number, steps.length].min penalty_hours = steps[step_number] Integer(penalty_hours, 10).hours.from_now - rescue + rescue StandardError nil end diff --git a/app/serializers/admin_email_template_serializer.rb b/app/serializers/admin_email_template_serializer.rb index 07bfa929915..d48256dcaba 100644 --- a/app/serializers/admin_email_template_serializer.rb +++ b/app/serializers/admin_email_template_serializer.rb @@ -11,7 +11,7 @@ class AdminEmailTemplateSerializer < ApplicationSerializer if I18n.exists?("#{object}.title") I18n.t("#{object}.title") else - object.gsub(/.*\./, '').titleize + object.gsub(/.*\./, "").titleize end end diff --git a/app/serializers/admin_user_action_serializer.rb b/app/serializers/admin_user_action_serializer.rb index 1bff61203db..a07f99b594f 100644 --- a/app/serializers/admin_user_action_serializer.rb +++ b/app/serializers/admin_user_action_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'post_item_excerpt' +require_relative "post_item_excerpt" class AdminUserActionSerializer < ApplicationSerializer include PostItemExcerpt @@ -24,7 +24,7 @@ class AdminUserActionSerializer < ApplicationSerializer :deleted_at, :deleted_by, :reply_to_post_number, - :action_type + :action_type, ) def post_id @@ -64,7 +64,8 @@ class AdminUserActionSerializer < ApplicationSerializer end def moderator_action - object.post_type == Post.types[:moderator_action] || object.post_type == Post.types[:small_action] + object.post_type == Post.types[:moderator_action] || + object.post_type == Post.types[:small_action] end def deleted_by @@ -76,9 +77,12 @@ class AdminUserActionSerializer < ApplicationSerializer end def action_type - object.user_actions.select { |ua| ua.user_id = object.user_id } + object + .user_actions + .select { |ua| ua.user_id = object.user_id } .select { |ua| [UserAction::REPLY, UserAction::RESPONSE].include? ua.action_type } - .first.try(:action_type) + .first + .try(:action_type) end # we need this to handle deleted topics which aren't loaded via diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb index dd9ce3f6f04..9a6be3c71ab 100644 --- a/app/serializers/admin_user_list_serializer.rb +++ b/app/serializers/admin_user_list_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class AdminUserListSerializer < BasicUserSerializer - attributes :email, :secondary_emails, :active, @@ -28,7 +27,7 @@ class AdminUserListSerializer < BasicUserSerializer :staged, :second_factor_enabled - [:days_visited, :posts_read_count, :topics_entered, :post_count].each do |sym| + %i[days_visited posts_read_count topics_entered post_count].each do |sym| attributes sym define_method sym do object.user_stat.public_send(sym) @@ -106,13 +105,11 @@ class AdminUserListSerializer < BasicUserSerializer end def include_second_factor_enabled? - !SiteSetting.enable_discourse_connect && - SiteSetting.enable_local_logins && + !SiteSetting.enable_discourse_connect && SiteSetting.enable_local_logins && object.has_any_second_factor_methods_enabled? end def second_factor_enabled true end - end diff --git a/app/serializers/admin_user_serializer.rb b/app/serializers/admin_user_serializer.rb index e864578f919..fc396fc4bdd 100644 --- a/app/serializers/admin_user_serializer.rb +++ b/app/serializers/admin_user_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class AdminUserSerializer < AdminUserListSerializer - attributes :name, :associated_accounts, :can_send_activation_email, @@ -40,5 +39,4 @@ class AdminUserSerializer < AdminUserListSerializer def registration_ip_address object.registration_ip_address.try(:to_s) end - end diff --git a/app/serializers/admin_web_hook_serializer.rb b/app/serializers/admin_web_hook_serializer.rb index cf3134188b2..1fa79e384e9 100644 --- a/app/serializers/admin_web_hook_serializer.rb +++ b/app/serializers/admin_web_hook_serializer.rb @@ -12,7 +12,12 @@ class AdminWebHookSerializer < ApplicationSerializer :web_hook_event_types has_many :categories, serializer: BasicCategorySerializer, embed: :ids, include: false - has_many :tags, key: :tag_names, serializer: TagSerializer, embed: :ids, embed_key: :name, include: false + has_many :tags, + key: :tag_names, + serializer: TagSerializer, + embed: :ids, + embed_key: :name, + include: false has_many :groups, serializer: BasicGroupSerializer, embed: :ids, include: false def web_hook_event_types diff --git a/app/serializers/api_key_scope_serializer.rb b/app/serializers/api_key_scope_serializer.rb index f874efcf204..3386ab387a4 100644 --- a/app/serializers/api_key_scope_serializer.rb +++ b/app/serializers/api_key_scope_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class ApiKeyScopeSerializer < ApplicationSerializer - - attributes :resource, - :action, - :parameters, - :urls, - :allowed_parameters, - :key + attributes :resource, :action, :parameters, :urls, :allowed_parameters, :key def parameters ApiKeyScope.scope_mappings.dig(object.resource.to_sym, object.action.to_sym, :params).to_a @@ -18,7 +12,7 @@ class ApiKeyScopeSerializer < ApplicationSerializer end def action - object.action.to_s.gsub('_', ' ') + object.action.to_s.gsub("_", " ") end def key diff --git a/app/serializers/api_key_serializer.rb b/app/serializers/api_key_serializer.rb index ab2a7a2e4d5..d1ccac58de0 100644 --- a/app/serializers/api_key_serializer.rb +++ b/app/serializers/api_key_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ApiKeySerializer < ApplicationSerializer - attributes :id, :key, :truncated_key, diff --git a/app/serializers/application_serializer.rb b/app/serializers/application_serializer.rb index 40ee57b0622..aeecb045916 100644 --- a/app/serializers/application_serializer.rb +++ b/app/serializers/application_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'distributed_cache' +require "distributed_cache" class ApplicationSerializer < ActiveModel::Serializer embed :ids, include: true @@ -19,7 +19,9 @@ class ApplicationSerializer < ActiveModel::Serializer when String fragment_cache.delete(name_or_regexp) when Regexp - fragment_cache.hash.keys + fragment_cache + .hash + .keys .select { |k| k =~ name_or_regexp } .each { |k| fragment_cache.delete(k) } end diff --git a/app/serializers/archetype_serializer.rb b/app/serializers/archetype_serializer.rb index 469b1195f3b..8b74d5140a6 100644 --- a/app/serializers/archetype_serializer.rb +++ b/app/serializers/archetype_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ArchetypeSerializer < ApplicationSerializer - attributes :id, :name, :options def options @@ -10,7 +9,7 @@ class ArchetypeSerializer < ApplicationSerializer key: k, title: I18n.t("archetypes.#{object.id}.options.#{k}.title"), description: I18n.t("archetypes.#{object.id}.options.#{k}.description"), - option_type: object.options[k] + option_type: object.options[k], } end end @@ -18,5 +17,4 @@ class ArchetypeSerializer < ApplicationSerializer def name I18n.t("archetypes.#{object.id}.title") end - end diff --git a/app/serializers/associated_group_serializer.rb b/app/serializers/associated_group_serializer.rb index db7dec87238..121afd7d0ba 100644 --- a/app/serializers/associated_group_serializer.rb +++ b/app/serializers/associated_group_serializer.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true class AssociatedGroupSerializer < ApplicationSerializer - attributes :id, - :name, - :provider_name, - :label + attributes :id, :name, :provider_name, :label end diff --git a/app/serializers/auth_provider_serializer.rb b/app/serializers/auth_provider_serializer.rb index 78cfcffd0b9..d7bc83afc29 100644 --- a/app/serializers/auth_provider_serializer.rb +++ b/app/serializers/auth_provider_serializer.rb @@ -1,9 +1,14 @@ # frozen_string_literal: true class AuthProviderSerializer < ApplicationSerializer - - attributes :name, :custom_url, :pretty_name_override, :title_override, - :frame_width, :frame_height, :can_connect, :can_revoke, + attributes :name, + :custom_url, + :pretty_name_override, + :title_override, + :frame_width, + :frame_height, + :can_connect, + :can_revoke, :icon def title_override @@ -15,5 +20,4 @@ class AuthProviderSerializer < ApplicationSerializer return SiteSetting.get(object.pretty_name_setting) if object.pretty_name_setting object.pretty_name end - end diff --git a/app/serializers/backup_file_serializer.rb b/app/serializers/backup_file_serializer.rb index 1ccab21a15a..239c8952c9f 100644 --- a/app/serializers/backup_file_serializer.rb +++ b/app/serializers/backup_file_serializer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true class BackupFileSerializer < ApplicationSerializer - attributes :filename, - :size, - :last_modified + attributes :filename, :size, :last_modified end diff --git a/app/serializers/badge_index_serializer.rb b/app/serializers/badge_index_serializer.rb index 81e58b3d9e0..127bec384ab 100644 --- a/app/serializers/badge_index_serializer.rb +++ b/app/serializers/badge_index_serializer.rb @@ -11,5 +11,4 @@ class BadgeIndexSerializer < BadgeSerializer def has_badge @options[:user_badges].include?(object.id) end - end diff --git a/app/serializers/badge_serializer.rb b/app/serializers/badge_serializer.rb index cd837ac1cd9..004f0193230 100644 --- a/app/serializers/badge_serializer.rb +++ b/app/serializers/badge_serializer.rb @@ -1,9 +1,22 @@ # frozen_string_literal: true class BadgeSerializer < ApplicationSerializer - attributes :id, :name, :description, :grant_count, :allow_title, - :multiple_grant, :icon, :image_url, :listable, :enabled, :badge_grouping_id, - :system, :long_description, :slug, :has_badge, :manually_grantable? + attributes :id, + :name, + :description, + :grant_count, + :allow_title, + :multiple_grant, + :icon, + :image_url, + :listable, + :enabled, + :badge_grouping_id, + :system, + :long_description, + :slug, + :has_badge, + :manually_grantable? has_one :badge_type diff --git a/app/serializers/basic_category_serializer.rb b/app/serializers/basic_category_serializer.rb index fe1b8cd2f71..06dfa9628eb 100644 --- a/app/serializers/basic_category_serializer.rb +++ b/app/serializers/basic_category_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class BasicCategorySerializer < ApplicationSerializer - attributes :id, :name, :color, @@ -42,19 +41,35 @@ class BasicCategorySerializer < ApplicationSerializer end def name - object.uncategorized? ? I18n.t('uncategorized_category_name', locale: SiteSetting.default_locale) : object.name + if object.uncategorized? + I18n.t("uncategorized_category_name", locale: SiteSetting.default_locale) + else + object.name + end end def description_text - object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description_text + if object.uncategorized? + I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) + else + object.description_text + end end def description - object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description + if object.uncategorized? + I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) + else + object.description + end end def description_excerpt - object.uncategorized? ? I18n.t('category.uncategorized_description', locale: SiteSetting.default_locale) : object.description_excerpt + if object.uncategorized? + I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale) + else + object.description_excerpt + end end def can_edit diff --git a/app/serializers/basic_group_history_serializer.rb b/app/serializers/basic_group_history_serializer.rb index afd1c60f709..40518bd8ec8 100644 --- a/app/serializers/basic_group_history_serializer.rb +++ b/app/serializers/basic_group_history_serializer.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class BasicGroupHistorySerializer < ApplicationSerializer - attributes :action, - :subject, - :prev_value, - :new_value, - :created_at + attributes :action, :subject, :prev_value, :new_value, :created_at has_one :acting_user, embed: :objects, serializer: BasicUserSerializer has_one :target_user, embed: :objects, serializer: BasicUserSerializer diff --git a/app/serializers/basic_group_serializer.rb b/app/serializers/basic_group_serializer.rb index e51e6000022..ab64df800a1 100644 --- a/app/serializers/basic_group_serializer.rb +++ b/app/serializers/basic_group_serializer.rb @@ -44,7 +44,9 @@ class BasicGroupSerializer < ApplicationSerializer end def bio_excerpt - PrettyText.excerpt(object.bio_cooked, 110, keep_emoji_images: true) if object.bio_cooked.present? + if object.bio_cooked.present? + PrettyText.excerpt(object.bio_cooked, 110, keep_emoji_images: true) + end end def include_incoming_email? diff --git a/app/serializers/basic_post_serializer.rb b/app/serializers/basic_post_serializer.rb index 3486c47bc2c..029b04bdbf6 100644 --- a/app/serializers/basic_post_serializer.rb +++ b/app/serializers/basic_post_serializer.rb @@ -2,13 +2,7 @@ # The most basic attributes of a topic that we need to create a link for it. class BasicPostSerializer < ApplicationSerializer - attributes :id, - :name, - :username, - :avatar_template, - :created_at, - :cooked, - :cooked_hidden + attributes :id, :name, :username, :avatar_template, :created_at, :cooked, :cooked_hidden attr_accessor :topic_view @@ -35,9 +29,9 @@ class BasicPostSerializer < ApplicationSerializer def cooked if cooked_hidden if scope.current_user && object.user_id == scope.current_user.id - I18n.t('flagging.you_must_edit', path: "/my/messages") + I18n.t("flagging.you_must_edit", path: "/my/messages") else - I18n.t('flagging.user_must_edit') + I18n.t("flagging.user_must_edit") end else object.filter_quotes(@parent_post) @@ -49,11 +43,11 @@ class BasicPostSerializer < ApplicationSerializer end def post_custom_fields - @post_custom_fields ||= if @topic_view - (@topic_view.post_custom_fields || {})[object.id] || {} - else - object.custom_fields - end + @post_custom_fields ||= + if @topic_view + (@topic_view.post_custom_fields || {})[object.id] || {} + else + object.custom_fields + end end - end diff --git a/app/serializers/basic_user_serializer.rb b/app/serializers/basic_user_serializer.rb index fc580931f60..769002d0f00 100644 --- a/app/serializers/basic_user_serializer.rb +++ b/app/serializers/basic_user_serializer.rb @@ -28,9 +28,9 @@ class BasicUserSerializer < ApplicationSerializer end def categories_with_notification_level(lookup_level) - category_user_notification_levels.select do |id, level| - level == CategoryUser.notification_levels[lookup_level] - end.keys + category_user_notification_levels + .select { |id, level| level == CategoryUser.notification_levels[lookup_level] } + .keys end def category_user_notification_levels diff --git a/app/serializers/category_and_topic_lists_serializer.rb b/app/serializers/category_and_topic_lists_serializer.rb index 863164fb108..55983ebc4a8 100644 --- a/app/serializers/category_and_topic_lists_serializer.rb +++ b/app/serializers/category_and_topic_lists_serializer.rb @@ -7,9 +7,7 @@ class CategoryAndTopicListsSerializer < ApplicationSerializer has_many :primary_groups, serializer: PrimaryGroupSerializer, embed: :objects def users - users = object.topic_list.topics.map do |t| - t.posters.map { |poster| poster.try(:user) } - end + users = object.topic_list.topics.map { |t| t.posters.map { |poster| poster.try(:user) } } users.flatten! users.compact! users.uniq!(&:id) @@ -17,13 +15,11 @@ class CategoryAndTopicListsSerializer < ApplicationSerializer end def primary_groups - groups = object.topic_list.topics.map do |t| - t.posters.map { |poster| poster.try(:primary_group) } - end + groups = + object.topic_list.topics.map { |t| t.posters.map { |poster| poster.try(:primary_group) } } groups.flatten! groups.compact! groups.uniq!(&:id) groups end - end diff --git a/app/serializers/category_detailed_serializer.rb b/app/serializers/category_detailed_serializer.rb index 1349cd44aa8..39197008e2b 100644 --- a/app/serializers/category_detailed_serializer.rb +++ b/app/serializers/category_detailed_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class CategoryDetailedSerializer < BasicCategorySerializer - attributes :topic_count, :post_count, :topics_day, @@ -14,7 +13,10 @@ class CategoryDetailedSerializer < BasicCategorySerializer has_many :displayable_topics, serializer: ListableTopicSerializer, embed: :objects, key: :topics - has_many :subcategory_list, serializer: CategoryDetailedSerializer, embed: :objects, key: :subcategory_list + has_many :subcategory_list, + serializer: CategoryDetailedSerializer, + embed: :objects, + key: :subcategory_list def include_displayable_topics? displayable_topics.present? @@ -55,11 +57,8 @@ class CategoryDetailedSerializer < BasicCategorySerializer def count_with_subcategories(method) count = object.public_send(method) || 0 - object.subcategories.each do |category| - count += (category.public_send(method) || 0) - end + object.subcategories.each { |category| count += (category.public_send(method) || 0) } count end - end diff --git a/app/serializers/category_list_serializer.rb b/app/serializers/category_list_serializer.rb index 1fe73e2229e..9f159e6fe16 100644 --- a/app/serializers/category_list_serializer.rb +++ b/app/serializers/category_list_serializer.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class CategoryListSerializer < ApplicationSerializer - - attributes :can_create_category, - :can_create_topic + attributes :can_create_category, :can_create_topic has_many :categories, serializer: CategoryDetailedSerializer, embed: :objects @@ -14,5 +12,4 @@ class CategoryListSerializer < ApplicationSerializer def can_create_topic scope.can_create?(Topic) end - end diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb index b7ab43b5806..d2bfbb16121 100644 --- a/app/serializers/category_serializer.rb +++ b/app/serializers/category_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class CategorySerializer < SiteCategorySerializer - attributes :read_restricted, :available_groups, :auto_close_hours, @@ -32,25 +31,25 @@ class CategorySerializer < SiteCategorySerializer end def group_permissions - @group_permissions ||= begin - perms = object - .category_groups - .joins(:group) - .includes(:group) - .merge(Group.visible_groups(scope&.user, "groups.name ASC", include_everyone: true)) - .map do |cg| - { - permission_type: cg.permission_type, - group_name: cg.group.name + @group_permissions ||= + begin + perms = + object + .category_groups + .joins(:group) + .includes(:group) + .merge(Group.visible_groups(scope&.user, "groups.name ASC", include_everyone: true)) + .map { |cg| { permission_type: cg.permission_type, group_name: cg.group.name } } + + if perms.length == 0 && !object.read_restricted + perms << { + permission_type: CategoryGroup.permission_types[:full], + group_name: Group[:everyone]&.name.presence || :everyone, } end - if perms.length == 0 && !object.read_restricted - perms << { permission_type: CategoryGroup.permission_types[:full], group_name: Group[:everyone]&.name.presence || :everyone } + perms end - - perms - end end def include_group_permissions? @@ -70,8 +69,11 @@ class CategorySerializer < SiteCategorySerializer end def include_is_special? - [SiteSetting.meta_category_id, SiteSetting.staff_category_id, SiteSetting.uncategorized_category_id] - .include? object.id + [ + SiteSetting.meta_category_id, + SiteSetting.staff_category_id, + SiteSetting.uncategorized_category_id, + ].include? object.id end def is_special @@ -101,8 +103,8 @@ class CategorySerializer < SiteCategorySerializer def notification_level user = scope && scope.user object.notification_level || - (user && CategoryUser.where(user: user, category: object).first.try(:notification_level)) || - CategoryUser.default_notification_level + (user && CategoryUser.where(user: user, category: object).first.try(:notification_level)) || + CategoryUser.default_notification_level end def custom_fields diff --git a/app/serializers/concerns/email_logs_mixin.rb b/app/serializers/concerns/email_logs_mixin.rb index f0fe239309a..4f76679f746 100644 --- a/app/serializers/concerns/email_logs_mixin.rb +++ b/app/serializers/concerns/email_logs_mixin.rb @@ -3,12 +3,12 @@ module EmailLogsMixin def self.included(klass) klass.attributes :id, - :to_address, - :email_type, - :user_id, - :created_at, - :post_url, - :post_description + :to_address, + :email_type, + :user_id, + :created_at, + :post_url, + :post_description klass.has_one :user, serializer: BasicUserSerializer, embed: :objects end diff --git a/app/serializers/concerns/topic_tags_mixin.rb b/app/serializers/concerns/topic_tags_mixin.rb index 5c39cd5a5d4..2704ba33611 100644 --- a/app/serializers/concerns/topic_tags_mixin.rb +++ b/app/serializers/concerns/topic_tags_mixin.rb @@ -27,10 +27,15 @@ module TopicTagsMixin def all_tags return @tags if defined?(@tags) # Calling method `pluck` or `order` along with `includes` causing N+1 queries - tags = (SiteSetting.tags_sort_alphabetically ? topic.tags.sort_by(&:name) : topic.tags.sort_by(&:topic_count).reverse) - if !scope.is_staff? - tags = tags.reject { |tag| scope.hidden_tag_names.include?(tag[:name]) } - end + tags = + ( + if SiteSetting.tags_sort_alphabetically + topic.tags.sort_by(&:name) + else + topic.tags.sort_by(&:topic_count).reverse + end + ) + tags = tags.reject { |tag| scope.hidden_tag_names.include?(tag[:name]) } if !scope.is_staff? @tags = tags end end diff --git a/app/serializers/concerns/user_auth_tokens_mixin.rb b/app/serializers/concerns/user_auth_tokens_mixin.rb index 8e963f7fadb..aaa0b752eaa 100644 --- a/app/serializers/concerns/user_auth_tokens_mixin.rb +++ b/app/serializers/concerns/user_auth_tokens_mixin.rb @@ -3,16 +3,7 @@ module UserAuthTokensMixin extend ActiveSupport::Concern - included do - attributes :id, - :client_ip, - :location, - :browser, - :device, - :os, - :icon, - :created_at - end + included { attributes :id, :client_ip, :location, :browser, :device, :os, :icon, :created_at } def client_ip object.client_ip.to_s @@ -20,7 +11,7 @@ module UserAuthTokensMixin def location ipinfo = DiscourseIpInfo.get(client_ip, locale: I18n.locale) - ipinfo[:location].presence || I18n.t('staff_action_logs.unknown') + ipinfo[:location].presence || I18n.t("staff_action_logs.unknown") end def browser @@ -41,17 +32,17 @@ module UserAuthTokensMixin def icon case BrowserDetection.os(object.user_agent) when :android - 'fab-android' + "fab-android" when :chromeos - 'fab-chrome' + "fab-chrome" when :macos, :ios - 'fab-apple' + "fab-apple" when :linux - 'fab-linux' + "fab-linux" when :windows - 'fab-windows' + "fab-windows" else - 'question' + "question" end end end diff --git a/app/serializers/concerns/user_primary_group_mixin.rb b/app/serializers/concerns/user_primary_group_mixin.rb index 65441ebba47..0bd2538b1eb 100644 --- a/app/serializers/concerns/user_primary_group_mixin.rb +++ b/app/serializers/concerns/user_primary_group_mixin.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module UserPrimaryGroupMixin - def self.included(klass) klass.attributes :primary_group_name, :flair_name, diff --git a/app/serializers/concerns/user_sidebar_mixin.rb b/app/serializers/concerns/user_sidebar_mixin.rb index 1149b639602..cd872195858 100644 --- a/app/serializers/concerns/user_sidebar_mixin.rb +++ b/app/serializers/concerns/user_sidebar_mixin.rb @@ -2,13 +2,11 @@ module UserSidebarMixin def sidebar_tags - object.visible_sidebar_tags(scope) + object + .visible_sidebar_tags(scope) .pluck(:name, :topic_count, :pm_topic_count) .reduce([]) do |tags, sidebar_tag| - tags.push( - name: sidebar_tag[0], - pm_only: sidebar_tag[1] == 0 && sidebar_tag[2] > 0 - ) + tags.push(name: sidebar_tag[0], pm_only: sidebar_tag[1] == 0 && sidebar_tag[2] > 0) end end @@ -33,7 +31,11 @@ module UserSidebarMixin end def sidebar_list_destination - object.user_option.sidebar_list_none_selected? ? SiteSetting.default_sidebar_list_destination : object.user_option.sidebar_list_destination + if object.user_option.sidebar_list_none_selected? + SiteSetting.default_sidebar_list_destination + else + object.user_option.sidebar_list_destination + end end def include_sidebar_list_destination? diff --git a/app/serializers/concerns/user_tag_notifications_mixin.rb b/app/serializers/concerns/user_tag_notifications_mixin.rb index dfa367d6165..74c56ec1db7 100644 --- a/app/serializers/concerns/user_tag_notifications_mixin.rb +++ b/app/serializers/concerns/user_tag_notifications_mixin.rb @@ -22,9 +22,9 @@ module UserTagNotificationsMixin end def tags_with_notification_level(lookup_level) - tag_user_notification_levels.select do |id, level| - level == TagUser.notification_levels[lookup_level] - end.keys + tag_user_notification_levels + .select { |id, level| level == TagUser.notification_levels[lookup_level] } + .keys end def tag_user_notification_levels diff --git a/app/serializers/current_user_option_serializer.rb b/app/serializers/current_user_option_serializer.rb index edfabcf3d6c..f3223f79243 100644 --- a/app/serializers/current_user_option_serializer.rb +++ b/app/serializers/current_user_option_serializer.rb @@ -18,10 +18,9 @@ class CurrentUserOptionSerializer < ApplicationSerializer :should_be_redirected_to_top, :redirected_to_top, :treat_as_new_topic_start_date, - - def likes_notifications_disabled - object.likes_notifications_disabled? - end + def likes_notifications_disabled + object.likes_notifications_disabled? + end def include_redirected_to_top? object.redirected_to_top.present? diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb index cb4153edefc..9a7cb1d9f11 100644 --- a/app/serializers/current_user_serializer.rb +++ b/app/serializers/current_user_serializer.rb @@ -79,11 +79,14 @@ class CurrentUserSerializer < BasicUserSerializer def groups owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set - object.visible_groups.pluck(:id, :name, :has_messages).map do |id, name, has_messages| - group = { id: id, name: name, has_messages: has_messages } - group[:owner] = true if owned_group_ids.include?(id) - group - end + object + .visible_groups + .pluck(:id, :name, :has_messages) + .map do |id, name, has_messages| + group = { id: id, name: name, has_messages: has_messages } + group[:owner] = true if owned_group_ids.include?(id) + group + end end def link_posting_access @@ -141,7 +144,7 @@ class CurrentUserSerializer < BasicUserSerializer def custom_fields fields = nil if SiteSetting.public_user_custom_fields.present? - fields = SiteSetting.public_user_custom_fields.split('|') + fields = SiteSetting.public_user_custom_fields.split("|") end DiscoursePluginRegistry.serialized_current_user_fields.each do |f| fields ||= [] @@ -184,15 +187,21 @@ class CurrentUserSerializer < BasicUserSerializer end def top_category_ids - omitted_notification_levels = [CategoryUser.notification_levels[:muted], CategoryUser.notification_levels[:regular]] - CategoryUser.where(user_id: object.id) + omitted_notification_levels = [ + CategoryUser.notification_levels[:muted], + CategoryUser.notification_levels[:regular], + ] + CategoryUser + .where(user_id: object.id) .where.not(notification_level: omitted_notification_levels) - .order(" + .order( + " CASE WHEN notification_level = 3 THEN 1 WHEN notification_level = 2 THEN 2 WHEN notification_level = 4 THEN 3 - END") + END", + ) .pluck(:category_id) .slice(0, SiteSetting.header_dropdown_category_count) end @@ -293,7 +302,9 @@ class CurrentUserSerializer < BasicUserSerializer def redesigned_topic_timeline_enabled if SiteSetting.enable_experimental_topic_timeline_groups.present? - object.in_any_groups?(SiteSetting.enable_experimental_topic_timeline_groups.split("|").map(&:to_i)) + object.in_any_groups?( + SiteSetting.enable_experimental_topic_timeline_groups.split("|").map(&:to_i), + ) else false end diff --git a/app/serializers/detailed_tag_serializer.rb b/app/serializers/detailed_tag_serializer.rb index 79354be61fb..83570c3d5e0 100644 --- a/app/serializers/detailed_tag_serializer.rb +++ b/app/serializers/detailed_tag_serializer.rb @@ -28,9 +28,8 @@ class DetailedTagSerializer < TagSerializer private def category_ids - @_category_ids ||= object.categories.pluck(:id) + - object.tag_groups.includes(:categories).map do |tg| - tg.categories.map(&:id) - end.flatten + @_category_ids ||= + object.categories.pluck(:id) + + object.tag_groups.includes(:categories).map { |tg| tg.categories.map(&:id) }.flatten end end diff --git a/app/serializers/detailed_user_badge_serializer.rb b/app/serializers/detailed_user_badge_serializer.rb index e31d8f8e8a7..693ceffb0e1 100644 --- a/app/serializers/detailed_user_badge_serializer.rb +++ b/app/serializers/detailed_user_badge_serializer.rb @@ -33,7 +33,7 @@ class DetailedUserBadgeSerializer < BasicUserBadgeSerializer def can_favorite SiteSetting.max_favorite_badges > 0 && - (scope.current_user.present? && object.user_id == scope.current_user.id) && - !(1..4).include?(object.badge_id) + (scope.current_user.present? && object.user_id == scope.current_user.id) && + !(1..4).include?(object.badge_id) end end diff --git a/app/serializers/directory_column_serializer.rb b/app/serializers/directory_column_serializer.rb index 4e172d3e9e6..608ec4d2ddf 100644 --- a/app/serializers/directory_column_serializer.rb +++ b/app/serializers/directory_column_serializer.rb @@ -1,12 +1,7 @@ # frozen_string_literal: true class DirectoryColumnSerializer < ApplicationSerializer - attributes :id, - :name, - :type, - :position, - :icon, - :user_field_id + attributes :id, :name, :type, :position, :icon, :user_field_id def name object.name || object.user_field.name diff --git a/app/serializers/directory_item_serializer.rb b/app/serializers/directory_item_serializer.rb index c4923cbbd10..bd218f982bf 100644 --- a/app/serializers/directory_item_serializer.rb +++ b/app/serializers/directory_item_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class DirectoryItemSerializer < ApplicationSerializer - class UserSerializer < UserNameSerializer include UserPrimaryGroupMixin @@ -12,9 +11,7 @@ class DirectoryItemSerializer < ApplicationSerializer object.user_custom_fields.each do |cuf| user_field_id = @options[:user_custom_field_map][cuf.name] - if user_field_id - fields[user_field_id] = cuf.value - end + fields[user_field_id] = cuf.value if user_field_id end fields @@ -38,9 +35,7 @@ class DirectoryItemSerializer < ApplicationSerializer def attributes hash = super - @options[:attributes].each do |attr| - hash.merge!("#{attr}": object[attr]) - end + @options[:attributes].each { |attr| hash.merge!("#{attr}": object[attr]) } if object.period_type == DirectoryItem.period_types[:all] hash.merge!(time_read: object.user_stat.time_read) diff --git a/app/serializers/draft_serializer.rb b/app/serializers/draft_serializer.rb index bcdfa257fcf..8ca8111ccfb 100644 --- a/app/serializers/draft_serializer.rb +++ b/app/serializers/draft_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'post_item_excerpt' +require_relative "post_item_excerpt" class DraftSerializer < ApplicationSerializer include PostItemExcerpt @@ -24,7 +24,7 @@ class DraftSerializer < ApplicationSerializer :archived def cooked - object.parsed_data['reply'] || "" + object.parsed_data["reply"] || "" end def draft_username @@ -86,5 +86,4 @@ class DraftSerializer < ApplicationSerializer def include_category_id? object.topic&.category_id&.present? end - end diff --git a/app/serializers/edit_directory_column_serializer.rb b/app/serializers/edit_directory_column_serializer.rb index 7c703d59659..ac9824c5000 100644 --- a/app/serializers/edit_directory_column_serializer.rb +++ b/app/serializers/edit_directory_column_serializer.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true class EditDirectoryColumnSerializer < DirectoryColumnSerializer - attributes :enabled, - :automatic_position + attributes :enabled, :automatic_position has_one :user_field, serializer: UserFieldSerializer, embed: :objects end diff --git a/app/serializers/email_log_serializer.rb b/app/serializers/email_log_serializer.rb index 803a55c2e56..0e8640bc16e 100644 --- a/app/serializers/email_log_serializer.rb +++ b/app/serializers/email_log_serializer.rb @@ -3,10 +3,7 @@ class EmailLogSerializer < ApplicationSerializer include EmailLogsMixin - attributes :reply_key, - :bounced, - :has_bounce_key, - :smtp_transaction_response + attributes :reply_key, :bounced, :has_bounce_key, :smtp_transaction_response has_one :user, serializer: BasicUserSerializer, embed: :objects diff --git a/app/serializers/embeddable_host_serializer.rb b/app/serializers/embeddable_host_serializer.rb index 28bf3e905bc..d6a36de5c27 100644 --- a/app/serializers/embeddable_host_serializer.rb +++ b/app/serializers/embeddable_host_serializer.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true class EmbeddableHostSerializer < ApplicationSerializer - - TO_SERIALIZE = [:id, :host, :allowed_paths, :class_name, :category_id] + TO_SERIALIZE = %i[id host allowed_paths class_name category_id] attributes *TO_SERIALIZE - TO_SERIALIZE.each do |attr| - define_method(attr) { object.public_send(attr) } - end - + TO_SERIALIZE.each { |attr| define_method(attr) { object.public_send(attr) } } end diff --git a/app/serializers/flagged_topic_summary_serializer.rb b/app/serializers/flagged_topic_summary_serializer.rb index 0679e1ac4b0..251bcd4ca1c 100644 --- a/app/serializers/flagged_topic_summary_serializer.rb +++ b/app/serializers/flagged_topic_summary_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class FlaggedTopicSummarySerializer < ActiveModel::Serializer - - attributes( - :id, - :flag_counts, - :user_ids, - :last_flag_at - ) + attributes(:id, :flag_counts, :user_ids, :last_flag_at) has_one :topic, serializer: FlaggedTopicSerializer diff --git a/app/serializers/flagged_user_serializer.rb b/app/serializers/flagged_user_serializer.rb index 92a383559aa..ed98e77ac12 100644 --- a/app/serializers/flagged_user_serializer.rb +++ b/app/serializers/flagged_user_serializer.rb @@ -39,11 +39,8 @@ class FlaggedUserSerializer < BasicUserSerializer fields = User.allowed_user_custom_fields(scope) result = {} - fields.each do |k| - result[k] = object.custom_fields[k] if object.custom_fields[k].present? - end + fields.each { |k| result[k] = object.custom_fields[k] if object.custom_fields[k].present? } result end - end diff --git a/app/serializers/group_post_serializer.rb b/app/serializers/group_post_serializer.rb index 1176fc36094..0eed59b7a63 100644 --- a/app/serializers/group_post_serializer.rb +++ b/app/serializers/group_post_serializer.rb @@ -1,18 +1,11 @@ # frozen_string_literal: true -require_relative 'post_item_excerpt' +require_relative "post_item_excerpt" class GroupPostSerializer < ApplicationSerializer include PostItemExcerpt - attributes :id, - :created_at, - :title, - :url, - :category_id, - :post_number, - :topic_id, - :post_type + attributes :id, :created_at, :title, :url, :category_id, :post_number, :topic_id, :post_type has_one :user, serializer: GroupPostUserSerializer, embed: :object has_one :topic, serializer: BasicTopicSerializer, embed: :object diff --git a/app/serializers/group_show_serializer.rb b/app/serializers/group_show_serializer.rb index f3b520d19ac..f70c379a4a2 100644 --- a/app/serializers/group_show_serializer.rb +++ b/app/serializers/group_show_serializer.rb @@ -1,7 +1,13 @@ # frozen_string_literal: true class GroupShowSerializer < BasicGroupSerializer - attributes :is_group_user, :is_group_owner, :is_group_owner_display, :mentionable, :messageable, :flair_icon, :flair_type + attributes :is_group_user, + :is_group_owner, + :is_group_owner_display, + :mentionable, + :messageable, + :flair_icon, + :flair_type def self.admin_attributes(*attrs) attributes(*attrs) @@ -108,19 +114,16 @@ class GroupShowSerializer < BasicGroupSerializer flair_type.present? && (is_group_owner || scope.is_admin?) end - [:watching, :regular, :tracking, :watching_first_post, :muted].each do |level| + %i[watching regular tracking watching_first_post muted].each do |level| define_method("#{level}_category_ids") do group_category_notifications[NotificationLevels.all[level]] || [] end define_method("include_#{level}_tags?") do - SiteSetting.tagging_enabled? && - scope.is_admin? || (include_is_group_owner? && is_group_owner) + SiteSetting.tagging_enabled? && scope.is_admin? || (include_is_group_owner? && is_group_owner) end - define_method("#{level}_tags") do - group_tag_notifications[NotificationLevels.all[level]] || [] - end + define_method("#{level}_tags") { group_tag_notifications[NotificationLevels.all[level]] || [] } end def associated_group_ids @@ -144,7 +147,8 @@ class GroupShowSerializer < BasicGroupSerializer def group_category_notifications @group_category_notification_defaults ||= - GroupCategoryNotificationDefault.where(group_id: object.id) + GroupCategoryNotificationDefault + .where(group_id: object.id) .pluck(:notification_level, :category_id) .inject({}) do |h, arr| h[arr[0]] ||= [] @@ -155,7 +159,8 @@ class GroupShowSerializer < BasicGroupSerializer def group_tag_notifications @group_tag_notification_defaults ||= - GroupTagNotificationDefault.where(group_id: object.id) + GroupTagNotificationDefault + .where(group_id: object.id) .joins(:tag) .pluck(:notification_level, :name) .inject({}) do |h, arr| diff --git a/app/serializers/group_user_serializer.rb b/app/serializers/group_user_serializer.rb index fa5bbda7867..bc286d109b7 100644 --- a/app/serializers/group_user_serializer.rb +++ b/app/serializers/group_user_serializer.rb @@ -3,13 +3,7 @@ class GroupUserSerializer < BasicUserSerializer include UserPrimaryGroupMixin - attributes :name, - :title, - :last_posted_at, - :last_seen_at, - :added_at, - :timezone, - :status + attributes :name, :title, :last_posted_at, :last_seen_at, :added_at, :timezone, :status def timezone user.user_option.timezone diff --git a/app/serializers/grouped_screened_url_serializer.rb b/app/serializers/grouped_screened_url_serializer.rb index 9e2a4a1ff71..5117420070c 100644 --- a/app/serializers/grouped_screened_url_serializer.rb +++ b/app/serializers/grouped_screened_url_serializer.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true class GroupedScreenedUrlSerializer < ApplicationSerializer - attributes :domain, - :action, - :match_count, - :last_match_at, - :created_at + attributes :domain, :action, :match_count, :last_match_at, :created_at def action - 'do_nothing' + "do_nothing" end end diff --git a/app/serializers/grouped_search_result_serializer.rb b/app/serializers/grouped_search_result_serializer.rb index 653ac70ac84..66c49602693 100644 --- a/app/serializers/grouped_search_result_serializer.rb +++ b/app/serializers/grouped_search_result_serializer.rb @@ -6,7 +6,14 @@ class GroupedSearchResultSerializer < ApplicationSerializer has_many :categories, serializer: BasicCategorySerializer has_many :tags, serializer: TagSerializer has_many :groups, serializer: BasicGroupSerializer - attributes :more_posts, :more_users, :more_categories, :term, :search_log_id, :more_full_page_results, :can_create_topic, :error + attributes :more_posts, + :more_users, + :more_categories, + :term, + :search_log_id, + :more_full_page_results, + :can_create_topic, + :error def search_log_id object.search_log_id @@ -23,5 +30,4 @@ class GroupedSearchResultSerializer < ApplicationSerializer def can_create_topic scope.can_create?(Topic) end - end diff --git a/app/serializers/hidden_profile_serializer.rb b/app/serializers/hidden_profile_serializer.rb index f2a04db807b..f0b8e00d3b6 100644 --- a/app/serializers/hidden_profile_serializer.rb +++ b/app/serializers/hidden_profile_serializer.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class HiddenProfileSerializer < BasicUserSerializer - attributes( - :profile_hidden?, - :title, - :primary_group_name - ) + attributes(:profile_hidden?, :title, :primary_group_name) def profile_hidden? true diff --git a/app/serializers/incoming_email_details_serializer.rb b/app/serializers/incoming_email_details_serializer.rb index d248a3ac588..490b692e88f 100644 --- a/app/serializers/incoming_email_details_serializer.rb +++ b/app/serializers/incoming_email_details_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class IncomingEmailDetailsSerializer < ApplicationSerializer - - attributes :error, - :error_description, - :rejection_message, - :headers, - :subject, - :body + attributes :error, :error_description, :rejection_message, :headers, :subject, :body def initialize(incoming_email, opts) super @@ -39,15 +33,30 @@ class IncomingEmailDetailsSerializer < ApplicationSerializer end def body - body = @mail.text_part.decoded rescue nil - body ||= @mail.html_part.decoded rescue nil - body ||= @mail.body.decoded rescue nil + body = + begin + @mail.text_part.decoded + rescue StandardError + nil + end + body ||= + begin + @mail.html_part.decoded + rescue StandardError + nil + end + body ||= + begin + @mail.body.decoded + rescue StandardError + nil + end return I18n.t("emails.incoming.no_body") if body.blank? - body.encode("utf-8", invalid: :replace, undef: :replace, replace: "") + body + .encode("utf-8", invalid: :replace, undef: :replace, replace: "") .strip .truncate_words(100, escape: false) end - end diff --git a/app/serializers/incoming_email_serializer.rb b/app/serializers/incoming_email_serializer.rb index 54830995a12..116c36a43a5 100644 --- a/app/serializers/incoming_email_serializer.rb +++ b/app/serializers/incoming_email_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class IncomingEmailSerializer < ApplicationSerializer - attributes :id, :created_at, :from_address, @@ -34,5 +33,4 @@ class IncomingEmailSerializer < ApplicationSerializer def error @object.error.presence || I18n.t("emails.incoming.unrecognized_error") end - end diff --git a/app/serializers/invite_link_serializer.rb b/app/serializers/invite_link_serializer.rb index 21a64509189..8f454f87274 100644 --- a/app/serializers/invite_link_serializer.rb +++ b/app/serializers/invite_link_serializer.rb @@ -1,7 +1,13 @@ # frozen_string_literal: true class InviteLinkSerializer < ApplicationSerializer - attributes :id, :invite_key, :created_at, :max_redemptions_allowed, :redemption_count, :expires_at, :group_names + attributes :id, + :invite_key, + :created_at, + :max_redemptions_allowed, + :redemption_count, + :expires_at, + :group_names def group_names object.groups.pluck(:name).join(", ") diff --git a/app/serializers/invited_serializer.rb b/app/serializers/invited_serializer.rb index a0f76017e44..c1acf056b2f 100644 --- a/app/serializers/invited_serializer.rb +++ b/app/serializers/invited_serializer.rb @@ -6,10 +6,17 @@ class InvitedSerializer < ApplicationSerializer def invites ActiveModel::ArraySerializer.new( object.invite_list, - each_serializer: object.type == "pending" || object.type == "expired" ? InviteSerializer : InvitedUserSerializer, + each_serializer: + ( + if object.type == "pending" || object.type == "expired" + InviteSerializer + else + InvitedUserSerializer + end + ), scope: scope, root: false, - show_emails: object.show_emails + show_emails: object.show_emails, ).as_json end diff --git a/app/serializers/invited_user_record_serializer.rb b/app/serializers/invited_user_record_serializer.rb index 027e8f60da2..66cd1e5d202 100644 --- a/app/serializers/invited_user_record_serializer.rb +++ b/app/serializers/invited_user_record_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class InvitedUserRecordSerializer < BasicUserSerializer - attributes :topics_entered, :posts_read_count, :last_seen_at, @@ -56,5 +55,4 @@ class InvitedUserRecordSerializer < BasicUserSerializer def can_see_invite_details? @can_see_invite_details ||= scope.can_see_invite_details?(invited_by) end - end diff --git a/app/serializers/listable_topic_serializer.rb b/app/serializers/listable_topic_serializer.rb index afcbb6fa6c1..de117bc1026 100644 --- a/app/serializers/listable_topic_serializer.rb +++ b/app/serializers/listable_topic_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ListableTopicSerializer < BasicTopicSerializer - attributes :reply_count, :highest_post_number, :image_url, @@ -114,29 +113,30 @@ class ListableTopicSerializer < BasicTopicSerializer object.excerpt end - alias :include_last_read_post_number? :has_user_data + alias include_last_read_post_number? has_user_data # TODO: For backwards compatibility with themes, # Remove once Discourse 2.8 is released def unread 0 end - alias :include_unread? :has_user_data + alias include_unread? has_user_data # TODO: For backwards compatibility with themes, # Remove once Discourse 2.8 is released def new_posts unread_helper.unread_posts end - alias :include_new_posts? :has_user_data + alias include_new_posts? has_user_data def unread_posts unread_helper.unread_posts end - alias :include_unread_posts? :has_user_data + alias include_unread_posts? has_user_data def include_excerpt? - pinned || SiteSetting.always_include_topic_excerpts || theme_modifier_helper.serialize_topic_excerpts + pinned || SiteSetting.always_include_topic_excerpts || + theme_modifier_helper.serialize_topic_excerpts end def pinned @@ -170,5 +170,4 @@ class ListableTopicSerializer < BasicTopicSerializer def theme_modifier_helper @theme_modifier_helper ||= ThemeModifierHelper.new(request: scope.request) end - end diff --git a/app/serializers/new_post_result_serializer.rb b/app/serializers/new_post_result_serializer.rb index e15af2fad14..f5ad629c808 100644 --- a/app/serializers/new_post_result_serializer.rb +++ b/app/serializers/new_post_result_serializer.rb @@ -1,14 +1,7 @@ # frozen_string_literal: true class NewPostResultSerializer < ApplicationSerializer - attributes :action, - :post, - :errors, - :success, - :pending_count, - :reason, - :message, - :route_to + attributes :action, :post, :errors, :success, :pending_count, :reason, :message, :route_to has_one :pending_post, serializer: TopicPendingPostSerializer, root: false, embed: :objects @@ -81,5 +74,4 @@ class NewPostResultSerializer < ApplicationSerializer def include_message? object.message.present? end - end diff --git a/app/serializers/notification_serializer.rb b/app/serializers/notification_serializer.rb index 40ff319b762..63092363f28 100644 --- a/app/serializers/notification_serializer.rb +++ b/app/serializers/notification_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class NotificationSerializer < ApplicationSerializer - attributes :id, :user_id, :external_id, @@ -47,5 +46,4 @@ class NotificationSerializer < ApplicationSerializer def include_external_id? SiteSetting.enable_discourse_connect end - end diff --git a/app/serializers/permalink_serializer.rb b/app/serializers/permalink_serializer.rb index 78b46fd9a76..7aa66affa29 100644 --- a/app/serializers/permalink_serializer.rb +++ b/app/serializers/permalink_serializer.rb @@ -1,10 +1,22 @@ # frozen_string_literal: true class PermalinkSerializer < ApplicationSerializer - attributes :id, :url, :topic_id, :topic_title, :topic_url, - :post_id, :post_url, :post_number, :post_topic_title, - :category_id, :category_name, :category_url, :external_url, - :tag_id, :tag_name, :tag_url + attributes :id, + :url, + :topic_id, + :topic_title, + :topic_url, + :post_id, + :post_url, + :post_number, + :post_topic_title, + :category_id, + :category_name, + :category_url, + :external_url, + :tag_id, + :tag_name, + :tag_url def topic_title object&.topic&.title diff --git a/app/serializers/post_action_type_serializer.rb b/app/serializers/post_action_type_serializer.rb index 915edb4da1a..a829d91fd8e 100644 --- a/app/serializers/post_action_type_serializer.rb +++ b/app/serializers/post_action_type_serializer.rb @@ -1,16 +1,7 @@ # frozen_string_literal: true class PostActionTypeSerializer < ApplicationSerializer - - attributes( - :id, - :name_key, - :name, - :description, - :short_description, - :is_flag, - :is_custom_flag - ) + attributes(:id, :name_key, :name, :description, :short_description, :is_flag, :is_custom_flag) include ConfigurableUrls @@ -23,15 +14,15 @@ class PostActionTypeSerializer < ApplicationSerializer end def name - i18n('title') + i18n("title") end def description - i18n('description', tos_url: tos_path, base_path: Discourse.base_path) + i18n("description", tos_url: tos_path, base_path: Discourse.base_path) end def short_description - i18n('short_description', tos_url: tos_path, base_path: Discourse.base_path) + i18n("short_description", tos_url: tos_path, base_path: Discourse.base_path) end def name_key diff --git a/app/serializers/post_action_user_serializer.rb b/app/serializers/post_action_user_serializer.rb index 40d81451ac5..b58a5723e3f 100644 --- a/app/serializers/post_action_user_serializer.rb +++ b/app/serializers/post_action_user_serializer.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class PostActionUserSerializer < BasicUserSerializer - attributes :post_url, - :username_lower, - :unknown + attributes :post_url, :username_lower, :unknown def id object.user.id @@ -32,5 +30,4 @@ class PostActionUserSerializer < BasicUserSerializer def include_unknown? (@options[:unknown_user_ids] || []).include?(object.user.id) end - end diff --git a/app/serializers/post_item_excerpt.rb b/app/serializers/post_item_excerpt.rb index e7f9f004153..164eb861dec 100644 --- a/app/serializers/post_item_excerpt.rb +++ b/app/serializers/post_item_excerpt.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module PostItemExcerpt - def self.included(base) base.attributes(:excerpt, :truncated) end @@ -22,5 +21,4 @@ module PostItemExcerpt def include_truncated? cooked.length > 300 end - end diff --git a/app/serializers/post_revision_serializer.rb b/app/serializers/post_revision_serializer.rb index de4430fafc3..46c2835ff09 100644 --- a/app/serializers/post_revision_serializer.rb +++ b/app/serializers/post_revision_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PostRevisionSerializer < ApplicationSerializer - attributes :created_at, :post_id, # which revision is hidden @@ -35,13 +34,9 @@ class PostRevisionSerializer < ApplicationSerializer changes_name = "#{field}_changes".to_sym self.attributes changes_name - define_method(changes_name) do - { previous: previous[field], current: current[field] } - end + define_method(changes_name) { { previous: previous[field], current: current[field] } } - define_method("include_#{changes_name}?") do - previous[field] != current[field] - end + define_method("include_#{changes_name}?") { previous[field] != current[field] } end add_compared_field :wiki @@ -59,9 +54,12 @@ class PostRevisionSerializer < ApplicationSerializer end def previous_revision - @previous_revision ||= revisions.select { |r| r["revision"] >= first_revision } - .select { |r| r["revision"] < current_revision } - .last.try(:[], "revision") + @previous_revision ||= + revisions + .select { |r| r["revision"] >= first_revision } + .select { |r| r["revision"] < current_revision } + .last + .try(:[], "revision") end def current_revision @@ -69,9 +67,12 @@ class PostRevisionSerializer < ApplicationSerializer end def next_revision - @next_revision ||= revisions.select { |r| r["revision"] <= last_revision } - .select { |r| r["revision"] > current_revision } - .first.try(:[], "revision") + @next_revision ||= + revisions + .select { |r| r["revision"] <= last_revision } + .select { |r| r["revision"] > current_revision } + .first + .try(:[], "revision") end def last_revision @@ -108,8 +109,9 @@ class PostRevisionSerializer < ApplicationSerializer def edit_reason # only show 'edit_reason' when revisions are consecutive - current["edit_reason"] if scope.can_view_hidden_post_revisions? || - current["revision"] == previous["revision"] + 1 + if scope.can_view_hidden_post_revisions? || current["revision"] == previous["revision"] + 1 + current["edit_reason"] + end end def body_changes @@ -119,23 +121,20 @@ class PostRevisionSerializer < ApplicationSerializer { inline: cooked_diff.inline_html, side_by_side: cooked_diff.side_by_side_html, - side_by_side_markdown: raw_diff.side_by_side_markdown + side_by_side_markdown: raw_diff.side_by_side_markdown, } end def title_changes - prev = "
    #{previous["title"] && CGI::escapeHTML(previous["title"])}
    " - cur = "
    #{current["title"] && CGI::escapeHTML(current["title"])}
    " + prev = "
    #{previous["title"] && CGI.escapeHTML(previous["title"])}
    " + cur = "
    #{current["title"] && CGI.escapeHTML(current["title"])}
    " # always show the title for post_number == 1 return if object.post.post_number > 1 && prev == cur diff = DiscourseDiff.new(prev, cur) - { - inline: diff.inline_html, - side_by_side: diff.side_by_side_html - } + { inline: diff.inline_html, side_by_side: diff.side_by_side_html } end def user_changes @@ -148,23 +147,23 @@ class PostRevisionSerializer < ApplicationSerializer current = User.find_by(id: cur) || Discourse.system_user { - previous: { - username: previous.username_lower, - display_username: previous.username, - avatar_template: previous.avatar_template - }, - current: { - username: current.username_lower, - display_username: current.username, - avatar_template: current.avatar_template - } + previous: { + username: previous.username_lower, + display_username: previous.username, + avatar_template: previous.avatar_template, + }, + current: { + username: current.username_lower, + display_username: current.username, + avatar_template: current.avatar_template, + }, } end def tags_changes changes = { previous: filter_visible_tags(previous["tags"]), - current: filter_visible_tags(current["tags"]) + current: filter_visible_tags(current["tags"]), } changes[:previous] == changes[:current] ? nil : changes end @@ -184,18 +183,15 @@ class PostRevisionSerializer < ApplicationSerializer end def revisions - @revisions ||= all_revisions.select { |r| scope.can_view_hidden_post_revisions? || !r["hidden"] } + @revisions ||= + all_revisions.select { |r| scope.can_view_hidden_post_revisions? || !r["hidden"] } end def all_revisions return @all_revisions if @all_revisions - post_revisions = PostRevision - .where(post_id: object.post_id) - .order(number: :desc) - .limit(99) - .to_a - .reverse + post_revisions = + PostRevision.where(post_id: object.post_id).order(number: :desc).limit(99).to_a.reverse latest_modifications = { "raw" => [post.raw], @@ -203,24 +199,24 @@ class PostRevisionSerializer < ApplicationSerializer "edit_reason" => [post.edit_reason], "wiki" => [post.wiki], "post_type" => [post.post_type], - "user_id" => [post.user_id] + "user_id" => [post.user_id], } # Retrieve any `tracked_topic_fields` PostRevisor.tracked_topic_fields.each_key do |field| next if field == :tags # Special handling below - if topic.respond_to?(field) - latest_modifications[field.to_s] = [topic.public_send(field)] - end + latest_modifications[field.to_s] = [topic.public_send(field)] if topic.respond_to?(field) end - latest_modifications["featured_link"] = [post.topic.featured_link] if SiteSetting.topic_featured_link_enabled + latest_modifications["featured_link"] = [ + post.topic.featured_link, + ] if SiteSetting.topic_featured_link_enabled latest_modifications["tags"] = [topic.tags.pluck(:name)] if scope.can_see_tags?(topic) post_revisions << PostRevision.new( number: post_revisions.last.number + 1, hidden: post.hidden, - modifications: latest_modifications + modifications: latest_modifications, ) @all_revisions = [] @@ -231,22 +227,20 @@ class PostRevisionSerializer < ApplicationSerializer revision[:revision] = pr.number revision[:hidden] = pr.hidden - pr.modifications.each_key do |field| - revision[field] = pr.modifications[field][0] - end + pr.modifications.each_key { |field| revision[field] = pr.modifications[field][0] } @all_revisions << revision end # waterfall - (@all_revisions.count - 1).downto(1).each do |r| - cur = @all_revisions[r] - prev = @all_revisions[r - 1] + (@all_revisions.count - 1) + .downto(1) + .each do |r| + cur = @all_revisions[r] + prev = @all_revisions[r - 1] - cur.each_key do |field| - prev[field] = prev.has_key?(field) ? prev[field] : cur[field] + cur.each_key { |field| prev[field] = prev.has_key?(field) ? prev[field] : cur[field] } end - end @all_revisions end @@ -272,5 +266,4 @@ class PostRevisionSerializer < ApplicationSerializer tags end end - end diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index 426c31f220a..78e559c031a 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -1,22 +1,19 @@ # frozen_string_literal: true class PostSerializer < BasicPostSerializer - # To pass in additional information we might need - INSTANCE_VARS ||= [ - :parent_post, - :add_raw, - :add_title, - :single_post_link_counts, - :draft_sequence, - :post_actions, - :all_post_actions, - :add_excerpt + INSTANCE_VARS ||= %i[ + parent_post + add_raw + add_title + single_post_link_counts + draft_sequence + post_actions + all_post_actions + add_excerpt ] - INSTANCE_VARS.each do |v| - self.public_send(:attr_accessor, v) - end + INSTANCE_VARS.each { |v| self.public_send(:attr_accessor, v) } attributes :post_number, :post_type, @@ -95,9 +92,7 @@ class PostSerializer < BasicPostSerializer super(object, opts) PostSerializer::INSTANCE_VARS.each do |name| - if opts.include? name - self.public_send("#{name}=", opts[name]) - end + self.public_send("#{name}=", opts[name]) if opts.include? name end end @@ -150,13 +145,14 @@ class PostSerializer < BasicPostSerializer end def include_group_moderator? - @group_moderator ||= begin - if @topic_view - @topic_view.category_group_moderator_user_ids.include?(object.user_id) - else - object&.user&.guardian&.is_category_group_moderator?(object&.topic&.category) + @group_moderator ||= + begin + if @topic_view + @topic_view.category_group_moderator_user_ids.include?(object.user_id) + else + object&.user&.guardian&.is_category_group_moderator?(object&.topic&.category) + end end - end end def yours @@ -260,7 +256,7 @@ class PostSerializer < BasicPostSerializer { username: object.reply_to_user.username, name: object.reply_to_user.name, - avatar_template: object.reply_to_user.avatar_template + avatar_template: object.reply_to_user.avatar_template, } end @@ -290,16 +286,22 @@ class PostSerializer < BasicPostSerializer count = object.public_send(count_col) if object.respond_to?(count_col) summary = { id: id, count: count } - if scope.post_can_act?(object, sym, opts: { taken_actions: actions }, can_see_post: can_see_post) + if scope.post_can_act?( + object, + sym, + opts: { + taken_actions: actions, + }, + can_see_post: can_see_post, + ) summary[:can_act] = true end if sym == :notify_user && - ( - (scope.current_user.present? && scope.current_user == object.user) || - (object.user && object.user.bot?) - ) - + ( + (scope.current_user.present? && scope.current_user == object.user) || + (object.user && object.user.bot?) + ) summary.delete(:can_act) end @@ -316,9 +318,7 @@ class PostSerializer < BasicPostSerializer summary.delete(:count) if summary[:count] == 0 # Only include it if the user can do it or it has a count - if summary[:can_act] || summary[:count] - result << summary - end + result << summary if summary[:can_act] || summary[:count] end result @@ -339,7 +339,8 @@ class PostSerializer < BasicPostSerializer def include_link_counts? return true if @single_post_link_counts.present? - @topic_view.present? && @topic_view.link_counts.present? && @topic_view.link_counts[object.id].present? + @topic_view.present? && @topic_view.link_counts.present? && + @topic_view.link_counts[object.id].present? end def include_read? @@ -496,9 +497,7 @@ class PostSerializer < BasicPostSerializer end def include_last_wiki_edit? - object.wiki && - object.post_number == 1 && - object.revisions.size > 0 + object.wiki && object.post_number == 1 && object.revisions.size > 0 end def include_hidden_reason_id? @@ -563,9 +562,7 @@ class PostSerializer < BasicPostSerializer def mentioned_users if @topic_view && (mentions = @topic_view.mentions[object.id]) - users = mentions - .map { |username| @topic_view.mentioned_users[username] } - .compact + users = mentions.map { |username| @topic_view.mentioned_users[username] }.compact else users = User.where(username: object.mentions) end @@ -573,7 +570,7 @@ class PostSerializer < BasicPostSerializer users.map { |user| BasicUserWithStatusSerializer.new(user, root: false) } end -private + private def can_review_topic? return @can_review_topic unless @can_review_topic.nil? diff --git a/app/serializers/post_stream_serializer_mixin.rb b/app/serializers/post_stream_serializer_mixin.rb index 662e187b82c..e3e8612aab5 100644 --- a/app/serializers/post_stream_serializer_mixin.rb +++ b/app/serializers/post_stream_serializer_mixin.rb @@ -42,17 +42,17 @@ module PostStreamSerializerMixin end def posts - @posts ||= begin - (object.posts || []).map do |post| - post.topic = object.topic + @posts ||= + begin + (object.posts || []).map do |post| + post.topic = object.topic - serializer = PostSerializer.new(post, scope: scope, root: false) - serializer.add_raw = true if @options[:include_raw] - serializer.topic_view = object + serializer = PostSerializer.new(post, scope: scope, root: false) + serializer.add_raw = true if @options[:include_raw] + serializer.topic_view = object - serializer.as_json + serializer.as_json + end end - end end - end diff --git a/app/serializers/post_wordpress_serializer.rb b/app/serializers/post_wordpress_serializer.rb index a415d225833..d8839115d66 100644 --- a/app/serializers/post_wordpress_serializer.rb +++ b/app/serializers/post_wordpress_serializer.rb @@ -11,5 +11,4 @@ class PostWordpressSerializer < BasicPostSerializer nil end end - end diff --git a/app/serializers/queued_post_serializer.rb b/app/serializers/queued_post_serializer.rb index f9dc0dcdefd..91d64f26bfb 100644 --- a/app/serializers/queued_post_serializer.rb +++ b/app/serializers/queued_post_serializer.rb @@ -14,13 +14,13 @@ class QueuedPostSerializer < ApplicationSerializer :post_options, :created_at, :category_id, - :can_delete_user + :can_delete_user, ) has_one :created_by, serializer: AdminUserListSerializer, root: :users has_one :topic, serializer: BasicTopicSerializer def queue - 'default' + "default" end def user_id @@ -40,11 +40,11 @@ class QueuedPostSerializer < ApplicationSerializer end def raw - object.payload['raw'] + object.payload["raw"] end def post_options - object.payload.except('raw') + object.payload.except("raw") end def can_delete_user @@ -58,9 +58,6 @@ class QueuedPostSerializer < ApplicationSerializer private def post_history - object. - reviewable_histories. - transitioned. - order(:created_at) + object.reviewable_histories.transitioned.order(:created_at) end end diff --git a/app/serializers/reviewable_action_serializer.rb b/app/serializers/reviewable_action_serializer.rb index fbba925f27d..469164bc26d 100644 --- a/app/serializers/reviewable_action_serializer.rb +++ b/app/serializers/reviewable_action_serializer.rb @@ -1,7 +1,15 @@ # frozen_string_literal: true class ReviewableActionSerializer < ApplicationSerializer - attributes :id, :icon, :button_class, :label, :confirm_message, :description, :client_action, :require_reject_reason, :custom_modal + attributes :id, + :icon, + :button_class, + :label, + :confirm_message, + :description, + :client_action, + :require_reject_reason, + :custom_modal def label I18n.t(object.label) diff --git a/app/serializers/reviewable_bundled_action_serializer.rb b/app/serializers/reviewable_bundled_action_serializer.rb index 45875ac2bdb..e55241af05d 100644 --- a/app/serializers/reviewable_bundled_action_serializer.rb +++ b/app/serializers/reviewable_bundled_action_serializer.rb @@ -2,7 +2,7 @@ class ReviewableBundledActionSerializer < ApplicationSerializer attributes :id, :icon, :label - has_many :actions, serializer: ReviewableActionSerializer, root: 'actions' + has_many :actions, serializer: ReviewableActionSerializer, root: "actions" def label I18n.t(object.label, default: nil) diff --git a/app/serializers/reviewable_conversation_post_serializer.rb b/app/serializers/reviewable_conversation_post_serializer.rb index cccd746b364..e6608db5f8e 100644 --- a/app/serializers/reviewable_conversation_post_serializer.rb +++ b/app/serializers/reviewable_conversation_post_serializer.rb @@ -2,5 +2,5 @@ class ReviewableConversationPostSerializer < ApplicationSerializer attributes :id, :excerpt - has_one :user, serializer: BasicUserSerializer, root: 'users' + has_one :user, serializer: BasicUserSerializer, root: "users" end diff --git a/app/serializers/reviewable_explanation_serializer.rb b/app/serializers/reviewable_explanation_serializer.rb index f268d12794a..d2cc51c6062 100644 --- a/app/serializers/reviewable_explanation_serializer.rb +++ b/app/serializers/reviewable_explanation_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class ReviewableExplanationSerializer < ApplicationSerializer - attributes( - :id, - :total_score, - :scores, - :min_score_visibility, - :hide_post_score - ) + attributes(:id, :total_score, :scores, :min_score_visibility, :hide_post_score) has_many :scores, serializer: ReviewableScoreExplanationSerializer, embed: :objects diff --git a/app/serializers/reviewable_history_serializer.rb b/app/serializers/reviewable_history_serializer.rb index d4ae2e78383..30feff77301 100644 --- a/app/serializers/reviewable_history_serializer.rb +++ b/app/serializers/reviewable_history_serializer.rb @@ -6,5 +6,5 @@ class ReviewableHistorySerializer < ApplicationSerializer attribute :reviewable_history_type_for_database, key: :reviewable_history_type attribute :status_for_database, key: :status - has_one :created_by, serializer: BasicUserSerializer, root: 'users' + has_one :created_by, serializer: BasicUserSerializer, root: "users" end diff --git a/app/serializers/reviewable_perform_result_serializer.rb b/app/serializers/reviewable_perform_result_serializer.rb index b5ff078fe84..84b8974fa6d 100644 --- a/app/serializers/reviewable_perform_result_serializer.rb +++ b/app/serializers/reviewable_perform_result_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ReviewablePerformResultSerializer < ApplicationSerializer - attributes( :success, :transition_to, @@ -11,7 +10,7 @@ class ReviewablePerformResultSerializer < ApplicationSerializer :remove_reviewable_ids, :version, :reviewable_count, - :unseen_reviewable_count + :unseen_reviewable_count, ) def success diff --git a/app/serializers/reviewable_queued_post_serializer.rb b/app/serializers/reviewable_queued_post_serializer.rb index c2ce6fecf22..94a7918a238 100644 --- a/app/serializers/reviewable_queued_post_serializer.rb +++ b/app/serializers/reviewable_queued_post_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ReviewableQueuedPostSerializer < ReviewableSerializer - attributes :reply_to_post_number payload_attributes( @@ -18,15 +17,14 @@ class ReviewableQueuedPostSerializer < ReviewableSerializer :composer_open_duration_msecs, :tags, :via_email, - :raw_email + :raw_email, ) def reply_to_post_number - object.payload['reply_to_post_number'].to_i + object.payload["reply_to_post_number"].to_i end def include_reply_to_post_number? - object.payload.present? && object.payload['reply_to_post_number'].present? + object.payload.present? && object.payload["reply_to_post_number"].present? end - end diff --git a/app/serializers/reviewable_score_explanation_serializer.rb b/app/serializers/reviewable_score_explanation_serializer.rb index f0082022883..0c100c23929 100644 --- a/app/serializers/reviewable_score_explanation_serializer.rb +++ b/app/serializers/reviewable_score_explanation_serializer.rb @@ -10,6 +10,6 @@ class ReviewableScoreExplanationSerializer < ApplicationSerializer :flags_disagreed, :flags_ignored, :user_accuracy_bonus, - :score + :score, ) end diff --git a/app/serializers/reviewable_score_serializer.rb b/app/serializers/reviewable_score_serializer.rb index 5a4d1f05ded..bbf26b62ce8 100644 --- a/app/serializers/reviewable_score_serializer.rb +++ b/app/serializers/reviewable_score_serializer.rb @@ -2,33 +2,33 @@ class ReviewableScoreSerializer < ApplicationSerializer REASONS_AND_SETTINGS = { - post_count: 'approve_post_count', - trust_level: 'approve_unless_trust_level', - new_topics_unless_trust_level: 'approve_new_topics_unless_trust_level', - fast_typer: 'min_first_post_typing_time', - auto_silence_regex: 'auto_silence_first_post_regex', - staged: 'approve_unless_staged', - must_approve_users: 'must_approve_users', - invite_only: 'invite_only', - email_spam: 'email_in_spam_header', - suspect_user: 'approve_suspect_users', - contains_media: 'review_media_unless_trust_level', + post_count: "approve_post_count", + trust_level: "approve_unless_trust_level", + new_topics_unless_trust_level: "approve_new_topics_unless_trust_level", + fast_typer: "min_first_post_typing_time", + auto_silence_regex: "auto_silence_first_post_regex", + staged: "approve_unless_staged", + must_approve_users: "must_approve_users", + invite_only: "invite_only", + email_spam: "email_in_spam_header", + suspect_user: "approve_suspect_users", + contains_media: "review_media_unless_trust_level", } attributes :id, :score, :agree_stats, :reason, :created_at, :reviewed_at attribute :status_for_database, key: :status - has_one :user, serializer: BasicUserSerializer, root: 'users' + has_one :user, serializer: BasicUserSerializer, root: "users" has_one :score_type, serializer: ReviewableScoreTypeSerializer has_one :reviewable_conversation, serializer: ReviewableConversationSerializer - has_one :reviewed_by, serializer: BasicUserSerializer, root: 'users' + has_one :reviewed_by, serializer: BasicUserSerializer, root: "users" def agree_stats { agreed: user.user_stat.flags_agreed, disagreed: user.user_stat.flags_disagreed, - ignored: user.user_stat.flags_ignored + ignored: user.user_stat.flags_ignored, } end @@ -69,9 +69,9 @@ class ReviewableScoreSerializer < ApplicationSerializer def url_for(reason, text) case reason - when 'watched_word' + when "watched_word" "#{Discourse.base_url}/admin/customize/watched_words" - when 'category' + when "category" "#{Discourse.base_url}/c/#{object.reviewable.category&.slug}/edit/settings" else "#{Discourse.base_url}/admin/site_settings/category/all_results?filter=#{text}" @@ -79,8 +79,8 @@ class ReviewableScoreSerializer < ApplicationSerializer end def build_link_for(reason, text) - return text.gsub('_', ' ') unless scope.is_staff? + return text.gsub("_", " ") unless scope.is_staff? - "#{text.gsub('_', ' ')}" + "#{text.gsub("_", " ")}" end end diff --git a/app/serializers/reviewable_score_type_serializer.rb b/app/serializers/reviewable_score_type_serializer.rb index 60368ebcbce..e31e5fafd9a 100644 --- a/app/serializers/reviewable_score_type_serializer.rb +++ b/app/serializers/reviewable_score_type_serializer.rb @@ -20,5 +20,4 @@ class ReviewableScoreTypeSerializer < ApplicationSerializer def icon "flag" end - end diff --git a/app/serializers/reviewable_serializer.rb b/app/serializers/reviewable_serializer.rb index 2260a4f9db1..20c02e4b72e 100644 --- a/app/serializers/reviewable_serializer.rb +++ b/app/serializers/reviewable_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ReviewableSerializer < ApplicationSerializer - class_attribute :_payload_for_serialization attributes( @@ -16,18 +15,18 @@ class ReviewableSerializer < ApplicationSerializer :can_edit, :score, :version, - :target_created_by_trust_level + :target_created_by_trust_level, ) attribute :status_for_database, key: :status - has_one :created_by, serializer: UserWithCustomFieldsSerializer, root: 'users' - has_one :target_created_by, serializer: UserWithCustomFieldsSerializer, root: 'users' + has_one :created_by, serializer: UserWithCustomFieldsSerializer, root: "users" + has_one :target_created_by, serializer: UserWithCustomFieldsSerializer, root: "users" has_one :topic, serializer: ListableTopicSerializer has_many :editable_fields, serializer: ReviewableEditableFieldSerializer, embed: :objects has_many :reviewable_scores, serializer: ReviewableScoreSerializer has_many :bundled_actions, serializer: ReviewableBundledActionSerializer - has_one :claimed_by, serializer: UserWithCustomFieldsSerializer, root: 'users' + has_one :claimed_by, serializer: UserWithCustomFieldsSerializer, root: "users" # Used to keep track of our payload attributes class_attribute :_payload_for_serialization @@ -73,9 +72,7 @@ class ReviewableSerializer < ApplicationSerializer # This is easier than creating an AMS method for each attribute def self.target_attributes(*attributes) - attributes.each do |a| - create_attribute(a, "object.target&.#{a}") - end + attributes.each { |a| create_attribute(a, "object.target&.#{a}") } end def self.payload_attributes(*attributes) @@ -108,7 +105,9 @@ class ReviewableSerializer < ApplicationSerializer end def target_url - return Discourse.base_url + object.target.url if object.target.is_a?(Post) && object.target.present? + if object.target.is_a?(Post) && object.target.present? + return Discourse.base_url + object.target.url + end topic_url end @@ -135,5 +134,4 @@ class ReviewableSerializer < ApplicationSerializer def target_created_by_trust_level object&.target_created_by&.trust_level end - end diff --git a/app/serializers/reviewable_settings_serializer.rb b/app/serializers/reviewable_settings_serializer.rb index f2a34e4e962..5b21101000a 100644 --- a/app/serializers/reviewable_settings_serializer.rb +++ b/app/serializers/reviewable_settings_serializer.rb @@ -14,8 +14,6 @@ class ReviewableSettingsSerializer < ApplicationSerializer end def reviewable_priorities - Reviewable.priorities.map do |p| - { id: p[1], name: I18n.t("reviewables.priorities.#{p[0]}") } - end + Reviewable.priorities.map { |p| { id: p[1], name: I18n.t("reviewables.priorities.#{p[0]}") } } end end diff --git a/app/serializers/reviewable_topic_serializer.rb b/app/serializers/reviewable_topic_serializer.rb index 70e0d7fa036..59bf143f869 100644 --- a/app/serializers/reviewable_topic_serializer.rb +++ b/app/serializers/reviewable_topic_serializer.rb @@ -12,10 +12,10 @@ class ReviewableTopicSerializer < ApplicationSerializer :archetype, :relative_url, :stats, - :reviewable_score + :reviewable_score, ) - has_one :claimed_by, serializer: BasicUserSerializer, root: 'users' + has_one :claimed_by, serializer: BasicUserSerializer, root: "users" def stats @options[:stats][object.id] @@ -28,5 +28,4 @@ class ReviewableTopicSerializer < ApplicationSerializer def include_claimed_by? @options[:claimed_topics] end - end diff --git a/app/serializers/reviewable_user_serializer.rb b/app/serializers/reviewable_user_serializer.rb index 5841899cfd3..c5173382012 100644 --- a/app/serializers/reviewable_user_serializer.rb +++ b/app/serializers/reviewable_user_serializer.rb @@ -1,16 +1,9 @@ # frozen_string_literal: true class ReviewableUserSerializer < ReviewableSerializer - attributes :link_admin, :user_fields, :reject_reason - payload_attributes( - :username, - :email, - :name, - :bio, - :website - ) + payload_attributes(:username, :email, :name, :bio, :website) def link_admin scope.is_staff? && object.target.present? @@ -23,5 +16,4 @@ class ReviewableUserSerializer < ReviewableSerializer def include_user_fields? object.target.present? && object.target.user_fields.present? end - end diff --git a/app/serializers/screened_email_serializer.rb b/app/serializers/screened_email_serializer.rb index bb38bd9093e..799d122a3f1 100644 --- a/app/serializers/screened_email_serializer.rb +++ b/app/serializers/screened_email_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class ScreenedEmailSerializer < ApplicationSerializer - attributes :email, - :action, - :match_count, - :last_match_at, - :created_at, - :ip_address, - :id + attributes :email, :action, :match_count, :last_match_at, :created_at, :ip_address, :id def action ScreenedEmail.actions.key(object.action_type).to_s @@ -16,5 +10,4 @@ class ScreenedEmailSerializer < ApplicationSerializer def ip_address object.ip_address.try(:to_s) end - end diff --git a/app/serializers/screened_ip_address_serializer.rb b/app/serializers/screened_ip_address_serializer.rb index f833e0b5fd2..75853a2aaab 100644 --- a/app/serializers/screened_ip_address_serializer.rb +++ b/app/serializers/screened_ip_address_serializer.rb @@ -1,12 +1,7 @@ # frozen_string_literal: true class ScreenedIpAddressSerializer < ApplicationSerializer - attributes :id, - :ip_address, - :action_name, - :match_count, - :last_match_at, - :created_at + attributes :id, :ip_address, :action_name, :match_count, :last_match_at, :created_at def action_name ScreenedIpAddress.actions.key(object.action_type).to_s @@ -15,5 +10,4 @@ class ScreenedIpAddressSerializer < ApplicationSerializer def ip_address object.ip_address_with_mask end - end diff --git a/app/serializers/screened_url_serializer.rb b/app/serializers/screened_url_serializer.rb index 7c071349fae..c4f561071ec 100644 --- a/app/serializers/screened_url_serializer.rb +++ b/app/serializers/screened_url_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class ScreenedUrlSerializer < ApplicationSerializer - attributes :url, - :domain, - :action, - :match_count, - :last_match_at, - :created_at, - :ip_address + attributes :url, :domain, :action, :match_count, :last_match_at, :created_at, :ip_address def action ScreenedUrl.actions.key(object.action_type).to_s @@ -16,5 +10,4 @@ class ScreenedUrlSerializer < ApplicationSerializer def ip_address object.ip_address.try(:to_s) end - end diff --git a/app/serializers/search_logs_serializer.rb b/app/serializers/search_logs_serializer.rb index 81f70e0694c..910aeb66069 100644 --- a/app/serializers/search_logs_serializer.rb +++ b/app/serializers/search_logs_serializer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true class SearchLogsSerializer < ApplicationSerializer - attributes :term, - :searches, - :ctr + attributes :term, :searches, :ctr end diff --git a/app/serializers/similar_admin_user_serializer.rb b/app/serializers/similar_admin_user_serializer.rb index 3f343ed8416..542d4885c28 100644 --- a/app/serializers/similar_admin_user_serializer.rb +++ b/app/serializers/similar_admin_user_serializer.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true class SimilarAdminUserSerializer < AdminUserListSerializer - attributes :can_be_suspended, - :can_be_silenced + attributes :can_be_suspended, :can_be_silenced def can_be_suspended scope.can_suspend?(object) diff --git a/app/serializers/similar_topic_serializer.rb b/app/serializers/similar_topic_serializer.rb index 68d6ec73744..7c73730f2ba 100644 --- a/app/serializers/similar_topic_serializer.rb +++ b/app/serializers/similar_topic_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class SimilarTopicSerializer < ApplicationSerializer - has_one :topic, serializer: TopicListItemSerializer, embed: :ids attributes :id, :blurb, :created_at, :url diff --git a/app/serializers/single_sign_on_record_serializer.rb b/app/serializers/single_sign_on_record_serializer.rb index 55af0fb7bcb..5bc88037159 100644 --- a/app/serializers/single_sign_on_record_serializer.rb +++ b/app/serializers/single_sign_on_record_serializer.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true class SingleSignOnRecordSerializer < ApplicationSerializer - attributes :user_id, :external_id, - :created_at, :updated_at, - :external_username, :external_name, + attributes :user_id, + :external_id, + :created_at, + :updated_at, + :external_username, + :external_name, :external_avatar_url, :external_profile_background_url, :external_card_background_url diff --git a/app/serializers/site_category_serializer.rb b/app/serializers/site_category_serializer.rb index 1d29b7c7af0..869e9b7868a 100644 --- a/app/serializers/site_category_serializer.rb +++ b/app/serializers/site_category_serializer.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class SiteCategorySerializer < BasicCategorySerializer - - attributes :allowed_tags, - :allowed_tag_groups, - :allow_global_tags, - :read_only_banner + attributes :allowed_tags, :allowed_tag_groups, :allow_global_tags, :read_only_banner has_many :category_required_tag_groups, key: :required_tag_groups, embed: :objects diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 0ddc329fd8e..4aa21862328 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class SiteSerializer < ApplicationSerializer - attributes( :default_archetype, :notification_types, @@ -40,7 +39,7 @@ class SiteSerializer < ApplicationSerializer :displayed_about_plugin_stat_groups, :show_welcome_topic_banner, :anonymous_default_sidebar_tags, - :whispers_allowed_groups_names + :whispers_allowed_groups_names, ) has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer @@ -49,19 +48,29 @@ class SiteSerializer < ApplicationSerializer def user_themes cache_fragment("user_themes") do - Theme.where('id = :default OR user_selectable', - default: SiteSetting.default_theme_id) + Theme + .where("id = :default OR user_selectable", default: SiteSetting.default_theme_id) .order("lower(name)") .pluck(:id, :name, :color_scheme_id) - .map { |id, n, cs| { theme_id: id, name: n, default: id == SiteSetting.default_theme_id, color_scheme_id: cs } } + .map do |id, n, cs| + { + theme_id: id, + name: n, + default: id == SiteSetting.default_theme_id, + color_scheme_id: cs, + } + end .as_json end end def user_color_schemes cache_fragment("user_color_schemes") do - schemes = ColorScheme.includes(:color_scheme_colors).where('user_selectable').order(:name) - ActiveModel::ArraySerializer.new(schemes, each_serializer: ColorSchemeSelectableSerializer).as_json + schemes = ColorScheme.includes(:color_scheme_colors).where("user_selectable").order(:name) + ActiveModel::ArraySerializer.new( + schemes, + each_serializer: ColorSchemeSelectableSerializer, + ).as_json end end @@ -71,7 +80,9 @@ class SiteSerializer < ApplicationSerializer def groups cache_anon_fragment("group_names") do - object.groups.order(:name) + object + .groups + .order(:name) .select(:id, :name, :flair_icon, :flair_upload_id, :flair_bg_color, :flair_color) .map do |g| { @@ -81,7 +92,8 @@ class SiteSerializer < ApplicationSerializer flair_bg_color: g.flair_bg_color, flair_color: g.flair_color, } - end.as_json + end + .as_json end end @@ -244,7 +256,8 @@ class SiteSerializer < ApplicationSerializer end def include_anonymous_default_sidebar_tags? - scope.anonymous? && !SiteSetting.legacy_navigation_menu? && SiteSetting.tagging_enabled && SiteSetting.default_sidebar_tags.present? + scope.anonymous? && !SiteSetting.legacy_navigation_menu? && SiteSetting.tagging_enabled && + SiteSetting.default_sidebar_tags.present? end def whispers_allowed_groups_names diff --git a/app/serializers/suggested_topic_serializer.rb b/app/serializers/suggested_topic_serializer.rb index f2ad535fbbe..5796ede9121 100644 --- a/app/serializers/suggested_topic_serializer.rb +++ b/app/serializers/suggested_topic_serializer.rb @@ -10,7 +10,12 @@ class SuggestedTopicSerializer < ListableTopicSerializer has_one :user, serializer: BasicUserSerializer, embed: :objects end - attributes :archetype, :like_count, :views, :category_id, :featured_link, :featured_link_root_domain + attributes :archetype, + :like_count, + :views, + :category_id, + :featured_link, + :featured_link_root_domain has_many :posters, serializer: SuggestedPosterSerializer, embed: :objects def posters diff --git a/app/serializers/suggested_topics_mixin.rb b/app/serializers/suggested_topics_mixin.rb index cf0de7b4a10..a899d73f7d1 100644 --- a/app/serializers/suggested_topics_mixin.rb +++ b/app/serializers/suggested_topics_mixin.rb @@ -26,10 +26,12 @@ module SuggestedTopicsMixin return if object.topic.topic_allowed_users.exists?(user_id: scope.user.id) if object.topic_allowed_group_ids.present? - Group.joins(:group_users) + Group + .joins(:group_users) .where( "group_users.group_id IN (?) AND group_users.user_id = ?", - object.topic_allowed_group_ids, scope.user.id + object.topic_allowed_group_ids, + scope.user.id, ) .pluck_first(:name) end diff --git a/app/serializers/tag_group_serializer.rb b/app/serializers/tag_group_serializer.rb index db61936fceb..6ccdd3e2d66 100644 --- a/app/serializers/tag_group_serializer.rb +++ b/app/serializers/tag_group_serializer.rb @@ -12,17 +12,22 @@ class TagGroupSerializer < ApplicationSerializer end def permissions - @permissions ||= begin - h = {} + @permissions ||= + begin + h = {} - object.tag_group_permissions.joins(:group).includes(:group).find_each do |tgp| - name = Group::AUTO_GROUP_IDS.fetch(tgp.group_id, tgp.group.name).to_s - h[name] = tgp.permission_type + object + .tag_group_permissions + .joins(:group) + .includes(:group) + .find_each do |tgp| + name = Group::AUTO_GROUP_IDS.fetch(tgp.group_id, tgp.group.name).to_s + h[name] = tgp.permission_type + end + + h["everyone"] = TagGroupPermission.permission_types[:full] if h.empty? + + h end - - h["everyone"] = TagGroupPermission.permission_types[:full] if h.empty? - - h - end end end diff --git a/app/serializers/theme_serializer.rb b/app/serializers/theme_serializer.rb index b0edeaa7c4d..d3307a6825d 100644 --- a/app/serializers/theme_serializer.rb +++ b/app/serializers/theme_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'base64' +require "base64" class ThemeFieldSerializer < ApplicationSerializer attributes :name, :target, :value, :error, :type_id, :upload_id, :url, :filename @@ -47,9 +47,23 @@ class BasicThemeSerializer < ApplicationSerializer end class RemoteThemeSerializer < ApplicationSerializer - attributes :id, :remote_url, :remote_version, :local_version, :commits_behind, :branch, - :remote_updated_at, :updated_at, :github_diff_link, :last_error_text, :is_git?, - :license_url, :about_url, :authors, :theme_version, :minimum_discourse_version, :maximum_discourse_version + attributes :id, + :remote_url, + :remote_version, + :local_version, + :commits_behind, + :branch, + :remote_updated_at, + :updated_at, + :github_diff_link, + :last_error_text, + :is_git?, + :license_url, + :about_url, + :authors, + :theme_version, + :minimum_discourse_version, + :maximum_discourse_version # wow, AMS has some pretty nutty logic where it tries to find the path here # from action dispatch, tell it not to @@ -63,9 +77,17 @@ class RemoteThemeSerializer < ApplicationSerializer end class ThemeSerializer < BasicThemeSerializer - attributes :color_scheme, :color_scheme_id, :user_selectable, :auto_update, - :remote_theme_id, :settings, :errors, :supported?, :description, - :enabled?, :disabled_at + attributes :color_scheme, + :color_scheme_id, + :user_selectable, + :auto_update, + :remote_theme_id, + :settings, + :errors, + :supported?, + :description, + :enabled?, + :disabled_at has_one :user, serializer: UserNameSerializer, embed: :object has_one :disabled_by, serializer: UserNameSerializer, embed: :object @@ -80,9 +102,7 @@ class ThemeSerializer < BasicThemeSerializer super @errors = [] - object.theme_fields.each do |o| - @errors << o.error if o.error - end + object.theme_fields.each { |o| @errors << o.error if o.error } end def child_themes @@ -113,7 +133,7 @@ class ThemeSerializer < BasicThemeSerializer end def description - object.internal_translations.find { |t| t.key == "theme_metadata.description" } &.value + object.internal_translations.find { |t| t.key == "theme_metadata.description" }&.value end def include_disabled_at? diff --git a/app/serializers/theme_settings_serializer.rb b/app/serializers/theme_settings_serializer.rb index 987003d06a1..cbd01da5a87 100644 --- a/app/serializers/theme_settings_serializer.rb +++ b/app/serializers/theme_settings_serializer.rb @@ -1,8 +1,15 @@ # frozen_string_literal: true class ThemeSettingsSerializer < ApplicationSerializer - attributes :setting, :type, :default, :value, :description, :valid_values, - :list_type, :textarea, :json_schema + attributes :setting, + :type, + :default, + :value, + :description, + :valid_values, + :list_type, + :textarea, + :json_schema def setting object.name @@ -21,7 +28,12 @@ class ThemeSettingsSerializer < ApplicationSerializer end def description - locale_file_description = object.theme.internal_translations.find { |t| t.key == "theme_metadata.settings.#{setting}" } &.value + locale_file_description = + object + .theme + .internal_translations + .find { |t| t.key == "theme_metadata.settings.#{setting}" } + &.value locale_file_description || object.description end diff --git a/app/serializers/topic_embed_serializer.rb b/app/serializers/topic_embed_serializer.rb index d31ab6e89a9..928d3ec5d04 100644 --- a/app/serializers/topic_embed_serializer.rb +++ b/app/serializers/topic_embed_serializer.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class TopicEmbedSerializer < ApplicationSerializer - attributes \ - :topic_id, - :post_id, - :topic_slug, - :comment_count + attributes :topic_id, :post_id, :topic_slug, :comment_count def topic_slug object.topic.slug diff --git a/app/serializers/topic_flag_type_serializer.rb b/app/serializers/topic_flag_type_serializer.rb index 6532f4918db..ccee54a0aaf 100644 --- a/app/serializers/topic_flag_type_serializer.rb +++ b/app/serializers/topic_flag_type_serializer.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true class TopicFlagTypeSerializer < PostActionTypeSerializer - protected def i18n(field, vars = nil) key = "topic_flag_types.#{name_key}.#{field}" vars ? I18n.t(key, vars) : I18n.t(key) end - end diff --git a/app/serializers/topic_link_serializer.rb b/app/serializers/topic_link_serializer.rb index d3d3fb686c4..d4b814d2c98 100644 --- a/app/serializers/topic_link_serializer.rb +++ b/app/serializers/topic_link_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TopicLinkSerializer < ApplicationSerializer - attributes :url, :title, # :fancy_title, @@ -12,10 +11,9 @@ class TopicLinkSerializer < ApplicationSerializer :user_id, :domain, :root_domain, - - def attachment - Discourse.store.has_been_uploaded?(object.url) - end + def attachment + Discourse.store.has_been_uploaded?(object.url) + end def include_user_id? object.user_id.present? @@ -24,5 +22,4 @@ class TopicLinkSerializer < ApplicationSerializer def root_domain MiniSuffix.domain(domain) end - end diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb index 4a8f12b37c8..3d43c0848a8 100644 --- a/app/serializers/topic_list_item_serializer.rb +++ b/app/serializers/topic_list_item_serializer.rb @@ -32,7 +32,6 @@ class TopicListItemSerializer < ListableTopicSerializer end def category_id - # If it's a shared draft, show the destination topic instead if object.includes_destination_category && object.shared_draft return object.shared_draft.category_id @@ -50,8 +49,7 @@ class TopicListItemSerializer < ListableTopicSerializer end def include_post_action?(action) - object.user_data && - object.user_data.post_action_data && + object.user_data && object.user_data.post_action_data && object.user_data.post_action_data.key?(PostActionType.types[action]) end @@ -86,5 +84,4 @@ class TopicListItemSerializer < ListableTopicSerializer def include_allowed_user_count? object.private_message? end - end diff --git a/app/serializers/topic_list_serializer.rb b/app/serializers/topic_list_serializer.rb index a3ece4822f2..5e78f344a3e 100644 --- a/app/serializers/topic_list_serializer.rb +++ b/app/serializers/topic_list_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TopicListSerializer < ApplicationSerializer - attributes :can_create_topic, :more_topics_url, :for_period, diff --git a/app/serializers/topic_pending_post_serializer.rb b/app/serializers/topic_pending_post_serializer.rb index c07fc479e5e..da02c073583 100644 --- a/app/serializers/topic_pending_post_serializer.rb +++ b/app/serializers/topic_pending_post_serializer.rb @@ -4,11 +4,10 @@ class TopicPendingPostSerializer < ApplicationSerializer attributes :id, :raw, :created_at def raw - object.payload['raw'] + object.payload["raw"] end def include_raw? - object.payload && object.payload['raw'].present? + object.payload && object.payload["raw"].present? end - end diff --git a/app/serializers/topic_post_count_serializer.rb b/app/serializers/topic_post_count_serializer.rb index 90f5a89fa49..4495019bec5 100644 --- a/app/serializers/topic_post_count_serializer.rb +++ b/app/serializers/topic_post_count_serializer.rb @@ -1,14 +1,18 @@ # frozen_string_literal: true class TopicPostCountSerializer < BasicUserSerializer - - attributes :post_count, :primary_group_name, - :flair_name, :flair_url, :flair_color, :flair_bg_color, - :admin, :moderator, :trust_level, - - def id - object[:user].id - end + attributes :post_count, + :primary_group_name, + :flair_name, + :flair_url, + :flair_color, + :flair_bg_color, + :admin, + :moderator, + :trust_level, + def id + object[:user].id + end def username object[:user].username @@ -58,5 +62,4 @@ class TopicPostCountSerializer < BasicUserSerializer def trust_level object[:user].trust_level end - end diff --git a/app/serializers/topic_timer_serializer.rb b/app/serializers/topic_timer_serializer.rb index b7f3b88788d..9f9934f6342 100644 --- a/app/serializers/topic_timer_serializer.rb +++ b/app/serializers/topic_timer_serializer.rb @@ -1,12 +1,7 @@ # frozen_string_literal: true class TopicTimerSerializer < ApplicationSerializer - attributes :id, - :execute_at, - :duration_minutes, - :based_on_last_post, - :status_type, - :category_id + attributes :id, :execute_at, :duration_minutes, :based_on_last_post, :status_type, :category_id def status_type TopicTimer.types[object.status_type] diff --git a/app/serializers/topic_view_details_serializer.rb b/app/serializers/topic_view_details_serializer.rb index ffe51d4db33..22ae968e3fb 100644 --- a/app/serializers/topic_view_details_serializer.rb +++ b/app/serializers/topic_view_details_serializer.rb @@ -1,29 +1,30 @@ # frozen_string_literal: true class TopicViewDetailsSerializer < ApplicationSerializer - def self.can_attributes - [:can_move_posts, - :can_delete, - :can_permanently_delete, - :can_recover, - :can_remove_allowed_users, - :can_invite_to, - :can_invite_via_email, - :can_create_post, - :can_reply_as_new_topic, - :can_flag_topic, - :can_convert_topic, - :can_review_topic, - :can_edit_tags, - :can_publish_page, - :can_close_topic, - :can_archive_topic, - :can_split_merge_topic, - :can_edit_staff_notes, - :can_toggle_topic_visibility, - :can_pin_unpin_topic, - :can_moderate_category] + %i[ + can_move_posts + can_delete + can_permanently_delete + can_recover + can_remove_allowed_users + can_invite_to + can_invite_via_email + can_create_post + can_reply_as_new_topic + can_flag_topic + can_convert_topic + can_review_topic + can_edit_tags + can_publish_page + can_close_topic + can_archive_topic + can_split_merge_topic + can_edit_staff_notes + can_toggle_topic_visibility + can_pin_unpin_topic + can_moderate_category + ] end # NOTE: `can_edit` is defined as an attribute because we explicitly want @@ -35,7 +36,7 @@ class TopicViewDetailsSerializer < ApplicationSerializer *can_attributes, :can_remove_self_id, :participants, - :allowed_users + :allowed_users, ) has_one :created_by, serializer: BasicUserSerializer, embed: :objects @@ -46,9 +47,10 @@ class TopicViewDetailsSerializer < ApplicationSerializer has_many :allowed_groups, serializer: BasicGroupSerializer, embed: :objects def participants - object.post_counts_by_user.reject { |p| object.participants[p].blank? }.map do |pc| - { user: object.participants[pc[0]], post_count: pc[1] } - end + object + .post_counts_by_user + .reject { |p| object.participants[p].blank? } + .map { |pc| { user: object.participants[pc[0]], post_count: pc[1] } } end def include_participants? @@ -88,9 +90,7 @@ class TopicViewDetailsSerializer < ApplicationSerializer scope.can_remove_allowed_users?(object.topic, scope.user) end - can_attributes.each do |ca| - define_method(ca) { true } - end + can_attributes.each { |ca| define_method(ca) { true } } # NOTE: A Category Group Moderator moving a topic to a different category # may result in the 'can_edit?' result changing from `true` to `false`. @@ -160,13 +160,14 @@ class TopicViewDetailsSerializer < ApplicationSerializer end def can_perform_action_available_to_group_moderators? - @can_perform_action_available_to_group_moderators ||= scope.can_perform_action_available_to_group_moderators?(object.topic) + @can_perform_action_available_to_group_moderators ||= + scope.can_perform_action_available_to_group_moderators?(object.topic) end - alias :include_can_close_topic? :can_perform_action_available_to_group_moderators? - alias :include_can_archive_topic? :can_perform_action_available_to_group_moderators? - alias :include_can_split_merge_topic? :can_perform_action_available_to_group_moderators? - alias :include_can_edit_staff_notes? :can_perform_action_available_to_group_moderators? - alias :include_can_moderate_category? :can_perform_action_available_to_group_moderators? + alias include_can_close_topic? can_perform_action_available_to_group_moderators? + alias include_can_archive_topic? can_perform_action_available_to_group_moderators? + alias include_can_split_merge_topic? can_perform_action_available_to_group_moderators? + alias include_can_edit_staff_notes? can_perform_action_available_to_group_moderators? + alias include_can_moderate_category? can_perform_action_available_to_group_moderators? def include_can_publish_page? scope.can_publish_page?(object.topic) @@ -189,5 +190,4 @@ class TopicViewDetailsSerializer < ApplicationSerializer def include_allowed_groups? object.personal_message end - end diff --git a/app/serializers/topic_view_posts_serializer.rb b/app/serializers/topic_view_posts_serializer.rb index 8afcac3e9ff..8dbb4d2b059 100644 --- a/app/serializers/topic_view_posts_serializer.rb +++ b/app/serializers/topic_view_posts_serializer.rb @@ -21,5 +21,4 @@ class TopicViewPostsSerializer < ApplicationSerializer def include_timeline_lookup? false end - end diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb index 57bd35aaf48..91eb2a6f2f1 100644 --- a/app/serializers/topic_view_serializer.rb +++ b/app/serializers/topic_view_serializer.rb @@ -42,7 +42,7 @@ class TopicViewSerializer < ApplicationSerializer :pinned_until, :image_url, :slow_mode_seconds, - :external_id + :external_id, ) attributes( @@ -77,7 +77,7 @@ class TopicViewSerializer < ApplicationSerializer :thumbnails, :user_last_posted_at, :is_shared_draft, - :slow_mode_enabled_until + :slow_mode_enabled_until, ) has_one :details, serializer: TopicViewDetailsSerializer, root: false, embed: :objects @@ -247,7 +247,8 @@ class TopicViewSerializer < ApplicationSerializer end def include_destination_category_id? - scope.can_see_shared_draft? && SiteSetting.shared_drafts_enabled? && object.topic.shared_draft.present? + scope.can_see_shared_draft? && SiteSetting.shared_drafts_enabled? && + object.topic.shared_draft.present? end def is_shared_draft @@ -276,20 +277,21 @@ class TopicViewSerializer < ApplicationSerializer Group .joins(:group_users) .where( - id: object.topic.custom_fields['requested_group_id'].to_i, - group_users: { user_id: scope.user.id, owner: true } + id: object.topic.custom_fields["requested_group_id"].to_i, + group_users: { + user_id: scope.user.id, + owner: true, + }, ) .pluck_first(:name) end def include_requested_group_name? - object.personal_message && object.topic.custom_fields['requested_group_id'] + object.personal_message && object.topic.custom_fields["requested_group_id"] end def include_published_page? - SiteSetting.enable_page_publishing? && - scope.is_staff? && - object.published_page.present? && + SiteSetting.enable_page_publishing? && scope.is_staff? && object.published_page.present? && !SiteSetting.secure_uploads end diff --git a/app/serializers/topic_view_wordpress_serializer.rb b/app/serializers/topic_view_wordpress_serializer.rb index b4e76103190..ce94b630b41 100644 --- a/app/serializers/topic_view_wordpress_serializer.rb +++ b/app/serializers/topic_view_wordpress_serializer.rb @@ -1,13 +1,8 @@ # frozen_string_literal: true class TopicViewWordpressSerializer < ApplicationSerializer - # These attributes will be delegated to the topic - attributes :id, - :category_id, - :posts_count, - :filtered_posts_count, - :posts + attributes :id, :category_id, :posts_count, :filtered_posts_count, :posts has_many :participants, serializer: UserWordpressSerializer, embed: :objects has_many :posts, serializer: PostWordpressSerializer, embed: :objects @@ -35,5 +30,4 @@ class TopicViewWordpressSerializer < ApplicationSerializer def posts object.posts end - end diff --git a/app/serializers/trust_level3_requirements_serializer.rb b/app/serializers/trust_level3_requirements_serializer.rb index 2cf83317595..690e9682391 100644 --- a/app/serializers/trust_level3_requirements_serializer.rb +++ b/app/serializers/trust_level3_requirements_serializer.rb @@ -1,25 +1,37 @@ # frozen_string_literal: true class TrustLevel3RequirementsSerializer < ApplicationSerializer - has_one :penalty_counts, embed: :object, serializer: PenaltyCountsSerializer attributes :time_period, :requirements_met, :requirements_lost, - :trust_level_locked, :on_grace_period, - :days_visited, :min_days_visited, - :num_topics_replied_to, :min_topics_replied_to, - :topics_viewed, :min_topics_viewed, - :posts_read, :min_posts_read, - :topics_viewed_all_time, :min_topics_viewed_all_time, - :posts_read_all_time, :min_posts_read_all_time, - :num_flagged_posts, :max_flagged_posts, - :num_flagged_by_users, :max_flagged_by_users, - :num_likes_given, :min_likes_given, - :num_likes_received, :min_likes_received, - :num_likes_received_days, :min_likes_received_days, - :num_likes_received_users, :min_likes_received_users + :trust_level_locked, + :on_grace_period, + :days_visited, + :min_days_visited, + :num_topics_replied_to, + :min_topics_replied_to, + :topics_viewed, + :min_topics_viewed, + :posts_read, + :min_posts_read, + :topics_viewed_all_time, + :min_topics_viewed_all_time, + :posts_read_all_time, + :min_posts_read_all_time, + :num_flagged_posts, + :max_flagged_posts, + :num_flagged_by_users, + :max_flagged_by_users, + :num_likes_given, + :min_likes_given, + :num_likes_received, + :min_likes_received, + :num_likes_received_days, + :min_likes_received_days, + :num_likes_received_users, + :min_likes_received_users def requirements_met object.requirements_met? diff --git a/app/serializers/upload_serializer.rb b/app/serializers/upload_serializer.rb index 7626c0e1e23..eb32b011813 100644 --- a/app/serializers/upload_serializer.rb +++ b/app/serializers/upload_serializer.rb @@ -17,6 +17,10 @@ class UploadSerializer < ApplicationSerializer :dominant_color def url - object.for_site_setting ? object.url : UrlHelper.cook_url(object.url, secure: SiteSetting.secure_uploads? && object.secure) + if object.for_site_setting + object.url + else + UrlHelper.cook_url(object.url, secure: SiteSetting.secure_uploads? && object.secure) + end end end diff --git a/app/serializers/user_action_serializer.rb b/app/serializers/user_action_serializer.rb index 29ed526dc06..0958de3d9dd 100644 --- a/app/serializers/user_action_serializer.rb +++ b/app/serializers/user_action_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'post_item_excerpt' +require_relative "post_item_excerpt" class UserActionSerializer < ApplicationSerializer include PostItemExcerpt @@ -98,5 +98,4 @@ class UserActionSerializer < ApplicationSerializer def action_code_path object.action_code_path end - end diff --git a/app/serializers/user_badge_serializer.rb b/app/serializers/user_badge_serializer.rb index 4b2b536ef5a..752ae5c8341 100644 --- a/app/serializers/user_badge_serializer.rb +++ b/app/serializers/user_badge_serializer.rb @@ -6,9 +6,7 @@ class UserBadgeSerializer < ApplicationSerializer class UserSerializer < BasicUserSerializer include UserPrimaryGroupMixin - attributes :name, - :moderator, - :admin + attributes :name, :moderator, :admin end attributes :id, :granted_at, :created_at, :count, :post_id, :post_number @@ -26,7 +24,7 @@ class UserBadgeSerializer < ApplicationSerializer include_post_attributes? end - alias :include_post_number? :include_post_id? + alias include_post_number? include_post_id? def post_number object.post && object.post.post_number diff --git a/app/serializers/user_bookmark_list_serializer.rb b/app/serializers/user_bookmark_list_serializer.rb index cefdd95114c..b70349a1e83 100644 --- a/app/serializers/user_bookmark_list_serializer.rb +++ b/app/serializers/user_bookmark_list_serializer.rb @@ -9,7 +9,7 @@ class UserBookmarkListSerializer < ApplicationSerializer bm, **object.bookmark_serializer_opts, scope: scope, - root: false + root: false, ) end end diff --git a/app/serializers/user_card_serializer.rb b/app/serializers/user_card_serializer.rb index 5e208169c7a..d2c32facdc9 100644 --- a/app/serializers/user_card_serializer.rb +++ b/app/serializers/user_card_serializer.rb @@ -73,11 +73,7 @@ class UserCardSerializer < BasicUserSerializer :pending_posts_count, :status - untrusted_attributes :bio_excerpt, - :website, - :website_name, - :location, - :card_background_upload_url + untrusted_attributes :bio_excerpt, :website, :website_name, :location, :card_background_upload_url staff_attributes :staged @@ -91,8 +87,7 @@ class UserCardSerializer < BasicUserSerializer end def include_email? - (object.id && object.id == scope.user.try(:id)) || - (scope.is_staff? && object.staged?) + (object.id && object.id == scope.user.try(:id)) || (scope.is_staff? && object.staged?) end alias_method :include_secondary_emails?, :include_email? @@ -111,13 +106,14 @@ class UserCardSerializer < BasicUserSerializer end def website_name - uri = begin - URI(website.to_s) - rescue URI::Error - end + uri = + begin + URI(website.to_s) + rescue URI::Error + end return if uri.nil? || uri.host.nil? - uri.host.sub(/^www\./, '') + uri.path + uri.host.sub(/^www\./, "") + uri.path end def ignored diff --git a/app/serializers/user_history_serializer.rb b/app/serializers/user_history_serializer.rb index be3f26c97de..d188eccb99d 100644 --- a/app/serializers/user_history_serializer.rb +++ b/app/serializers/user_history_serializer.rb @@ -22,7 +22,7 @@ class UserHistorySerializer < ApplicationSerializer def action_name key = UserHistory.actions.key(object.action) - [:custom, :custom_staff].include?(key) ? object.custom_type : key.to_s + %i[custom custom_staff].include?(key) ? object.custom_type : key.to_s end def new_value diff --git a/app/serializers/user_option_serializer.rb b/app/serializers/user_option_serializer.rb index 8cc65d1779f..bd16f075364 100644 --- a/app/serializers/user_option_serializer.rb +++ b/app/serializers/user_option_serializer.rb @@ -37,13 +37,14 @@ class UserOptionSerializer < ApplicationSerializer :default_calendar, :oldest_search_log_date, :seen_popups, - - def auto_track_topics_after_msecs - object.auto_track_topics_after_msecs || SiteSetting.default_other_auto_track_topics_after_msecs - end + def auto_track_topics_after_msecs + object.auto_track_topics_after_msecs || + SiteSetting.default_other_auto_track_topics_after_msecs + end def notification_level_when_replying - object.notification_level_when_replying || SiteSetting.default_other_notification_level_when_replying + object.notification_level_when_replying || + SiteSetting.default_other_notification_level_when_replying end def new_topic_duration_minutes diff --git a/app/serializers/user_post_topic_bookmark_base_serializer.rb b/app/serializers/user_post_topic_bookmark_base_serializer.rb index ca5d50e684d..02798d061ad 100644 --- a/app/serializers/user_post_topic_bookmark_base_serializer.rb +++ b/app/serializers/user_post_topic_bookmark_base_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'post_item_excerpt' +require_relative "post_item_excerpt" class UserPostTopicBookmarkBaseSerializer < UserBookmarkBaseSerializer include TopicTagsMixin diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb index 505d7364544..eeda5132f68 100644 --- a/app/serializers/user_serializer.rb +++ b/app/serializers/user_serializer.rb @@ -31,9 +31,7 @@ class UserSerializer < UserCardSerializer can_edit end - staff_attributes :post_count, - :can_be_deleted, - :can_delete_all_posts + staff_attributes :post_count, :can_be_deleted, :can_delete_all_posts private_attributes :locale, :muted_category_ids, @@ -72,14 +70,13 @@ class UserSerializer < UserCardSerializer untrusted_attributes :bio_raw, :bio_cooked, :profile_background_upload_url, - - ### - ### ATTRIBUTES - ### - # - def user_notification_schedule - object.user_notification_schedule || UserNotificationSchedule::DEFAULT - end + ### + ### ATTRIBUTES + ### + # + def user_notification_schedule + object.user_notification_schedule || UserNotificationSchedule::DEFAULT + end def mailing_list_posts_per_day val = Post.estimate_posts_per_day @@ -87,8 +84,7 @@ class UserSerializer < UserCardSerializer end def groups - object.groups.order(:id) - .visible_groups(scope.user).members_visible_groups(scope.user) + object.groups.order(:id).visible_groups(scope.user).members_visible_groups(scope.user) end def group_users @@ -144,15 +140,19 @@ class UserSerializer < UserCardSerializer end def user_api_keys - keys = object.user_api_keys.where(revoked_at: nil).map do |k| - { - id: k.id, - application_name: k.application_name, - scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s.name}") }, - created_at: k.created_at, - last_used_at: k.last_used_at, - } - end + keys = + object + .user_api_keys + .where(revoked_at: nil) + .map do |k| + { + id: k.id, + application_name: k.application_name, + scopes: k.scopes.map { |s| I18n.t("user_api_key.scopes.#{s.name}") }, + created_at: k.created_at, + last_used_at: k.last_used_at, + } + end keys.sort! { |a, b| a[:last_used_at].to_time <=> b[:last_used_at].to_time } keys.length > 0 ? keys : nil @@ -162,7 +162,7 @@ class UserSerializer < UserCardSerializer ActiveModel::ArraySerializer.new( object.user_auth_tokens, each_serializer: UserAuthTokenSerializer, - scope: scope + scope: scope, ) end @@ -306,7 +306,8 @@ class UserSerializer < UserCardSerializer end def use_logo_small_as_avatar - object.is_system_user? && SiteSetting.logo_small && SiteSetting.use_site_small_logo_as_system_avatar + object.is_system_user? && SiteSetting.logo_small && + SiteSetting.use_site_small_logo_as_system_avatar end private @@ -314,11 +315,8 @@ class UserSerializer < UserCardSerializer def custom_field_keys fields = super - if scope.can_edit?(object) - fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a - end + fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a if scope.can_edit?(object) fields end - end diff --git a/app/serializers/user_summary_serializer.rb b/app/serializers/user_summary_serializer.rb index f6e87bb0559..03faf5b90f5 100644 --- a/app/serializers/user_summary_serializer.rb +++ b/app/serializers/user_summary_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserSummarySerializer < ApplicationSerializer - class TopicSerializer < BasicTopicSerializer attributes :category_id, :like_count, :created_at end @@ -65,9 +64,15 @@ class UserSummarySerializer < ApplicationSerializer end class CategoryWithCountsSerializer < ApplicationSerializer - attributes :topic_count, :post_count, - :id, :name, :color, :text_color, :slug, - :read_restricted, :parent_category_id + attributes :topic_count, + :post_count, + :id, + :name, + :color, + :text_color, + :slug, + :read_restricted, + :parent_category_id end has_many :topics, serializer: TopicSerializer diff --git a/app/serializers/user_tag_notifications_serializer.rb b/app/serializers/user_tag_notifications_serializer.rb index d2d0766a3e3..e3967cf4d4e 100644 --- a/app/serializers/user_tag_notifications_serializer.rb +++ b/app/serializers/user_tag_notifications_serializer.rb @@ -3,11 +3,7 @@ class UserTagNotificationsSerializer < ApplicationSerializer include UserTagNotificationsMixin - attributes :watched_tags, - :watching_first_post_tags, - :tracked_tags, - :muted_tags, - :regular_tags + attributes :watched_tags, :watching_first_post_tags, :tracked_tags, :muted_tags, :regular_tags def user object diff --git a/app/serializers/user_wordpress_serializer.rb b/app/serializers/user_wordpress_serializer.rb index c43f91076fe..6f8510b1897 100644 --- a/app/serializers/user_wordpress_serializer.rb +++ b/app/serializers/user_wordpress_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserWordpressSerializer < BasicUserSerializer - def avatar_template if Hash === object UrlHelper.absolute User.avatar_template(user[:username], user[:uploaded_avatar_id]) @@ -9,5 +8,4 @@ class UserWordpressSerializer < BasicUserSerializer UrlHelper.absolute object.avatar_template end end - end diff --git a/app/serializers/watched_word_list_serializer.rb b/app/serializers/watched_word_list_serializer.rb index ec3cac72a90..4b47ce16f78 100644 --- a/app/serializers/watched_word_list_serializer.rb +++ b/app/serializers/watched_word_list_serializer.rb @@ -4,14 +4,15 @@ class WatchedWordListSerializer < ApplicationSerializer attributes :actions, :words, :compiled_regular_expressions def actions - SiteSetting.tagging_enabled ? WatchedWord.actions.keys - : WatchedWord.actions.keys.filter { |k| k != :tag } + if SiteSetting.tagging_enabled + WatchedWord.actions.keys + else + WatchedWord.actions.keys.filter { |k| k != :tag } + end end def words - object.map do |word| - WatchedWordSerializer.new(word, root: false) - end + object.map { |word| WatchedWordSerializer.new(word, root: false) } end def compiled_regular_expressions diff --git a/app/serializers/web_hook_category_serializer.rb b/app/serializers/web_hook_category_serializer.rb index b7fd7ba79c9..dfbd8aa184c 100644 --- a/app/serializers/web_hook_category_serializer.rb +++ b/app/serializers/web_hook_category_serializer.rb @@ -1,15 +1,7 @@ # frozen_string_literal: true class WebHookCategorySerializer < CategorySerializer - - %i{ - can_edit - notification_level - available_groups - }.each do |attr| - define_method("include_#{attr}?") do - false - end + %i[can_edit notification_level available_groups].each do |attr| + define_method("include_#{attr}?") { false } end - end diff --git a/app/serializers/web_hook_flag_serializer.rb b/app/serializers/web_hook_flag_serializer.rb index ae2bf85754f..d37ca1d5a77 100644 --- a/app/serializers/web_hook_flag_serializer.rb +++ b/app/serializers/web_hook_flag_serializer.rb @@ -1,13 +1,7 @@ # frozen_string_literal: true class WebHookFlagSerializer < ApplicationSerializer - attributes :id, - :post, - :flag_type, - :created_by, - :created_at, - :resolved_at, - :resolved_by + attributes :id, :post, :flag_type, :created_by, :created_at, :resolved_at, :resolved_by def post WebHookPostSerializer.new(object.post, scope: scope, root: false).as_json @@ -41,7 +35,7 @@ class WebHookFlagSerializer < ApplicationSerializer disposed_by_id.present? end -protected + protected def disposed_by_id object.disagreed_by_id || object.agreed_by_id || object.deferred_by_id diff --git a/app/serializers/web_hook_group_serializer.rb b/app/serializers/web_hook_group_serializer.rb index d1947c2d848..e6134475054 100644 --- a/app/serializers/web_hook_group_serializer.rb +++ b/app/serializers/web_hook_group_serializer.rb @@ -1,14 +1,5 @@ # frozen_string_literal: true class WebHookGroupSerializer < BasicGroupSerializer - - %i{ - is_group_user - is_group_owner - }.each do |attr| - define_method("include_#{attr}?") do - false - end - end - + %i[is_group_user is_group_owner].each { |attr| define_method("include_#{attr}?") { false } } end diff --git a/app/serializers/web_hook_group_user_serializer.rb b/app/serializers/web_hook_group_user_serializer.rb index 342b5a5412b..23a181a28e8 100644 --- a/app/serializers/web_hook_group_user_serializer.rb +++ b/app/serializers/web_hook_group_user_serializer.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true class WebHookGroupUserSerializer < BasicGroupUserSerializer - attributes :id, - :created_at + attributes :id, :created_at end diff --git a/app/serializers/web_hook_post_serializer.rb b/app/serializers/web_hook_post_serializer.rb index beaa6738319..f804e00833e 100644 --- a/app/serializers/web_hook_post_serializer.rb +++ b/app/serializers/web_hook_post_serializer.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class WebHookPostSerializer < PostSerializer - - attributes :topic_posts_count, - :topic_filtered_posts_count, - :topic_archetype, - :category_slug + attributes :topic_posts_count, :topic_filtered_posts_count, :topic_archetype, :category_slug def include_topic_title? true @@ -19,7 +15,7 @@ class WebHookPostSerializer < PostSerializer true end - %i{ + %i[ can_view can_edit can_delete @@ -33,11 +29,7 @@ class WebHookPostSerializer < PostSerializer flair_color notice mentioned_users - }.each do |attr| - define_method("include_#{attr}?") do - false - end - end + ].each { |attr| define_method("include_#{attr}?") { false } } def topic_posts_count object.topic ? object.topic.posts_count : 0 @@ -48,7 +40,7 @@ class WebHookPostSerializer < PostSerializer end def topic_archetype - object.topic ? object.topic.archetype : '' + object.topic ? object.topic.archetype : "" end def include_category_slug? @@ -56,7 +48,7 @@ class WebHookPostSerializer < PostSerializer end def category_slug - object.topic && object.topic.category ? object.topic.category.slug_for_url : '' + object.topic && object.topic.category ? object.topic.category.slug_for_url : "" end def include_readers_count? diff --git a/app/serializers/web_hook_topic_view_serializer.rb b/app/serializers/web_hook_topic_view_serializer.rb index 2098c7a67be..ff11424db15 100644 --- a/app/serializers/web_hook_topic_view_serializer.rb +++ b/app/serializers/web_hook_topic_view_serializer.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true class WebHookTopicViewSerializer < TopicViewSerializer - attributes :created_by, - :last_poster + attributes :created_by, :last_poster - %i{ + %i[ post_stream timeline_lookup pm_with_non_human_user @@ -23,11 +22,7 @@ class WebHookTopicViewSerializer < TopicViewSerializer slow_mode_seconds slow_mode_enabled_until bookmarks - }.each do |attr| - define_method("include_#{attr}?") do - false - end - end + ].each { |attr| define_method("include_#{attr}?") { false } } def include_show_read_indicator? false diff --git a/app/serializers/web_hook_user_serializer.rb b/app/serializers/web_hook_user_serializer.rb index e077b191006..5de175413ef 100644 --- a/app/serializers/web_hook_user_serializer.rb +++ b/app/serializers/web_hook_user_serializer.rb @@ -7,7 +7,7 @@ class WebHookUserSerializer < UserSerializer def staff_attributes(*attrs) end - %i{ + %i[ unconfirmed_emails can_edit can_edit_username @@ -38,11 +38,7 @@ class WebHookUserSerializer < UserSerializer use_logo_small_as_avatar pending_posts_count status - }.each do |attr| - define_method("include_#{attr}?") do - false - end - end + ].each { |attr| define_method("include_#{attr}?") { false } } def include_email? scope.is_admin? @@ -57,5 +53,4 @@ class WebHookUserSerializer < UserSerializer def external_id object.single_sign_on_record.external_id end - end diff --git a/app/serializers/wizard_field_serializer.rb b/app/serializers/wizard_field_serializer.rb index 12c3db2060d..b4cb8a3b563 100644 --- a/app/serializers/wizard_field_serializer.rb +++ b/app/serializers/wizard_field_serializer.rb @@ -1,7 +1,17 @@ # frozen_string_literal: true class WizardFieldSerializer < ApplicationSerializer - attributes :id, :type, :required, :value, :label, :placeholder, :description, :extra_description, :icon, :disabled, :show_in_sidebar + attributes :id, + :type, + :required, + :value, + :label, + :placeholder, + :description, + :extra_description, + :icon, + :disabled, + :show_in_sidebar has_many :choices, serializer: WizardFieldChoiceSerializer, embed: :objects def id diff --git a/app/serializers/wizard_step_serializer.rb b/app/serializers/wizard_step_serializer.rb index 802a1a4be97..76ec852124d 100644 --- a/app/serializers/wizard_step_serializer.rb +++ b/app/serializers/wizard_step_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class WizardStepSerializer < ApplicationSerializer - attributes :id, :next, :previous, :description, :title, :index, :banner, :emoji has_many :fields, serializer: WizardFieldSerializer, embed: :objects @@ -68,5 +67,4 @@ class WizardStepSerializer < ApplicationSerializer def emoji object.emoji end - end diff --git a/app/services/anonymous_shadow_creator.rb b/app/services/anonymous_shadow_creator.rb index 77d353b2f97..2a769c7a681 100644 --- a/app/services/anonymous_shadow_creator.rb +++ b/app/services/anonymous_shadow_creator.rb @@ -30,9 +30,8 @@ class AnonymousShadowCreator shadow = user.shadow_user - if shadow && (shadow.post_count + shadow.topic_count) > 0 && - shadow.last_posted_at && - shadow.last_posted_at < SiteSetting.anonymous_account_duration_minutes.minutes.ago + if shadow && (shadow.post_count + shadow.topic_count) > 0 && shadow.last_posted_at && + shadow.last_posted_at < SiteSetting.anonymous_account_duration_minutes.minutes.ago shadow = nil end @@ -45,23 +44,24 @@ class AnonymousShadowCreator username = resolve_username User.transaction do - shadow = User.create!( - password: SecureRandom.hex, - email: "#{SecureRandom.hex}@anon.#{Discourse.current_hostname}", - skip_email_validation: true, - name: username, # prevents error when names are required - username: username, - active: true, - trust_level: 1, - manual_locked_trust_level: 1, - approved: true, - approved_at: 1.day.ago, - created_at: 1.day.ago # bypass new user restrictions - ) + shadow = + User.create!( + password: SecureRandom.hex, + email: "#{SecureRandom.hex}@anon.#{Discourse.current_hostname}", + skip_email_validation: true, + name: username, # prevents error when names are required + username: username, + active: true, + trust_level: 1, + manual_locked_trust_level: 1, + approved: true, + approved_at: 1.day.ago, + created_at: 1.day.ago, # bypass new user restrictions + ) shadow.user_option.update_columns( email_messages_level: UserOption.email_level_types[:never], - email_digests: false + email_digests: false, ) shadow.email_tokens.update_all(confirmed: true) diff --git a/app/services/badge_granter.rb b/app/services/badge_granter.rb index da264036c66..f32d1321f33 100644 --- a/app/services/badge_granter.rb +++ b/app/services/badge_granter.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class BadgeGranter - class GrantError < StandardError; end + class GrantError < StandardError + end def self.disable_queue @queue_disabled = true @@ -21,7 +22,12 @@ class BadgeGranter BadgeGranter.new(badge, user, opts).grant end - def self.enqueue_mass_grant_for_users(badge, emails: [], usernames: [], ensure_users_have_badge_once: true) + def self.enqueue_mass_grant_for_users( + badge, + emails: [], + usernames: [], + ensure_users_have_badge_once: true + ) emails = emails.map(&:downcase) usernames = usernames.map(&:downcase) usernames_map_to_ids = {} @@ -30,7 +36,7 @@ class BadgeGranter usernames_map_to_ids = User.where(username_lower: usernames).pluck(:username_lower, :id).to_h end if emails.size > 0 - emails_map_to_ids = User.with_email(emails).pluck('LOWER(user_emails.email)', :id).to_h + emails_map_to_ids = User.with_email(emails).pluck("LOWER(user_emails.email)", :id).to_h end count_per_user = {} @@ -57,18 +63,13 @@ class BadgeGranter count_per_user.each do |user_id, count| next if ensure_users_have_badge_once && existing_owners_ids.include?(user_id) - Jobs.enqueue( - :mass_award_badge, - user: user_id, - badge: badge.id, - count: count - ) + Jobs.enqueue(:mass_award_badge, user: user_id, badge: badge.id, count: count) end { unmatched_entries: unmatched.to_a, matched_users_count: count_per_user.size, - unmatched_entries_count: unmatched.size + unmatched_entries_count: unmatched.size, } end @@ -78,7 +79,8 @@ class BadgeGranter raise ArgumentError.new("count can't be less than 1") if count < 1 UserBadge.transaction do - DB.exec(<<~SQL * count, now: Time.zone.now, system: Discourse.system_user.id, user_id: user.id, badge_id: badge.id) + DB.exec( + <<~SQL * count, INSERT INTO user_badges (granted_at, created_at, granted_by_id, user_id, badge_id, seq) VALUES @@ -95,6 +97,11 @@ class BadgeGranter ), 0) ); SQL + now: Time.zone.now, + system: Discourse.system_user.id, + user_id: user.id, + badge_id: badge.id, + ) notification = send_notification(user.id, user.username, user.locale, badge) DB.exec(<<~SQL, notification_id: notification.id, user_id: user.id, badge_id: badge.id) @@ -114,9 +121,7 @@ class BadgeGranter find_by = { badge_id: @badge.id, user_id: @user.id } - if @badge.multiple_grant? - find_by[:post_id] = @post_id - end + find_by[:post_id] = @post_id if @badge.multiple_grant? user_badge = UserBadge.find_by(find_by) @@ -128,12 +133,15 @@ class BadgeGranter seq = (seq || -1) + 1 end - user_badge = UserBadge.create!(badge: @badge, - user: @user, - granted_by: @granted_by, - granted_at: @opts[:created_at] || Time.now, - post_id: @post_id, - seq: seq) + user_badge = + UserBadge.create!( + badge: @badge, + user: @user, + granted_by: @granted_by, + granted_at: @opts[:created_at] || Time.now, + post_id: @post_id, + seq: seq, + ) return unless SiteSetting.enable_badges @@ -143,7 +151,8 @@ class BadgeGranter skip_new_user_tips = @user.user_option.skip_new_user_tips unless self.class.suppress_notification?(@badge, user_badge.granted_at, skip_new_user_tips) - notification = self.class.send_notification(@user.id, @user.username, @user.effective_locale, @badge) + notification = + self.class.send_notification(@user.id, @user.username, @user.effective_locale, @badge) user_badge.update!(notification_id: notification.id) end end @@ -160,16 +169,18 @@ class BadgeGranter end # If the user's title is the same as the badge name OR the custom badge name, remove their title. - custom_badge_name = TranslationOverride.find_by(translation_key: user_badge.badge.translation_key)&.value + custom_badge_name = + TranslationOverride.find_by(translation_key: user_badge.badge.translation_key)&.value user_title_is_badge_name = user_badge.user.title == user_badge.badge.name - user_title_is_custom_badge_name = custom_badge_name.present? && user_badge.user.title == custom_badge_name + user_title_is_custom_badge_name = + custom_badge_name.present? && user_badge.user.title == custom_badge_name if user_title_is_badge_name || user_title_is_custom_badge_name if options[:revoked_by] StaffActionLogger.new(options[:revoked_by]).log_title_revoke( user_badge.user, - revoke_reason: 'user title was same as revoked badge name or custom badge name', - previous_value: user_badge.user.title + revoke_reason: "user title was same as revoked badge name or custom badge name", + previous_value: user_badge.user.title, ) end user_badge.user.title = nil @@ -179,10 +190,15 @@ class BadgeGranter end def self.revoke_all(badge) - custom_badge_names = TranslationOverride.where(translation_key: badge.translation_key).pluck(:value) + custom_badge_names = + TranslationOverride.where(translation_key: badge.translation_key).pluck(:value) - users = User.joins(:user_badges).where(user_badges: { badge_id: badge.id }).where(title: badge.name) - users = users.or(User.joins(:user_badges).where(title: custom_badge_names)) unless custom_badge_names.empty? + users = + User.joins(:user_badges).where(user_badges: { badge_id: badge.id }).where(title: badge.name) + users = + users.or( + User.joins(:user_badges).where(title: custom_badge_names), + ) unless custom_badge_names.empty? users.update_all(title: nil) UserBadge.where(badge: badge).delete_all @@ -195,28 +211,16 @@ class BadgeGranter case type when Badge::Trigger::PostRevision post = opt[:post] - payload = { - type: "PostRevision", - post_ids: [post.id] - } + payload = { type: "PostRevision", post_ids: [post.id] } when Badge::Trigger::UserChange user = opt[:user] - payload = { - type: "UserChange", - user_ids: [user.id] - } + payload = { type: "UserChange", user_ids: [user.id] } when Badge::Trigger::TrustLevelChange user = opt[:user] - payload = { - type: "TrustLevelChange", - user_ids: [user.id] - } + payload = { type: "TrustLevelChange", user_ids: [user.id] } when Badge::Trigger::PostAction action = opt[:post_action] - payload = { - type: "PostAction", - post_ids: [action.post_id, action.related_post_id].compact! - } + payload = { type: "PostAction", post_ids: [action.post_id, action.related_post_id].compact! } end Discourse.redis.lpush queue_key, payload.to_json if payload @@ -242,9 +246,7 @@ class BadgeGranter next unless post_ids.present? || user_ids.present? - find_by_type(type).each do |badge| - backfill(badge, post_ids: post_ids, user_ids: user_ids) - end + find_by_type(type).each { |badge| backfill(badge, post_ids: post_ids, user_ids: user_ids) } end end @@ -263,27 +265,47 @@ class BadgeGranter return if sql.blank? if Badge::Trigger.uses_post_ids?(opts[:trigger]) - raise("Contract violation:\nQuery triggers on posts, but does not reference the ':post_ids' array") unless sql.match(/:post_ids/) - raise "Contract violation:\nQuery triggers on posts, but references the ':user_ids' array" if sql.match(/:user_ids/) + unless sql.match(/:post_ids/) + raise( + "Contract violation:\nQuery triggers on posts, but does not reference the ':post_ids' array", + ) + end + if sql.match(/:user_ids/) + raise "Contract violation:\nQuery triggers on posts, but references the ':user_ids' array" + end end if Badge::Trigger.uses_user_ids?(opts[:trigger]) - raise "Contract violation:\nQuery triggers on users, but does not reference the ':user_ids' array" unless sql.match(/:user_ids/) - raise "Contract violation:\nQuery triggers on users, but references the ':post_ids' array" if sql.match(/:post_ids/) + unless sql.match(/:user_ids/) + raise "Contract violation:\nQuery triggers on users, but does not reference the ':user_ids' array" + end + if sql.match(/:post_ids/) + raise "Contract violation:\nQuery triggers on users, but references the ':post_ids' array" + end end if opts[:trigger] && !Badge::Trigger.is_none?(opts[:trigger]) - raise "Contract violation:\nQuery is triggered, but does not reference the ':backfill' parameter.\n(Hint: if :backfill is TRUE, you should ignore the :post_ids/:user_ids)" unless sql.match(/:backfill/) + unless sql.match(/:backfill/) + raise "Contract violation:\nQuery is triggered, but does not reference the ':backfill' parameter.\n(Hint: if :backfill is TRUE, you should ignore the :post_ids/:user_ids)" + end end # TODO these three conditions have a lot of false negatives if opts[:target_posts] - raise "Contract violation:\nQuery targets posts, but does not return a 'post_id' column" unless sql.match(/post_id/) + unless sql.match(/post_id/) + raise "Contract violation:\nQuery targets posts, but does not return a 'post_id' column" + end end - raise "Contract violation:\nQuery does not return a 'user_id' column" unless sql.match(/user_id/) - raise "Contract violation:\nQuery does not return a 'granted_at' column" unless sql.match(/granted_at/) - raise "Contract violation:\nQuery ends with a semicolon. Remove the semicolon; your sql will be used in a subquery." if sql.match(/;\s*\z/) + unless sql.match(/user_id/) + raise "Contract violation:\nQuery does not return a 'user_id' column" + end + unless sql.match(/granted_at/) + raise "Contract violation:\nQuery does not return a 'granted_at' column" + end + if sql.match(/;\s*\z/) + raise "Contract violation:\nQuery ends with a semicolon. Remove the semicolon; your sql will be used in a subquery." + end end # Options: @@ -305,8 +327,9 @@ class BadgeGranter SQL grant_count = DB.query_single(count_sql, params).first.to_i - grants_sql = if opts[:target_posts] - <<~SQL + grants_sql = + if opts[:target_posts] + <<~SQL SELECT u.id, u.username, q.post_id, t.title, q.granted_at FROM ( #{sql} @@ -317,8 +340,8 @@ class BadgeGranter WHERE :backfill = :backfill LIMIT 10 SQL - else - <<~SQL + else + <<~SQL SELECT u.id, u.username, q.granted_at FROM ( #{sql} @@ -327,7 +350,7 @@ class BadgeGranter WHERE :backfill = :backfill LIMIT 10 SQL - end + end query_plan = nil # HACK: active record sanitization too flexible, force it to go down the sanitization path that cares not for % stuff @@ -337,11 +360,17 @@ class BadgeGranter sample = DB.query(grants_sql, params) sample.each do |result| - raise "Query returned a non-existent user ID:\n#{result.id}" unless User.exists?(id: result.id) - raise "Query did not return a badge grant time\n(Try using 'current_timestamp granted_at')" unless result.granted_at + unless User.exists?(id: result.id) + raise "Query returned a non-existent user ID:\n#{result.id}" + end + unless result.granted_at + raise "Query did not return a badge grant time\n(Try using 'current_timestamp granted_at')" + end if opts[:target_posts] raise "Query did not return a post ID" unless result.post_id - raise "Query returned a non-existent post ID:\n#{result.post_id}" unless Post.exists?(result.post_id).present? + unless Post.exists?(result.post_id).present? + raise "Query returned a non-existent post ID:\n#{result.post_id}" + end end end @@ -362,7 +391,7 @@ class BadgeGranter # safeguard fall back to full backfill if more than 200 if (post_ids && post_ids.size > MAX_ITEMS_FOR_DELTA) || - (user_ids && user_ids.size > MAX_ITEMS_FOR_DELTA) + (user_ids && user_ids.size > MAX_ITEMS_FOR_DELTA) post_ids = nil user_ids = nil end @@ -388,14 +417,16 @@ class BadgeGranter ) SQL - DB.exec( - sql, - id: badge.id, - post_ids: [-1], - user_ids: [-2], - backfill: true, - multiple_grant: true # cheat here, cause we only run on backfill and are deleting - ) if badge.auto_revoke && full_backfill + if badge.auto_revoke && full_backfill + DB.exec( + sql, + id: badge.id, + post_ids: [-1], + user_ids: [-2], + backfill: true, + multiple_grant: true, # cheat here, cause we only run on backfill and are deleting + ) + end sql = <<~SQL WITH w as ( @@ -434,25 +465,27 @@ class BadgeGranter return end - builder.query( - id: badge.id, - multiple_grant: badge.multiple_grant, - backfill: full_backfill, - post_ids: post_ids || [-2], - user_ids: user_ids || [-2]).each do |row| - - next if suppress_notification?(badge, row.granted_at, row.skip_new_user_tips) - next if row.staff && badge.awarded_for_trust_level? - - notification = send_notification(row.user_id, row.username, row.locale, badge) - UserBadge.trigger_user_badge_granted_event(badge.id, row.user_id) - - DB.exec( - "UPDATE user_badges SET notification_id = :notification_id WHERE id = :id", - notification_id: notification.id, - id: row.id + builder + .query( + id: badge.id, + multiple_grant: badge.multiple_grant, + backfill: full_backfill, + post_ids: post_ids || [-2], + user_ids: user_ids || [-2], ) - end + .each do |row| + next if suppress_notification?(badge, row.granted_at, row.skip_new_user_tips) + next if row.staff && badge.awarded_for_trust_level? + + notification = send_notification(row.user_id, row.username, row.locale, badge) + UserBadge.trigger_user_badge_granted_event(badge.id, row.user_id) + + DB.exec( + "UPDATE user_badges SET notification_id = :notification_id WHERE id = :id", + notification_id: notification.id, + id: row.id, + ) + end badge.reset_grant_count! rescue => e @@ -505,8 +538,8 @@ class BadgeGranter badge_name: badge.display_name, badge_slug: badge.slug, badge_title: badge.allow_title, - username: username - }.to_json + username: username, + }.to_json, ) end end diff --git a/app/services/base_bookmarkable.rb b/app/services/base_bookmarkable.rb index 6976eece625..77c19c7147f 100644 --- a/app/services/base_bookmarkable.rb +++ b/app/services/base_bookmarkable.rb @@ -119,16 +119,17 @@ class BaseBookmarkable # created. # @return [void] def self.send_reminder_notification(bookmark, notification_data) - if notification_data[:data].blank? || - notification_data[:data][:bookmarkable_url].blank? || - notification_data[:data][:title].blank? - raise Discourse::InvalidParameters.new("A `data` key must be present with at least `bookmarkable_url` and `title` entries.") + if notification_data[:data].blank? || notification_data[:data][:bookmarkable_url].blank? || + notification_data[:data][:title].blank? + raise Discourse::InvalidParameters.new( + "A `data` key must be present with at least `bookmarkable_url` and `title` entries.", + ) end notification_data[:data] = notification_data[:data].merge( display_username: bookmark.user.username, bookmark_name: bookmark.name, - bookmark_id: bookmark.id + bookmark_id: bookmark.id, ).to_json notification_data[:notification_type] = Notification.types[:bookmark_reminder] bookmark.user.notifications.create!(notification_data) diff --git a/app/services/color_scheme_revisor.rb b/app/services/color_scheme_revisor.rb index 7515d513d3a..937c6bf7d45 100644 --- a/app/services/color_scheme_revisor.rb +++ b/app/services/color_scheme_revisor.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class ColorSchemeRevisor - def initialize(color_scheme, params = {}) @color_scheme = color_scheme @params = params @@ -14,7 +13,9 @@ class ColorSchemeRevisor def revise ColorScheme.transaction do @color_scheme.name = @params[:name] if @params.has_key?(:name) - @color_scheme.user_selectable = @params[:user_selectable] if @params.has_key?(:user_selectable) + @color_scheme.user_selectable = @params[:user_selectable] if @params.has_key?( + :user_selectable, + ) @color_scheme.base_scheme_id = @params[:base_scheme_id] if @params.has_key?(:base_scheme_id) has_colors = @params[:colors] @@ -29,15 +30,12 @@ class ColorSchemeRevisor @color_scheme.clear_colors_cache end - if has_colors || - @color_scheme.saved_change_to_name? || - @color_scheme.will_save_change_to_user_selectable? || - @color_scheme.saved_change_to_base_scheme_id? - + if has_colors || @color_scheme.saved_change_to_name? || + @color_scheme.will_save_change_to_user_selectable? || + @color_scheme.saved_change_to_base_scheme_id? @color_scheme.save end end @color_scheme end - end diff --git a/app/services/destroy_task.rb b/app/services/destroy_task.rb index d06a4ad7556..fda78c9583d 100644 --- a/app/services/destroy_task.rb +++ b/app/services/destroy_task.rb @@ -18,9 +18,7 @@ class DestroyTask topics.find_each do |topic| @io.puts "Deleting #{topic.slug}..." first_post = topic.ordered_posts.first - if first_post.nil? - return @io.puts "Topic.ordered_posts.first was nil" - end + return @io.puts "Topic.ordered_posts.first was nil" if first_post.nil? @io.puts PostDestroyer.new(Discourse.system_user, first_post).destroy end end @@ -45,17 +43,17 @@ class DestroyTask def destroy_topics_all_categories categories = Category.all - categories.each do |c| - @io.puts destroy_topics(c.slug, c.parent_category&.slug) - end + categories.each { |c| @io.puts destroy_topics(c.slug, c.parent_category&.slug) } end def destroy_private_messages - Topic.where(archetype: "private_message").find_each do |pm| - @io.puts "Destroying #{pm.slug} pm" - first_post = pm.ordered_posts.first - @io.puts PostDestroyer.new(Discourse.system_user, first_post).destroy - end + Topic + .where(archetype: "private_message") + .find_each do |pm| + @io.puts "Destroying #{pm.slug} pm" + first_post = pm.ordered_posts.first + @io.puts PostDestroyer.new(Discourse.system_user, first_post).destroy + end end def destroy_category(category_id, destroy_system_topics = false) @@ -63,9 +61,7 @@ class DestroyTask return @io.puts "A category with the id: #{category_id} could not be found" if c.nil? subcategories = Category.where(parent_category_id: c.id) @io.puts "There are #{subcategories.count} subcategories to delete" if subcategories - subcategories.each do |s| - category_topic_destroyer(s, destroy_system_topics) - end + subcategories.each { |s| category_topic_destroyer(s, destroy_system_topics) } category_topic_destroyer(c, destroy_system_topics) end @@ -78,21 +74,30 @@ class DestroyTask end def destroy_users - User.human_users.where(admin: false).find_each do |user| - begin - if UserDestroyer.new(Discourse.system_user).destroy(user, delete_posts: true, context: "destroy task") - @io.puts "#{user.username} deleted" - else - @io.puts "#{user.username} not deleted" + User + .human_users + .where(admin: false) + .find_each do |user| + begin + if UserDestroyer.new(Discourse.system_user).destroy( + user, + delete_posts: true, + context: "destroy task", + ) + @io.puts "#{user.username} deleted" + else + @io.puts "#{user.username} not deleted" + end + rescue UserDestroyer::PostsExistError + raise Discourse::InvalidAccess.new( + "User #{user.username} has #{user.post_count} posts, so can't be deleted.", + ) + rescue NoMethodError + @io.puts "#{user.username} could not be deleted" + rescue Discourse::InvalidAccess => e + @io.puts "#{user.username} #{e.message}" end - rescue UserDestroyer::PostsExistError - raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.") - rescue NoMethodError - @io.puts "#{user.username} could not be deleted" - rescue Discourse::InvalidAccess => e - @io.puts "#{user.username} #{e.message}" end - end end def destroy_stats @@ -112,5 +117,4 @@ class DestroyTask @io.puts "Destroying #{category.slug} category" category.destroy end - end diff --git a/app/services/email_settings_exception_handler.rb b/app/services/email_settings_exception_handler.rb index ddb3fb908b8..b85a9aaa95b 100644 --- a/app/services/email_settings_exception_handler.rb +++ b/app/services/email_settings_exception_handler.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'net/imap' -require 'net/smtp' -require 'net/pop' +require "net/imap" +require "net/smtp" +require "net/pop" class EmailSettingsExceptionHandler EXPECTED_EXCEPTIONS = [ @@ -17,7 +17,7 @@ class EmailSettingsExceptionHandler Net::OpenTimeout, Net::ReadTimeout, SocketError, - Errno::ECONNREFUSED + Errno::ECONNREFUSED, ] class GenericProvider @@ -63,7 +63,10 @@ class EmailSettingsExceptionHandler if @exception.message.match(/Invalid credentials/) I18n.t("email_settings.imap_authentication_error") else - I18n.t("email_settings.imap_no_response_error", message: @exception.message.gsub(" (Failure)", "")) + I18n.t( + "email_settings.imap_no_response_error", + message: @exception.message.gsub(" (Failure)", ""), + ) end end diff --git a/app/services/email_settings_validator.rb b/app/services/email_settings_validator.rb index 238b5869627..bd6dbccfe60 100644 --- a/app/services/email_settings_validator.rb +++ b/app/services/email_settings_validator.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'net/imap' -require 'net/smtp' -require 'net/pop' +require "net/imap" +require "net/smtp" +require "net/pop" # Usage: # @@ -84,15 +84,14 @@ class EmailSettingsValidator debug: Rails.env.development? ) begin - port, enable_tls, enable_starttls_auto = provider_specific_ssl_overrides( - host, port, enable_tls, enable_starttls_auto - ) + port, enable_tls, enable_starttls_auto = + provider_specific_ssl_overrides(host, port, enable_tls, enable_starttls_auto) if enable_tls && enable_starttls_auto raise ArgumentError, "TLS and STARTTLS are mutually exclusive" end - if ![:plain, :login, :cram_md5].include?(authentication.to_sym) + if !%i[plain login cram_md5].include?(authentication.to_sym) raise ArgumentError, "Invalid authentication method. Must be plain, login, or cram_md5." end @@ -100,7 +99,6 @@ class EmailSettingsValidator if Rails.env.development? domain = "localhost" else - # Because we are using the SMTP settings here to send emails, # the domain should just be the TLD of the host. domain = MiniSuffix.domain(host) @@ -154,7 +152,11 @@ class EmailSettingsValidator begin imap = Net::IMAP.new(host, port: port, ssl: ssl, open_timeout: open_timeout) imap.login(username, password) - imap.logout rescue nil + begin + imap.logout + rescue StandardError + nil + end imap.disconnect rescue => err log_and_raise(err, debug) @@ -163,7 +165,9 @@ class EmailSettingsValidator def self.log_and_raise(err, debug) if debug - Rails.logger.warn("[EmailSettingsValidator] Error encountered when validating email settings: #{err.message} #{err.backtrace.join("\n")}") + Rails.logger.warn( + "[EmailSettingsValidator] Error encountered when validating email settings: #{err.message} #{err.backtrace.join("\n")}", + ) end raise err end diff --git a/app/services/email_style_updater.rb b/app/services/email_style_updater.rb index d13e84b78d6..42ebb0b7040 100644 --- a/app/services/email_style_updater.rb +++ b/app/services/email_style_updater.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class EmailStyleUpdater - attr_reader :errors def initialize(user) @@ -10,11 +9,8 @@ class EmailStyleUpdater end def update(attrs) - if attrs.has_key?(:html) && !attrs[:html].include?('%{email_content}') - @errors << I18n.t( - 'email_style.html_missing_placeholder', - placeholder: '%{email_content}' - ) + if attrs.has_key?(:html) && !attrs[:html].include?("%{email_content}") + @errors << I18n.t("email_style.html_missing_placeholder", placeholder: "%{email_content}") end if attrs.has_key?(:css) diff --git a/app/services/external_upload_manager.rb b/app/services/external_upload_manager.rb index cfeca323c72..23523ab4bc0 100644 --- a/app/services/external_upload_manager.rb +++ b/app/services/external_upload_manager.rb @@ -7,10 +7,14 @@ class ExternalUploadManager UPLOAD_TYPES_EXCLUDED_FROM_UPLOAD_PROMOTION = ["backup"].freeze - class ChecksumMismatchError < StandardError; end - class DownloadFailedError < StandardError; end - class CannotPromoteError < StandardError; end - class SizeMismatchError < StandardError; end + class ChecksumMismatchError < StandardError + end + class DownloadFailedError < StandardError + end + class CannotPromoteError < StandardError + end + class SizeMismatchError < StandardError + end attr_reader :external_upload_stub @@ -24,51 +28,54 @@ class ExternalUploadManager def self.create_direct_upload(current_user:, file_name:, file_size:, upload_type:, metadata: {}) store = store_for_upload_type(upload_type) - url = store.signed_url_for_temporary_upload( - file_name, metadata: metadata - ) + url = store.signed_url_for_temporary_upload(file_name, metadata: metadata) key = store.s3_helper.path_from_url(url) - upload_stub = ExternalUploadStub.create!( - key: key, - created_by: current_user, - original_filename: file_name, - upload_type: upload_type, - filesize: file_size - ) + upload_stub = + ExternalUploadStub.create!( + key: key, + created_by: current_user, + original_filename: file_name, + upload_type: upload_type, + filesize: file_size, + ) { url: url, key: key, unique_identifier: upload_stub.unique_identifier } end def self.create_direct_multipart_upload( - current_user:, file_name:, file_size:, upload_type:, metadata: {} + current_user:, + file_name:, + file_size:, + upload_type:, + metadata: {} ) content_type = MiniMime.lookup_by_filename(file_name)&.content_type store = store_for_upload_type(upload_type) - multipart_upload = store.create_multipart( - file_name, content_type, metadata: metadata - ) + multipart_upload = store.create_multipart(file_name, content_type, metadata: metadata) - upload_stub = ExternalUploadStub.create!( - key: multipart_upload[:key], - created_by: current_user, - original_filename: file_name, - upload_type: upload_type, - external_upload_identifier: multipart_upload[:upload_id], - multipart: true, - filesize: file_size - ) + upload_stub = + ExternalUploadStub.create!( + key: multipart_upload[:key], + created_by: current_user, + original_filename: file_name, + upload_type: upload_type, + external_upload_identifier: multipart_upload[:upload_id], + multipart: true, + filesize: file_size, + ) { external_upload_identifier: upload_stub.external_upload_identifier, key: upload_stub.key, - unique_identifier: upload_stub.unique_identifier + unique_identifier: upload_stub.unique_identifier, } end def self.store_for_upload_type(upload_type) if upload_type == "backup" - if !SiteSetting.enable_backups? || SiteSetting.backup_location != BackupLocationSiteSetting::S3 + if !SiteSetting.enable_backups? || + SiteSetting.backup_location != BackupLocationSiteSetting::S3 raise Discourse::InvalidAccess.new end BackupRestore::BackupStore.create @@ -98,9 +105,11 @@ class ExternalUploadManager if external_size != external_upload_stub.filesize ExternalUploadManager.ban_user_from_external_uploads!( user: external_upload_stub.created_by, - ban_minutes: SIZE_MISMATCH_BAN_MINUTES + ban_minutes: SIZE_MISMATCH_BAN_MINUTES, ) - raise SizeMismatchError.new("expected: #{external_upload_stub.filesize}, actual: #{external_size}") + raise SizeMismatchError.new( + "expected: #{external_upload_stub.filesize}, actual: #{external_size}", + ) end if UPLOAD_TYPES_EXCLUDED_FROM_UPLOAD_PROMOTION.include?(external_upload_stub.upload_type) @@ -108,7 +117,7 @@ class ExternalUploadManager else promote_to_upload end - rescue + rescue StandardError if !SiteSetting.enable_upload_debug_mode # We don't need to do anything special to abort multipart uploads here, # because at this point (calling promote_to_upload!), the multipart @@ -137,9 +146,7 @@ class ExternalUploadManager raise DownloadFailedError if tempfile.blank? actual_sha1 = Upload.generate_digest(tempfile) - if external_sha1 && external_sha1 != actual_sha1 - raise ChecksumMismatchError - end + raise ChecksumMismatchError if external_sha1 && external_sha1 != actual_sha1 end # TODO (martin): See if these additional opts will be needed @@ -148,11 +155,11 @@ class ExternalUploadManager type: external_upload_stub.upload_type, existing_external_upload_key: external_upload_stub.key, external_upload_too_big: external_size > DOWNLOAD_LIMIT, - filesize: external_size + filesize: external_size, }.merge(@upload_create_opts) UploadCreator.new(tempfile, external_upload_stub.original_filename, opts).create_for( - external_upload_stub.created_by_id + external_upload_stub.created_by_id, ) ensure tempfile&.close! @@ -163,7 +170,7 @@ class ExternalUploadManager @store.move_existing_stored_upload( existing_external_upload_key: external_upload_stub.key, original_filename: external_upload_stub.original_filename, - content_type: content_type + content_type: content_type, ) Struct.new(:errors).new([]) end @@ -190,7 +197,7 @@ class ExternalUploadManager url, max_file_size: DOWNLOAD_LIMIT, tmp_file_name: "discourse-upload-#{type}", - follow_redirect: true + follow_redirect: true, ) end end diff --git a/app/services/group_action_logger.rb b/app/services/group_action_logger.rb index d38d0e7c5a7..02299afb485 100644 --- a/app/services/group_action_logger.rb +++ b/app/services/group_action_logger.rb @@ -1,64 +1,71 @@ # frozen_string_literal: true class GroupActionLogger - def initialize(acting_user, group) @acting_user = acting_user @group = group end def log_make_user_group_owner(target_user) - GroupHistory.create!(default_params.merge( - action: GroupHistory.actions[:make_user_group_owner], - target_user: target_user - )) + GroupHistory.create!( + default_params.merge( + action: GroupHistory.actions[:make_user_group_owner], + target_user: target_user, + ), + ) end def log_remove_user_as_group_owner(target_user) - GroupHistory.create!(default_params.merge( - action: GroupHistory.actions[:remove_user_as_group_owner], - target_user: target_user - )) + GroupHistory.create!( + default_params.merge( + action: GroupHistory.actions[:remove_user_as_group_owner], + target_user: target_user, + ), + ) end def log_add_user_to_group(target_user, subject = nil) - GroupHistory.create!(default_params.merge( - action: GroupHistory.actions[:add_user_to_group], - target_user: target_user, - subject: subject - )) + GroupHistory.create!( + default_params.merge( + action: GroupHistory.actions[:add_user_to_group], + target_user: target_user, + subject: subject, + ), + ) end def log_remove_user_from_group(target_user, subject = nil) - GroupHistory.create!(default_params.merge( - action: GroupHistory.actions[:remove_user_from_group], - target_user: target_user, - subject: subject - )) + GroupHistory.create!( + default_params.merge( + action: GroupHistory.actions[:remove_user_from_group], + target_user: target_user, + subject: subject, + ), + ) end def log_change_group_settings - @group.previous_changes.except(*excluded_attributes).each do |attribute_name, value| - next if value[0].blank? && value[1].blank? + @group + .previous_changes + .except(*excluded_attributes) + .each do |attribute_name, value| + next if value[0].blank? && value[1].blank? - GroupHistory.create!(default_params.merge( - action: GroupHistory.actions[:change_group_setting], - subject: attribute_name, - prev_value: value[0], - new_value: value[1] - )) - end + GroupHistory.create!( + default_params.merge( + action: GroupHistory.actions[:change_group_setting], + subject: attribute_name, + prev_value: value[0], + new_value: value[1], + ), + ) + end end private def excluded_attributes - [ - :bio_cooked, - :updated_at, - :created_at, - :user_count - ] + %i[bio_cooked updated_at created_at user_count] end def default_params diff --git a/app/services/group_mentions_updater.rb b/app/services/group_mentions_updater.rb index 5130baa43e6..8099dcde2b8 100644 --- a/app/services/group_mentions_updater.rb +++ b/app/services/group_mentions_updater.rb @@ -2,15 +2,16 @@ class GroupMentionsUpdater def self.update(current_name, previous_name) - Post.where( - "cooked LIKE '%class=\"mention-group%' AND raw LIKE :previous_name", - previous_name: "%@#{previous_name}%" - ).find_in_batches do |posts| - - posts.each do |post| - post.raw.gsub!(/(^|\s)(@#{previous_name})(\s|$)/, "\\1@#{current_name}\\3") - post.save!(validate: false) + Post + .where( + "cooked LIKE '%class=\"mention-group%' AND raw LIKE :previous_name", + previous_name: "%@#{previous_name}%", + ) + .find_in_batches do |posts| + posts.each do |post| + post.raw.gsub!(/(^|\s)(@#{previous_name})(\s|$)/, "\\1@#{current_name}\\3") + post.save!(validate: false) + end end - end end end diff --git a/app/services/group_message.rb b/app/services/group_message.rb index 1f24289434a..47ee093a3fd 100644 --- a/app/services/group_message.rb +++ b/app/services/group_message.rb @@ -11,7 +11,6 @@ # The default is 24 hours. Set to false to always send the message. class GroupMessage - include Rails.application.routes.url_helpers RECENT_MESSAGE_PERIOD = 3.months @@ -29,14 +28,15 @@ class GroupMessage def create return false if sent_recently? - post = PostCreator.create( - Discourse.system_user, - target_group_names: [@group_name], - archetype: Archetype.private_message, - subtype: TopicSubtype.system_message, - title: I18n.t("system_messages.#{@message_type}.subject_template", message_params), - raw: I18n.t("system_messages.#{@message_type}.text_body_template", message_params) - ) + post = + PostCreator.create( + Discourse.system_user, + target_group_names: [@group_name], + archetype: Archetype.private_message, + subtype: TopicSubtype.system_message, + title: I18n.t("system_messages.#{@message_type}.subject_template", message_params), + raw: I18n.t("system_messages.#{@message_type}.text_body_template", message_params), + ) remember_message_sent post end @@ -44,38 +44,44 @@ class GroupMessage def delete_previous!(respect_sent_recently: true, match_raw: true) return false if respect_sent_recently && sent_recently? - posts = Post - .joins(topic: { topic_allowed_groups: :group }) - .where(topic: { - posts_count: 1, - user_id: Discourse.system_user, - archetype: Archetype.private_message, - subtype: TopicSubtype.system_message, - title: I18n.t("system_messages.#{@message_type}.subject_template", message_params), - topic_allowed_groups: { - groups: { name: @group_name } - } - }) - .where("posts.created_at > ?", RECENT_MESSAGE_PERIOD.ago) + posts = + Post + .joins(topic: { topic_allowed_groups: :group }) + .where( + topic: { + posts_count: 1, + user_id: Discourse.system_user, + archetype: Archetype.private_message, + subtype: TopicSubtype.system_message, + title: I18n.t("system_messages.#{@message_type}.subject_template", message_params), + topic_allowed_groups: { + groups: { + name: @group_name, + }, + }, + }, + ) + .where("posts.created_at > ?", RECENT_MESSAGE_PERIOD.ago) if match_raw - posts = posts.where(raw: I18n.t("system_messages.#{@message_type}.text_body_template", message_params).rstrip) + posts = + posts.where( + raw: I18n.t("system_messages.#{@message_type}.text_body_template", message_params).rstrip, + ) end - posts.find_each do |post| - PostDestroyer.new(Discourse.system_user, post).destroy - end + posts.find_each { |post| PostDestroyer.new(Discourse.system_user, post).destroy } end def message_params - @message_params ||= begin - h = { base_url: Discourse.base_url }.merge(@opts[:message_params] || {}) - if @opts[:user] - h.merge!(username: @opts[:user].username, - user_url: user_path(@opts[:user].username)) + @message_params ||= + begin + h = { base_url: Discourse.base_url }.merge(@opts[:message_params] || {}) + if @opts[:user] + h.merge!(username: @opts[:user].username, user_url: user_path(@opts[:user].username)) + end + h end - h - end end def sent_recently? @@ -85,10 +91,12 @@ class GroupMessage # default is to send no more than once every 24 hours (24 * 60 * 60 = 86,400 seconds) def remember_message_sent - Discourse.redis.setex(sent_recently_key, @opts[:limit_once_per].try(:to_i) || 86_400, 1) unless @opts[:limit_once_per] == false + unless @opts[:limit_once_per] == false + Discourse.redis.setex(sent_recently_key, @opts[:limit_once_per].try(:to_i) || 86_400, 1) + end end def sent_recently_key - "grpmsg:#{@group_name}:#{@message_type}:#{@opts[:user] ? @opts[:user].username : ''}" + "grpmsg:#{@group_name}:#{@message_type}:#{@opts[:user] ? @opts[:user].username : ""}" end end diff --git a/app/services/handle_chunk_upload.rb b/app/services/handle_chunk_upload.rb index d966f659aa1..cc69c1c5baa 100644 --- a/app/services/handle_chunk_upload.rb +++ b/app/services/handle_chunk_upload.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class HandleChunkUpload - def initialize(chunk, params = {}) @chunk = chunk @params = params @@ -21,7 +20,8 @@ class HandleChunkUpload def check_chunk # check whether the chunk has already been uploaded - has_chunk_been_uploaded = File.exist?(@chunk) && File.size(@chunk) == @params[:current_chunk_size] + has_chunk_been_uploaded = + File.exist?(@chunk) && File.size(@chunk) == @params[:current_chunk_size] # 200 = exists, 404 = not uploaded yet status = has_chunk_been_uploaded ? 200 : 404 end @@ -36,11 +36,11 @@ class HandleChunkUpload end def merge_chunks - upload_path = @params[:upload_path] + upload_path = @params[:upload_path] tmp_upload_path = @params[:tmp_upload_path] - identifier = @params[:identifier] - filename = @params[:filename] - tmp_directory = @params[:tmp_directory] + identifier = @params[:identifier] + filename = @params[:filename] + tmp_directory = @params[:tmp_directory] # delete destination files begin @@ -68,5 +68,4 @@ class HandleChunkUpload rescue Errno::ENOENT end end - end diff --git a/app/services/heat_settings_updater.rb b/app/services/heat_settings_updater.rb index 56666b71f47..325e87117c4 100644 --- a/app/services/heat_settings_updater.rb +++ b/app/services/heat_settings_updater.rb @@ -70,23 +70,20 @@ class HeatSettingsUpdater if SiteSetting.get(name) != SiteSetting.defaults[name] SiteSetting.set_and_log(name, SiteSetting.defaults[name]) end - elsif SiteSetting.get(name) == 0 || - (new_value.to_f / SiteSetting.get(name) - 1.0).abs >= 0.05 - - rounded_new_value = if new_value.is_a?(Integer) - if new_value > 9 - digits = new_value.digits.reverse - (digits[0] * 10 + digits[1]) * 10.pow(digits[2..-1].size) + elsif SiteSetting.get(name) == 0 || (new_value.to_f / SiteSetting.get(name) - 1.0).abs >= 0.05 + rounded_new_value = + if new_value.is_a?(Integer) + if new_value > 9 + digits = new_value.digits.reverse + (digits[0] * 10 + digits[1]) * 10.pow(digits[2..-1].size) + else + new_value + end else - new_value + new_value.round(2) end - else - new_value.round(2) - end - if SiteSetting.get(name) != rounded_new_value - SiteSetting.set_and_log(name, rounded_new_value) - end + SiteSetting.set_and_log(name, rounded_new_value) if SiteSetting.get(name) != rounded_new_value end end end diff --git a/app/services/inline_uploads.rb b/app/services/inline_uploads.rb index 62d081caa32..8feb0efc935 100644 --- a/app/services/inline_uploads.rb +++ b/app/services/inline_uploads.rb @@ -16,14 +16,20 @@ class InlineUploads end end - cooked_fragment = Nokogiri::HTML5::fragment(PrettyText.cook(markdown, disable_emojis: true)) + cooked_fragment = Nokogiri::HTML5.fragment(PrettyText.cook(markdown, disable_emojis: true)) link_occurrences = [] cooked_fragment.traverse do |node| if node.name == "img" # Do nothing - elsif !(node.children.count == 1 && (node.children[0].name != "img" && node.children[0].children.blank?)) && - !(node.name == "a" && node.children.count > 1 && !node_children_names(node).include?("img")) + elsif !( + node.children.count == 1 && + (node.children[0].name != "img" && node.children[0].children.blank?) + ) && + !( + node.name == "a" && node.children.count > 1 && + !node_children_names(node).include?("img") + ) next end @@ -55,7 +61,7 @@ class InlineUploads end regexps = [ - /(https?:\/\/[a-zA-Z0-9\.\/-]+\/#{Discourse.store.upload_path}#{UPLOAD_REGEXP_PATTERN})/, + %r{(https?://[a-zA-Z0-9\./-]+/#{Discourse.store.upload_path}#{UPLOAD_REGEXP_PATTERN})}, ] if Discourse.store.external? @@ -103,41 +109,38 @@ class InlineUploads raw_matches .sort { |a, b| a[3] <=> b[3] } .each do |match, link, replace_with, _index| + node_info = link_occurrences.shift + next unless node_info&.dig(:is_valid) - node_info = link_occurrences.shift - next unless node_info&.dig(:is_valid) - - if link.include?(node_info[:link]) - begin - uri = URI(link) - rescue URI::Error - end - - if !Discourse.store.external? - host = uri&.host - - hosts = [Discourse.current_hostname] - - if cdn_url = GlobalSetting.cdn_url - hosts << URI(GlobalSetting.cdn_url).hostname + if link.include?(node_info[:link]) + begin + uri = URI(link) + rescue URI::Error end - if host && !hosts.include?(host) - next + if !Discourse.store.external? + host = uri&.host + + hosts = [Discourse.current_hostname] + + if cdn_url = GlobalSetting.cdn_url + hosts << URI(GlobalSetting.cdn_url).hostname + end + + next if host && !hosts.include?(host) end - end - upload = Upload.get_from_url(link) + upload = Upload.get_from_url(link) - if upload - replace_with.sub!(PLACEHOLDER, upload.short_url) - replace_with.sub!(PATH_PLACEHOLDER, upload.short_path) - markdown.sub!(match, replace_with) - else - on_missing.call(link) if on_missing + if upload + replace_with.sub!(PLACEHOLDER, upload.short_url) + replace_with.sub!(PATH_PLACEHOLDER, upload.short_path) + markdown.sub!(match, replace_with) + else + on_missing.call(link) if on_missing + end end end - end markdown.scan(/(__(\h{40})__)/) do |match| upload = Upload.find_by(sha1: match[1]) @@ -161,7 +164,7 @@ class InlineUploads end def self.match_bbcode_img(markdown, external_src: false) - markdown.scan(/(\[img\]\s*([^\[\]\s]+)\s*\[\/img\])/i) do |match| + markdown.scan(%r{(\[img\]\s*([^\[\]\s]+)\s*\[/img\])}i) do |match| if (external_src || (matched_uploads(match[1]).present?)) && block_given? yield(match[0], match[1], +"![](#{PLACEHOLDER})", $~.offset(0)[0]) end @@ -182,9 +185,9 @@ class InlineUploads end def self.match_anchor(markdown, external_href: false) - markdown.scan(/(()([^<\a>]*?)<\/a>)/i) do |match| - node = Nokogiri::HTML5::fragment(match[0]).children[0] - href = node.attributes["href"]&.value + markdown.scan(%r{(()([^<\a>]*?))}i) do |match| + node = Nokogiri::HTML5.fragment(match[0]).children[0] + href = node.attributes["href"]&.value if href && (external_href || matched_uploads(href).present?) has_attachment = node.attributes["class"]&.value @@ -198,8 +201,8 @@ class InlineUploads end def self.match_img(markdown, external_src: false, uploads: nil) - markdown.scan(/(<(?!img)[^<>]+\/?>)?(\s*)(\n]+>)/i) do |match| - node = Nokogiri::HTML5::fragment(match[2].strip).children[0] + markdown.scan(%r{(<(?!img)[^<>]+/?>)?(\s*)(\n]+>)}i) do |match| + node = Nokogiri::HTML5.fragment(match[2].strip).children[0] src = node&.attributes&.[]("src")&.value if src && (external_src || matched_uploads(src).present?) @@ -215,22 +218,20 @@ class InlineUploads end def self.replace_hotlinked_image_urls(raw:, &blk) - replace = Proc.new do |match, match_src, replacement, _index| - upload = blk.call(match_src) - next if !upload + replace = + Proc.new do |match, match_src, replacement, _index| + upload = blk.call(match_src) + next if !upload - replacement = - if replacement.include?(InlineUploads::PLACEHOLDER) - replacement.sub(InlineUploads::PLACEHOLDER, upload.short_url) - elsif replacement.include?(InlineUploads::PATH_PLACEHOLDER) - replacement.sub(InlineUploads::PATH_PLACEHOLDER, upload.short_path) - end + replacement = + if replacement.include?(InlineUploads::PLACEHOLDER) + replacement.sub(InlineUploads::PLACEHOLDER, upload.short_url) + elsif replacement.include?(InlineUploads::PATH_PLACEHOLDER) + replacement.sub(InlineUploads::PATH_PLACEHOLDER, upload.short_path) + end - raw = raw.gsub( - match, - replacement - ) - end + raw = raw.gsub(match, replacement) + end # there are 6 ways to insert an image in a post # HTML tag - @@ -245,40 +246,41 @@ class InlineUploads # Markdown inline - ![alt](http://... "image title") InlineUploads.match_md_inline_img(raw, external_src: true, &replace) - raw = raw.gsub(/^(https?:\/\/\S+)(\s?)$/) do |match| - if upload = blk.call(match) - "![](#{upload.short_url})" - else - match + raw = + raw.gsub(%r{^(https?://\S+)(\s?)$}) do |match| + if upload = blk.call(match) + "![](#{upload.short_url})" + else + match + end end - end raw end def self.matched_uploads(node) upload_path = Discourse.store.upload_path - base_url = Discourse.base_url.sub(/https?:\/\//, "(https?://)") + base_url = Discourse.base_url.sub(%r{https?://}, "(https?://)") regexps = [ - /(upload:\/\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, - /(\/uploads\/short-url\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, - /(#{base_url}\/uploads\/short-url\/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)/, - /(#{GlobalSetting.relative_url_root}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/, - /(#{base_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/, + %r{(upload://([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)}, + %r{(/uploads/short-url/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)}, + %r{(#{base_url}/uploads/short-url/([a-zA-Z0-9]+)[a-zA-Z0-9\.]*)}, + %r{(#{GlobalSetting.relative_url_root}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})}, + %r{(#{base_url}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})}, ] - if GlobalSetting.cdn_url && (cdn_url = GlobalSetting.cdn_url.sub(/https?:\/\//, "(https?://)")) - regexps << /(#{cdn_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ + if GlobalSetting.cdn_url && (cdn_url = GlobalSetting.cdn_url.sub(%r{https?://}, "(https?://)")) + regexps << %r{(#{cdn_url}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})} if GlobalSetting.relative_url_root.present? - regexps << /(#{cdn_url}#{GlobalSetting.relative_url_root}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ + regexps << %r{(#{cdn_url}#{GlobalSetting.relative_url_root}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})} end end if Discourse.store.external? if Rails.configuration.multisite - regexps << /((https?:)?#{SiteSetting.Upload.s3_base_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ - regexps << /(#{SiteSetting.Upload.s3_cdn_url}\/#{upload_path}#{UPLOAD_REGEXP_PATTERN})/ + regexps << %r{((https?:)?#{SiteSetting.Upload.s3_base_url}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})} + regexps << %r{(#{SiteSetting.Upload.s3_cdn_url}/#{upload_path}#{UPLOAD_REGEXP_PATTERN})} else regexps << /((https?:)?#{SiteSetting.Upload.s3_base_url}#{UPLOAD_REGEXP_PATTERN})/ regexps << /(#{SiteSetting.Upload.s3_cdn_url}#{UPLOAD_REGEXP_PATTERN})/ @@ -289,9 +291,7 @@ class InlineUploads node = node.to_s regexps.each do |regexp| - node.scan(/(^|[\n\s"'\(>])#{regexp}($|[\n\s"'\)<])/) do |matched| - matches << matched[1] - end + node.scan(/(^|[\n\s"'\(>])#{regexp}($|[\n\s"'\)<])/) { |matched| matches << matched[1] } end matches @@ -304,9 +304,7 @@ class InlineUploads return names end - node.children.each do |child| - names = node_children_names(child, names) - end + node.children.each { |child| names = node_children_names(child, names) } names end diff --git a/app/services/notification_emailer.rb b/app/services/notification_emailer.rb index b768ae5b68b..60d124dc965 100644 --- a/app/services/notification_emailer.rb +++ b/app/services/notification_emailer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class NotificationEmailer - class EmailUser attr_reader :notification, :no_delay @@ -84,7 +83,6 @@ class NotificationEmailer end def enqueue_private(type, delay = private_delay) - if notification.user.user_option.nil? # this can happen if we roll back user creation really early # or delete user @@ -92,7 +90,9 @@ class NotificationEmailer return end - return if notification.user.user_option.email_messages_level == UserOption.email_level_types[:never] + if notification.user.user_option.email_messages_level == UserOption.email_level_types[:never] + return + end perform_enqueue(type, delay) end @@ -116,13 +116,13 @@ class NotificationEmailer end def post_type - @post_type ||= begin - type = notification.data_hash["original_post_type"] if notification.data_hash - type ||= notification.post.try(:post_type) - type - end + @post_type ||= + begin + type = notification.data_hash["original_post_type"] if notification.data_hash + type ||= notification.post.try(:post_type) + type + end end - end def self.disable @@ -136,9 +136,8 @@ class NotificationEmailer def self.process_notification(notification, no_delay: false) return if @disabled - email_user = EmailUser.new(notification, no_delay: no_delay) + email_user = EmailUser.new(notification, no_delay: no_delay) email_method = Notification.types[notification.notification_type] email_user.public_send(email_method) if email_user.respond_to? email_method end - end diff --git a/app/services/notifications/consolidate_notifications.rb b/app/services/notifications/consolidate_notifications.rb index adfeaee10fc..b50eb504e1a 100644 --- a/app/services/notifications/consolidate_notifications.rb +++ b/app/services/notifications/consolidate_notifications.rb @@ -34,7 +34,14 @@ module Notifications class ConsolidateNotifications < ConsolidationPlan - def initialize(from:, to:, consolidation_window: nil, unconsolidated_query_blk: nil, consolidated_query_blk: nil, threshold:) + def initialize( + from:, + to:, + consolidation_window: nil, + unconsolidated_query_blk: nil, + consolidated_query_blk: nil, + threshold: + ) @from = from @to = to @threshold = threshold @@ -67,15 +74,21 @@ module Notifications return unless can_consolidate_data?(notification) update_consolidated_notification!(notification) || - create_consolidated_notification!(notification) || - notification.tap(&:save!) + create_consolidated_notification!(notification) || notification.tap(&:save!) end private attr_reader( - :notification, :from, :to, :data, :threshold, :consolidated_query_blk, - :unconsolidated_query_blk, :consolidation_window, :bump_notification + :notification, + :from, + :to, + :data, + :threshold, + :consolidated_query_blk, + :unconsolidated_query_blk, + :consolidation_window, + :bump_notification, ) def update_consolidated_notification!(notification) @@ -90,18 +103,12 @@ module Notifications data_hash = consolidated.data_hash.merge(data) data_hash[:count] += 1 if data_hash[:count].present? - if @before_update_blk - @before_update_blk.call(consolidated, data_hash, notification) - end + @before_update_blk.call(consolidated, data_hash, notification) if @before_update_blk # Hack: We don't want to cache the old data if we're about to update it. consolidated.instance_variable_set(:@data_hash, nil) - consolidated.update!( - data: data_hash.to_json, - read: false, - updated_at: timestamp, - ) + consolidated.update!(data: data_hash.to_json, read: false, updated_at: timestamp) consolidated end @@ -119,22 +126,21 @@ module Notifications timestamp = notifications.last.created_at data[:count] = count_after_saving_notification - if @before_consolidation_blk - @before_consolidation_blk.call(notifications, data) - end + @before_consolidation_blk.call(notifications, data) if @before_consolidation_blk consolidated = nil Notification.transaction do notifications.destroy_all - consolidated = Notification.create!( - notification_type: to, - user_id: notification.user_id, - data: data.to_json, - updated_at: timestamp, - created_at: timestamp - ) + consolidated = + Notification.create!( + notification_type: to, + user_id: notification.user_id, + data: data.to_json, + updated_at: timestamp, + created_at: timestamp, + ) end consolidated @@ -148,7 +154,7 @@ module Notifications notifications = super(notification, type) if consolidation_window.present? - notifications = notifications.where('created_at > ?', consolidation_window.ago) + notifications = notifications.where("created_at > ?", consolidation_window.ago) end notifications diff --git a/app/services/notifications/consolidation_planner.rb b/app/services/notifications/consolidation_planner.rb index 6d1f5cb44d0..87704b010e8 100644 --- a/app/services/notifications/consolidation_planner.rb +++ b/app/services/notifications/consolidation_planner.rb @@ -12,105 +12,125 @@ module Notifications private def plan_for(notification) - consolidation_plans = [liked_by_two_users, liked, group_message_summary, group_membership, new_features_notification] + consolidation_plans = [ + liked_by_two_users, + liked, + group_message_summary, + group_membership, + new_features_notification, + ] consolidation_plans.concat(DiscoursePluginRegistry.notification_consolidation_plans) consolidation_plans.detect { |plan| plan.can_consolidate_data?(notification) } end def liked - ConsolidateNotifications.new( - from: Notification.types[:liked], - to: Notification.types[:liked_consolidated], - threshold: -> { SiteSetting.notification_consolidation_threshold }, - consolidation_window: SiteSetting.likes_notification_consolidation_window_mins.minutes, - unconsolidated_query_blk: Proc.new do |notifications, data| - key = 'display_username' - value = data[key.to_sym] - filtered = notifications.where("data::json ->> 'username2' IS NULL") + ConsolidateNotifications + .new( + from: Notification.types[:liked], + to: Notification.types[:liked_consolidated], + threshold: -> { SiteSetting.notification_consolidation_threshold }, + consolidation_window: SiteSetting.likes_notification_consolidation_window_mins.minutes, + unconsolidated_query_blk: + Proc.new do |notifications, data| + key = "display_username" + value = data[key.to_sym] + filtered = notifications.where("data::json ->> 'username2' IS NULL") - filtered = filtered.where("data::json ->> '#{key}' = ?", value) if value + filtered = filtered.where("data::json ->> '#{key}' = ?", value) if value - filtered - end, - consolidated_query_blk: filtered_by_data_attribute('display_username') - ).set_mutations( - set_data_blk: Proc.new do |notification| - data = notification.data_hash - data.merge(username: data[:display_username]) - end - ).set_precondition(precondition_blk: Proc.new { |data| data[:username2].blank? }) + filtered + end, + consolidated_query_blk: filtered_by_data_attribute("display_username"), + ) + .set_mutations( + set_data_blk: + Proc.new do |notification| + data = notification.data_hash + data.merge(username: data[:display_username]) + end, + ) + .set_precondition(precondition_blk: Proc.new { |data| data[:username2].blank? }) end def liked_by_two_users - DeletePreviousNotifications.new( - type: Notification.types[:liked], - previous_query_blk: Proc.new do |notifications, data| - notifications.where(id: data[:previous_notification_id]) - end - ).set_mutations( - set_data_blk: Proc.new do |notification| - existing_notification_of_same_type = Notification - .where(user: notification.user) - .order("notifications.id DESC") - .where(topic_id: notification.topic_id, post_number: notification.post_number) - .where(notification_type: notification.notification_type) - .where('created_at > ?', 1.day.ago) - .first + DeletePreviousNotifications + .new( + type: Notification.types[:liked], + previous_query_blk: + Proc.new do |notifications, data| + notifications.where(id: data[:previous_notification_id]) + end, + ) + .set_mutations( + set_data_blk: + Proc.new do |notification| + existing_notification_of_same_type = + Notification + .where(user: notification.user) + .order("notifications.id DESC") + .where(topic_id: notification.topic_id, post_number: notification.post_number) + .where(notification_type: notification.notification_type) + .where("created_at > ?", 1.day.ago) + .first - data = notification.data_hash - if existing_notification_of_same_type - same_type_data = existing_notification_of_same_type.data_hash - data.merge( - previous_notification_id: existing_notification_of_same_type.id, - username2: same_type_data[:display_username], - count: (same_type_data[:count] || 1).to_i + 1 - ) - else - data - end - end - ).set_precondition( - precondition_blk: Proc.new do |data, notification| - always_freq = UserOption.like_notification_frequency_type[:always] + data = notification.data_hash + if existing_notification_of_same_type + same_type_data = existing_notification_of_same_type.data_hash + data.merge( + previous_notification_id: existing_notification_of_same_type.id, + username2: same_type_data[:display_username], + count: (same_type_data[:count] || 1).to_i + 1, + ) + else + data + end + end, + ) + .set_precondition( + precondition_blk: + Proc.new do |data, notification| + always_freq = UserOption.like_notification_frequency_type[:always] - notification.user&.user_option&.like_notification_frequency == always_freq && - data[:previous_notification_id].present? - end - ) + notification.user&.user_option&.like_notification_frequency == always_freq && + data[:previous_notification_id].present? + end, + ) end def group_membership - ConsolidateNotifications.new( - from: Notification.types[:private_message], - to: Notification.types[:membership_request_consolidated], - threshold: -> { SiteSetting.notification_consolidation_threshold }, - consolidation_window: Notification::MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours, - unconsolidated_query_blk: filtered_by_data_attribute('topic_title'), - consolidated_query_blk: filtered_by_data_attribute('group_name') - ).set_precondition( - precondition_blk: Proc.new { |data| data[:group_name].present? } - ).set_mutations( - set_data_blk: Proc.new do |notification| - data = notification.data_hash - post_id = data[:original_post_id] - custom_field = PostCustomField.select(:value).find_by(post_id: post_id, name: "requested_group_id") - group_id = custom_field&.value - group_name = group_id.present? ? Group.select(:name).find_by(id: group_id.to_i)&.name : nil + ConsolidateNotifications + .new( + from: Notification.types[:private_message], + to: Notification.types[:membership_request_consolidated], + threshold: -> { SiteSetting.notification_consolidation_threshold }, + consolidation_window: Notification::MEMBERSHIP_REQUEST_CONSOLIDATION_WINDOW_HOURS.hours, + unconsolidated_query_blk: filtered_by_data_attribute("topic_title"), + consolidated_query_blk: filtered_by_data_attribute("group_name"), + ) + .set_precondition(precondition_blk: Proc.new { |data| data[:group_name].present? }) + .set_mutations( + set_data_blk: + Proc.new do |notification| + data = notification.data_hash + post_id = data[:original_post_id] + custom_field = + PostCustomField.select(:value).find_by(post_id: post_id, name: "requested_group_id") + group_id = custom_field&.value + group_name = + group_id.present? ? Group.select(:name).find_by(id: group_id.to_i)&.name : nil - data[:group_name] = group_name - data - end - ) + data[:group_name] = group_name + data + end, + ) end def group_message_summary DeletePreviousNotifications.new( type: Notification.types[:group_message_summary], - previous_query_blk: filtered_by_data_attribute('group_id') - ).set_precondition( - precondition_blk: Proc.new { |data| data[:group_id].present? } - ) + previous_query_blk: filtered_by_data_attribute("group_id"), + ).set_precondition(precondition_blk: Proc.new { |data| data[:group_id].present? }) end def filtered_by_data_attribute(attribute_name) diff --git a/app/services/notifications/delete_previous_notifications.rb b/app/services/notifications/delete_previous_notifications.rb index 955af73a012..d6f561c1b6d 100644 --- a/app/services/notifications/delete_previous_notifications.rb +++ b/app/services/notifications/delete_previous_notifications.rb @@ -35,9 +35,7 @@ module Notifications return unless can_consolidate_data?(notification) notifications = user_notifications(notification, type) - if previous_query_blk.present? - notifications = previous_query_blk.call(notifications, data) - end + notifications = previous_query_blk.call(notifications, data) if previous_query_blk.present? notification.data = data.to_json diff --git a/app/services/post_action_notifier.rb b/app/services/post_action_notifier.rb index 71bf1e5d80f..c90fa520349 100644 --- a/app/services/post_action_notifier.rb +++ b/app/services/post_action_notifier.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PostActionNotifier - def self.disable @disabled = true end @@ -22,11 +21,14 @@ class PostActionNotifier def self.refresh_like_notification(post, read) return unless post && post.user_id && post.topic - usernames = post.post_actions.where(post_action_type_id: PostActionType.types[:like]) - .joins(:user) - .order('post_actions.created_at desc') - .where('post_actions.created_at > ?', 1.day.ago) - .pluck(:username) + usernames = + post + .post_actions + .where(post_action_type_id: PostActionType.types[:like]) + .joins(:user) + .order("post_actions.created_at desc") + .where("post_actions.created_at > ?", 1.day.ago) + .pluck(:username) if usernames.length > 0 data = { @@ -34,7 +36,7 @@ class PostActionNotifier username: usernames[0], display_username: usernames[0], username2: usernames[1], - count: usernames.length + count: usernames.length, } Notification.create( notification_type: Notification.types[:liked], @@ -42,7 +44,7 @@ class PostActionNotifier post_number: post.post_number, user_id: post.user_id, read: read, - data: data.to_json + data: data.to_json, ) end end @@ -54,18 +56,19 @@ class PostActionNotifier return if post_action.deleted_at.blank? if post_action.post_action_type_id == PostActionType.types[:like] && post_action.post - read = true - Notification.where( - topic_id: post_action.post.topic_id, - user_id: post_action.post.user_id, - post_number: post_action.post.post_number, - notification_type: Notification.types[:liked] - ).each do |notification| - read = false unless notification.read - notification.destroy - end + Notification + .where( + topic_id: post_action.post.topic_id, + user_id: post_action.post.user_id, + post_number: post_action.post.post_number, + notification_type: Notification.types[:liked], + ) + .each do |notification| + read = false unless notification.read + notification.destroy + end refresh_like_notification(post_action.post, read) else @@ -89,7 +92,7 @@ class PostActionNotifier post, display_username: post_action.user.username, post_action_id: post_action.id, - user_id: post_action.user_id + user_id: post_action.user_id, ) end @@ -106,19 +109,18 @@ class PostActionNotifier user_ids = [] - if post_revision.user_id != post.user_id - user_ids << post.user_id - end + user_ids << post.user_id if post_revision.user_id != post.user_id # Notify all users watching the topic when the OP of a wiki topic is edited # or if the topic category allows unlimited owner edits on the OP. if post.is_first_post? && - (post.wiki? || post.topic.category_allows_unlimited_owner_edits_on_first_post?) + (post.wiki? || post.topic.category_allows_unlimited_owner_edits_on_first_post?) user_ids.concat( - TopicUser.watching(post.topic_id) + TopicUser + .watching(post.topic_id) .where.not(user_id: post_revision.user_id) .where(topic: post.topic) - .pluck(:user_id) + .pluck(:user_id), ) end @@ -128,10 +130,7 @@ class PostActionNotifier if user_ids.present? DB.after_commit do - Jobs.enqueue(:notify_post_revision, - user_ids: user_ids, - post_revision_id: post_revision.id - ) + Jobs.enqueue(:notify_post_revision, user_ids: user_ids, post_revision_id: post_revision.id) end end end @@ -145,7 +144,7 @@ class PostActionNotifier Notification.types[:edited], post, display_username: post.last_editor.username, - acting_user_id: post.last_editor.id + acting_user_id: post.last_editor.id, ) end end @@ -162,8 +161,13 @@ class PostActionNotifier def self.notification_is_disabled?(post_revision) modifications = post_revision.modifications - (SiteSetting.disable_system_edit_notifications && post_revision.user_id == Discourse::SYSTEM_USER_ID) || - (SiteSetting.disable_category_edit_notifications && modifications&.dig("category_id").present?) || - (SiteSetting.disable_tags_edit_notifications && modifications&.dig("tags").present?) + ( + SiteSetting.disable_system_edit_notifications && + post_revision.user_id == Discourse::SYSTEM_USER_ID + ) || + ( + SiteSetting.disable_category_edit_notifications && + modifications&.dig("category_id").present? + ) || (SiteSetting.disable_tags_edit_notifications && modifications&.dig("tags").present?) end end diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index c20ed382f0d..8b8de6aa827 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -18,13 +18,14 @@ class PostAlerter if post_url = post.url payload = { - notification_type: notification_type, - post_number: post.post_number, - topic_title: post.topic.title, - topic_id: post.topic.id, - excerpt: excerpt || post.excerpt(400, text_entities: true, strip_links: true, remap_emoji: true), - username: username || post.username, - post_url: post_url + notification_type: notification_type, + post_number: post.post_number, + topic_title: post.topic.title, + topic_id: post.topic.id, + excerpt: + excerpt || post.excerpt(400, text_entities: true, strip_links: true, remap_emoji: true), + username: username || post.username, + post_url: post_url, } DiscourseEvent.trigger(:pre_notification_alert, user, payload) @@ -51,22 +52,25 @@ class PostAlerter SiteSetting.push_notification_time_window_mins.minutes, :send_push_notification, user_id: user.id, - payload: payload + payload: payload, ) else Jobs.enqueue(:send_push_notification, user_id: user.id, payload: payload) end end - if SiteSetting.allow_user_api_key_scopes.split("|").include?("push") && SiteSetting.allowed_user_api_push_urls.present? - clients = user.user_api_keys - .joins(:scopes) - .where("user_api_key_scopes.name IN ('push', 'notifications')") - .where("push_url IS NOT NULL AND push_url <> ''") - .where("position(push_url IN ?) > 0", SiteSetting.allowed_user_api_push_urls) - .where("revoked_at IS NULL") - .order(client_id: :asc) - .pluck(:client_id, :push_url) + if SiteSetting.allow_user_api_key_scopes.split("|").include?("push") && + SiteSetting.allowed_user_api_push_urls.present? + clients = + user + .user_api_keys + .joins(:scopes) + .where("user_api_key_scopes.name IN ('push', 'notifications')") + .where("push_url IS NOT NULL AND push_url <> ''") + .where("position(push_url IN ?) > 0", SiteSetting.allowed_user_api_push_urls) + .where("revoked_at IS NULL") + .order(client_id: :asc) + .pluck(:client_id, :push_url) if clients.length > 0 Jobs.enqueue(:push_notification, clients: clients, payload: payload, user_id: user.id) @@ -79,9 +83,7 @@ class PostAlerter end def not_allowed?(user, post) - user.blank? || - user.bot? || - user.id == post.user_id + user.blank? || user.bot? || user.id == post.user_id end def all_allowed_users(post) @@ -130,7 +132,11 @@ class PostAlerter if post.last_editor_id != post.user_id # Mention comes from an edit by someone else, so notification should say who added the mention. - mentioned_opts = { user_id: editor.id, original_username: editor.username, display_username: editor.username } + mentioned_opts = { + user_id: editor.id, + original_username: editor.username, + display_username: editor.username, + } end if mentioned_users @@ -141,7 +147,8 @@ class PostAlerter expand_group_mentions(mentioned_groups, post) do |group, users| users = only_allowed_users(users, post) - notified += notify_users(users - notified, :group_mentioned, post, mentioned_opts.merge(group: group)) + notified += + notify_users(users - notified, :group_mentioned, post, mentioned_opts.merge(group: group)) end if mentioned_here @@ -162,7 +169,8 @@ class PostAlerter end topic_author = post.topic.user - if topic_author && !notified.include?(topic_author) && user_watching_topic?(topic_author, post.topic) + if topic_author && !notified.include?(topic_author) && + user_watching_topic?(topic_author, post.topic) notified += notify_non_pm_users(topic_author, :replied, post) end end @@ -187,8 +195,22 @@ class PostAlerter notified += notify_pm_users(post, reply_to_user, quoted_users, notified) elsif notify_about_reply?(post) # posts - notified += notify_post_users(post, notified, new_record: new_record, include_category_watchers: false, include_tag_watchers: false) - notified += notify_post_users(post, notified, new_record: new_record, include_topic_watchers: false, notification_type: :watching_category_or_tag) + notified += + notify_post_users( + post, + notified, + new_record: new_record, + include_category_watchers: false, + include_tag_watchers: false, + ) + notified += + notify_post_users( + post, + notified, + new_record: new_record, + include_topic_watchers: false, + notification_type: :watching_category_or_tag, + ) end end @@ -213,7 +235,7 @@ class PostAlerter def group_watchers(topic) GroupUser.where( group_id: topic.allowed_groups.pluck(:group_id), - notification_level: GroupUser.notification_levels[:watching_first_post] + notification_level: GroupUser.notification_levels[:watching_first_post], ).pluck(:user_id) end @@ -221,11 +243,13 @@ class PostAlerter topic .tag_users .notification_level_visible([TagUser.notification_levels[:watching_first_post]]) - .distinct(:user_id).pluck(:user_id) + .distinct(:user_id) + .pluck(:user_id) end def category_watchers(topic) - topic.category_users + topic + .category_users .where(notification_level: CategoryUser.notification_levels[:watching_first_post]) .pluck(:user_id) end @@ -259,23 +283,23 @@ class PostAlerter # running concurrently GroupMention.insert_all( mentioned_groups.map do |group| - { - post_id: post.id, - group_id: group.id, - created_at: now, - updated_at: now, - } - end + { post_id: post.id, group_id: group.id, created_at: now, updated_at: now } + end, ) end def unread_posts(user, topic) - Post.secured(Guardian.new(user)) - .where('post_number > COALESCE(( + Post + .secured(Guardian.new(user)) + .where( + "post_number > COALESCE(( SELECT last_read_post_number FROM topic_users tu - WHERE tu.user_id = ? AND tu.topic_id = ? ),0)', - user.id, topic.id) - .where('reply_to_user_id = :user_id + WHERE tu.user_id = ? AND tu.topic_id = ? ),0)", + user.id, + topic.id, + ) + .where( + "reply_to_user_id = :user_id OR exists(SELECT 1 from topic_users tu WHERE tu.user_id = :user_id AND tu.topic_id = :topic_id AND @@ -287,19 +311,19 @@ class PostAlerter OR exists(SELECT 1 from tag_users tu WHERE tu.user_id = :user_id AND tu.tag_id IN (SELECT tag_id FROM topic_tags WHERE topic_id = :topic_id) AND - notification_level = :tag_level)', + notification_level = :tag_level)", user_id: user.id, topic_id: topic.id, category_id: topic.category_id, topic_level: TopicUser.notification_levels[:watching], category_level: CategoryUser.notification_levels[:watching], - tag_level: TagUser.notification_levels[:watching] + tag_level: TagUser.notification_levels[:watching], ) .where(topic_id: topic.id) end def first_unread_post(user, topic) - unread_posts(user, topic).order('post_number').first + unread_posts(user, topic).order("post_number").first end def unread_count(user, topic) @@ -311,28 +335,26 @@ class PostAlerter return unless Guardian.new(user).can_see?(topic) User.transaction do - user.notifications.where( - notification_type: types, - topic_id: topic.id - ).destroy_all + user.notifications.where(notification_type: types, topic_id: topic.id).destroy_all # Reload so notification counts sync up correctly user.reload end end - NOTIFIABLE_TYPES = [ - :mentioned, - :replied, - :quoted, - :posted, - :linked, - :private_message, - :group_mentioned, - :watching_first_post, - :event_reminder, - :event_invitation - ].map { |t| Notification.types[t] } + NOTIFIABLE_TYPES = + %i[ + mentioned + replied + quoted + posted + linked + private_message + group_mentioned + watching_first_post + event_reminder + event_invitation + ].map { |t| Notification.types[t] } def group_stats(topic) sql = <<~SQL @@ -346,7 +368,7 @@ class PostAlerter { group_id: g.id, group_name: g.name, - inbox_count: DB.query_single(sql, group_id: g.id).first.to_i + inbox_count: DB.query_single(sql, group_id: g.id).first.to_i, } end end @@ -356,10 +378,8 @@ class PostAlerter stats = (@group_stats[topic.id] ||= group_stats(topic)) return unless stats - group_id = topic - .topic_allowed_groups - .where(group_id: user.groups.pluck(:id)) - .pluck_first(:group_id) + group_id = + topic.topic_allowed_groups.where(group_id: user.groups.pluck(:id)).pluck_first(:group_id) stat = stats.find { |s| s[:group_id] == group_id } return unless stat @@ -374,11 +394,12 @@ class PostAlerter group_id: stat[:group_id], group_name: stat[:group_name], inbox_count: stat[:inbox_count], - username: user.username_lower - }.to_json + username: user.username_lower, + }.to_json, ) else - Notification.where(user_id: user.id, notification_type: Notification.types[:group_message_summary]) + Notification + .where(user_id: user.id, notification_type: Notification.types[:group_message_summary]) .where("data::json ->> 'group_id' = ?", stat[:group_id].to_s) .delete_all end @@ -389,20 +410,31 @@ class PostAlerter def should_notify_edit?(notification, post, opts) notification.created_at < 1.day.ago || - notification.data_hash["display_username"] != (opts[:display_username].presence || post.user.username) + notification.data_hash["display_username"] != + (opts[:display_username].presence || post.user.username) end def should_notify_like?(user, notification) - return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:always] - return true if user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:first_time_and_daily] && notification.created_at < 1.day.ago + if user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:always] + return true + end + if user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:first_time_and_daily] && + notification.created_at < 1.day.ago + return true + end false end def should_notify_previous?(user, post, notification, opts) case notification.notification_type - when Notification.types[:edited] then should_notify_edit?(notification, post, opts) - when Notification.types[:liked] then should_notify_like?(user, notification) - else false + when Notification.types[:edited] + should_notify_edit?(notification, post, opts) + when Notification.types[:liked] + should_notify_like?(user, notification) + else + false end end @@ -422,7 +454,11 @@ class PostAlerter return if (topic = post.topic).blank? is_liked = type == Notification.types[:liked] - return if is_liked && user.user_option.like_notification_frequency == UserOption.like_notification_frequency_type[:never] + if is_liked && + user.user_option.like_notification_frequency == + UserOption.like_notification_frequency_type[:never] + return + end # Make sure the user can see the post return unless Guardian.new(user).can_see?(post) @@ -430,48 +466,60 @@ class PostAlerter return if user.staged? && topic.category&.mailinglist_mirror? notifier_id = opts[:user_id] || post.user_id # xxxxx look at revision history - return if notifier_id && UserCommScreener.new( - acting_user_id: notifier_id, target_user_ids: user.id - ).ignoring_or_muting_actor?(user.id) + if notifier_id && + UserCommScreener.new( + acting_user_id: notifier_id, + target_user_ids: user.id, + ).ignoring_or_muting_actor?(user.id) + return + end # skip if muted on the topic - return if TopicUser.where( - topic: topic, - user: user, - notification_level: TopicUser.notification_levels[:muted] - ).exists? + if TopicUser.where( + topic: topic, + user: user, + notification_level: TopicUser.notification_levels[:muted], + ).exists? + return + end # skip if muted on the group if group = opts[:group] - return if GroupUser.where( - group_id: opts[:group_id], - user_id: user.id, - notification_level: TopicUser.notification_levels[:muted] - ).exists? + if GroupUser.where( + group_id: opts[:group_id], + user_id: user.id, + notification_level: TopicUser.notification_levels[:muted], + ).exists? + return + end end - existing_notifications = user.notifications - .order("notifications.id DESC") - .where( - topic_id: post.topic_id, - post_number: post.post_number - ).limit(10) + existing_notifications = + user + .notifications + .order("notifications.id DESC") + .where(topic_id: post.topic_id, post_number: post.post_number) + .limit(10) # Don't notify the same user about the same type of notification on the same post - existing_notification_of_same_type = existing_notifications.find { |n| n.notification_type == type } + existing_notification_of_same_type = + existing_notifications.find { |n| n.notification_type == type } - if existing_notification_of_same_type && !should_notify_previous?(user, post, existing_notification_of_same_type, opts) + if existing_notification_of_same_type && + !should_notify_previous?(user, post, existing_notification_of_same_type, opts) return end # linked, quoted, mentioned, chat_quoted may be suppressed if you already have a reply notification if [ - Notification.types[:quoted], - Notification.types[:linked], - Notification.types[:mentioned], - Notification.types[:chat_quoted] - ].include?(type) - return if existing_notifications.find { |n| n.notification_type == Notification.types[:replied] } + Notification.types[:quoted], + Notification.types[:linked], + Notification.types[:mentioned], + Notification.types[:chat_quoted], + ].include?(type) + if existing_notifications.find { |n| n.notification_type == Notification.types[:replied] } + return + end end collapsed = false @@ -489,7 +537,7 @@ class PostAlerter count = unread_count(user, topic) if count > 1 I18n.with_locale(user.effective_locale) do - opts[:display_username] = I18n.t('embed.replies', count: count) + opts[:display_username] = I18n.t("embed.replies", count: count) end end end @@ -513,9 +561,7 @@ class PostAlerter display_username: opts[:display_username] || post.user.username, } - if opts[:custom_data]&.is_a?(Hash) - opts[:custom_data].each { |k, v| notification_data[k] = v } - end + opts[:custom_data].each { |k, v| notification_data[k] = v } if opts[:custom_data]&.is_a?(Hash) if group = opts[:group] notification_data[:group_id] = group.id @@ -527,23 +573,29 @@ class PostAlerter elsif original_post.via_email && (incoming_email = original_post.incoming_email) skip_send_email = incoming_email.to_addresses_split.include?(user.email) || - incoming_email.cc_addresses_split.include?(user.email) + incoming_email.cc_addresses_split.include?(user.email) else skip_send_email = opts[:skip_send_email] end # Create the notification - created = user.notifications.consolidate_or_create!( - notification_type: type, - topic_id: post.topic_id, - post_number: post.post_number, - post_action_id: opts[:post_action_id], - data: notification_data.to_json, - skip_send_email: skip_send_email - ) + created = + user.notifications.consolidate_or_create!( + notification_type: type, + topic_id: post.topic_id, + post_number: post.post_number, + post_action_id: opts[:post_action_id], + data: notification_data.to_json, + skip_send_email: skip_send_email, + ) if created.id && existing_notifications.empty? && NOTIFIABLE_TYPES.include?(type) - create_notification_alert(user: user, post: original_post, notification_type: type, username: original_username) + create_notification_alert( + user: user, + post: original_post, + notification_type: type, + username: original_username, + ) end created.id ? created : nil @@ -555,7 +607,7 @@ class PostAlerter post: post, notification_type: notification_type, excerpt: excerpt, - username: username + username: username, ) end @@ -566,11 +618,13 @@ class PostAlerter def expand_group_mentions(groups, post) return unless post.user && groups - Group.mentionable(post.user, include_public: false).where(id: groups.map(&:id)).each do |group| - next if group.user_count >= SiteSetting.max_users_notified_per_group_mention - yield group, group.users - end - + Group + .mentionable(post.user, include_public: false) + .where(id: groups.map(&:id)) + .each do |group| + next if group.user_count >= SiteSetting.max_users_notified_per_group_mention + yield group, group.users + end end def expand_here_mention(post, exclude_ids: nil) @@ -583,9 +637,7 @@ class PostAlerter posts = posts.where(post_type: Post.types[:regular]) end - User.real - .where(id: posts.select(:user_id)) - .limit(SiteSetting.max_here_mentioned) + User.real.where(id: posts.select(:user_id)).limit(SiteSetting.max_here_mentioned) end # TODO: Move to post-analyzer? @@ -593,12 +645,16 @@ class PostAlerter mentions = post.raw_mentions return if mentions.blank? - groups = Group.where('LOWER(name) IN (?)', mentions) + groups = Group.where("LOWER(name) IN (?)", mentions) mentions -= groups.map(&:name).map(&:downcase) groups = nil if groups.empty? if mentions.present? - users = User.where(username_lower: mentions).includes(:do_not_disturb_timings).where.not(id: post.user_id) + users = + User + .where(username_lower: mentions) + .includes(:do_not_disturb_timings) + .where.not(id: post.user_id) users = nil if users.empty? end @@ -611,22 +667,28 @@ class PostAlerter # TODO: Move to post-analyzer? # Returns a list of users who were quoted in the post def extract_quoted_users(post) - usernames = if SiteSetting.display_name_on_posts && !SiteSetting.prioritize_username_in_ux - post.raw.scan(/username:([[:alnum:]]*)"(?=\])/) - else - post.raw.scan(/\[quote=\"([^,]+),.+\"\]/) - end.uniq.map { |q| q.first.strip.downcase } + usernames = + if SiteSetting.display_name_on_posts && !SiteSetting.prioritize_username_in_ux + post.raw.scan(/username:([[:alnum:]]*)"(?=\])/) + else + post.raw.scan(/\[quote=\"([^,]+),.+\"\]/) + end.uniq.map { |q| q.first.strip.downcase } User.where.not(id: post.user_id).where(username_lower: usernames) end def extract_linked_users(post) - users = post.topic_links.where(reflection: false).map do |link| - linked_post = link.link_post - if !linked_post && topic = link.link_topic - linked_post = topic.posts.find_by(post_number: 1) - end - (linked_post && post.user_id != linked_post.user_id && linked_post.user) || nil - end.compact + users = + post + .topic_links + .where(reflection: false) + .map do |link| + linked_post = link.link_post + if !linked_post && topic = link.link_topic + linked_post = topic.posts.find_by(post_number: 1) + end + (linked_post && post.user_id != linked_post.user_id && linked_post.user) || nil + end + .compact DiscourseEvent.trigger(:after_extract_linked_users, users, post) @@ -647,9 +709,7 @@ class PostAlerter warn_if_not_sidekiq DiscourseEvent.trigger(:before_create_notifications_for_users, users, post) - users.each do |u| - create_notification(u, Notification.types[type], post, opts) - end + users.each { |u| create_notification(u, Notification.types[type], post, opts) } users end @@ -681,7 +741,12 @@ class PostAlerter DiscourseEvent.trigger(:before_create_notifications_for_users, users, post) users.each do |user| if reply_to_user == user || pm_watching_users(post).include?(user) || user.staged? - create_notification(user, Notification.types[:private_message], post, skip_send_email_to: emails_to_skip_send) + create_notification( + user, + Notification.types[:private_message], + post, + skip_send_email_to: emails_to_skip_send, + ) end end @@ -711,17 +776,13 @@ class PostAlerter return if !SiteSetting.enable_smtp || post.post_type != Post.types[:regular] return if post.topic.allowed_groups.none? - if post.topic.allowed_groups.count == 1 - return post.topic.first_smtp_enabled_group - end + return post.topic.first_smtp_enabled_group if post.topic.allowed_groups.count == 1 topic_incoming_email = post.topic.incoming_email.first return if topic_incoming_email.blank? group = Group.find_by_email(topic_incoming_email.to_addresses) - if !group&.smtp_enabled - return post.topic.first_smtp_enabled_group - end + return post.topic.first_smtp_enabled_group if !group&.smtp_enabled group end @@ -735,9 +796,13 @@ class PostAlerter # We need to use topic_allowed_users here instead of directly_targeted_users # because we want to make sure the to_address goes to the OP of the topic. - topic_allowed_users_by_age = post.topic.topic_allowed_users.includes(:user).order(:created_at).reject do |tau| - not_allowed?(tau.user, post) - end + topic_allowed_users_by_age = + post + .topic + .topic_allowed_users + .includes(:user) + .order(:created_at) + .reject { |tau| not_allowed?(tau.user, post) } return emails_to_skip_send if topic_allowed_users_by_age.empty? # This should usually be the OP of the topic, unless they are the one @@ -778,7 +843,7 @@ class PostAlerter group_id: group.id, post_id: post.id, email: to_address, - cc_emails: cc_addresses + cc_emails: cc_addresses, ) # Add the group's email_username into the array, because it is used for @@ -790,7 +855,16 @@ class PostAlerter emails_to_skip_send.uniq end - def notify_post_users(post, notified, group_ids: nil, include_topic_watchers: true, include_category_watchers: true, include_tag_watchers: true, new_record: false, notification_type: nil) + def notify_post_users( + post, + notified, + group_ids: nil, + include_topic_watchers: true, + include_category_watchers: true, + include_tag_watchers: true, + new_record: false, + notification_type: nil + ) return [] unless post.topic warn_if_not_sidekiq @@ -803,18 +877,15 @@ class PostAlerter /*tags*/ ) SQL - if include_topic_watchers - condition.sub! "/*topic*/", <<~SQL + condition.sub! "/*topic*/", <<~SQL if include_topic_watchers UNION SELECT user_id FROM topic_users WHERE notification_level = :watching AND topic_id = :topic_id SQL - end - if include_category_watchers - condition.sub! "/*category*/", <<~SQL + condition.sub! "/*category*/", <<~SQL if include_category_watchers UNION SELECT cu.user_id @@ -825,12 +896,10 @@ class PostAlerter AND cu.category_id = :category_id AND (tu.user_id IS NULL OR tu.notification_level = :watching) SQL - end - tag_ids = post.topic.topic_tags.pluck('topic_tags.tag_id') + tag_ids = post.topic.topic_tags.pluck("topic_tags.tag_id") - if include_tag_watchers && tag_ids.present? - condition.sub! "/*tags*/", <<~SQL + condition.sub! "/*tags*/", <<~SQL if include_tag_watchers && tag_ids.present? UNION SELECT tag_users.user_id @@ -850,36 +919,36 @@ class PostAlerter AND tag_users.tag_id IN (:tag_ids) AND (tu.user_id IS NULL OR tu.notification_level = :watching)) SQL - end - notify = User.where(condition, - watching: TopicUser.notification_levels[:watching], - topic_id: post.topic_id, - category_id: post.topic.category_id, - tag_ids: tag_ids, - staff_group_id: Group::AUTO_GROUPS[:staff], - everyone_group_id: Group::AUTO_GROUPS[:everyone] - ) + notify = + User.where( + condition, + watching: TopicUser.notification_levels[:watching], + topic_id: post.topic_id, + category_id: post.topic.category_id, + tag_ids: tag_ids, + staff_group_id: Group::AUTO_GROUPS[:staff], + everyone_group_id: Group::AUTO_GROUPS[:everyone], + ) if group_ids.present? notify = notify.joins(:group_users).where("group_users.group_id IN (?)", group_ids) end - if post.topic.private_message? - notify = notify.where(staged: false).staff - end + notify = notify.where(staged: false).staff if post.topic.private_message? exclude_user_ids = notified.map(&:id) notify = notify.where("users.id NOT IN (?)", exclude_user_ids) if exclude_user_ids.present? DiscourseEvent.trigger(:before_create_notifications_for_users, notify, post) - already_seen_user_ids = Set.new( - TopicUser - .where(topic_id: post.topic.id) - .where("last_read_post_number >= ?", post.post_number) - .pluck(:user_id) - ) + already_seen_user_ids = + Set.new( + TopicUser + .where(topic_id: post.topic.id) + .where("last_read_post_number >= ?", post.post_number) + .pluck(:user_id), + ) each_user_in_batches(notify) do |user| calculated_type = @@ -891,7 +960,8 @@ class PostAlerter Notification.types[:posted] end opts = {} - opts[:display_username] = post.last_editor.username if calculated_type == Notification.types[:edited] + opts[:display_username] = post.last_editor.username if calculated_type == + Notification.types[:edited] create_notification(user, calculated_type, post, opts) end @@ -899,20 +969,31 @@ class PostAlerter end def warn_if_not_sidekiq - Rails.logger.warn("PostAlerter.#{caller_locations(1, 1)[0].label} was called outside of sidekiq") unless Sidekiq.server? + unless Sidekiq.server? + Rails.logger.warn( + "PostAlerter.#{caller_locations(1, 1)[0].label} was called outside of sidekiq", + ) + end end private def each_user_in_batches(users) # This is race-condition-safe, unlike #find_in_batches - users.pluck(:id).each_slice(USER_BATCH_SIZE) do |user_ids_batch| - User.where(id: user_ids_batch).includes(:do_not_disturb_timings).each { |user| yield(user) } - end + users + .pluck(:id) + .each_slice(USER_BATCH_SIZE) do |user_ids_batch| + User.where(id: user_ids_batch).includes(:do_not_disturb_timings).each { |user| yield(user) } + end end def create_pm_notification(user, post, emails_to_skip_send) - create_notification(user, Notification.types[:private_message], post, skip_send_email_to: emails_to_skip_send) + create_notification( + user, + Notification.types[:private_message], + post, + skip_send_email_to: emails_to_skip_send, + ) end def is_replying?(user, reply_to_user, quoted_users) @@ -923,7 +1004,7 @@ class PostAlerter TopicUser.exists?( user_id: user.id, topic_id: topic.id, - notification_level: TopicUser.notification_levels[:watching] + notification_level: TopicUser.notification_levels[:watching], ) end end diff --git a/app/services/post_bookmarkable.rb b/app/services/post_bookmarkable.rb index 09373f6e99a..97086d9ba40 100644 --- a/app/services/post_bookmarkable.rb +++ b/app/services/post_bookmarkable.rb @@ -18,23 +18,26 @@ class PostBookmarkable < BaseBookmarkable def self.list_query(user, guardian) topics = Topic.listable_topics.secured(guardian) pms = Topic.private_messages_for_user(user) - post_bookmarks = user - .bookmarks_of_type("Post") - .joins("INNER JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'") - .joins("LEFT JOIN topics ON topics.id = posts.topic_id") - .joins("LEFT JOIN topic_users ON topic_users.topic_id = topics.id") - .where("topic_users.user_id = ?", user.id) + post_bookmarks = + user + .bookmarks_of_type("Post") + .joins( + "INNER JOIN posts ON posts.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'", + ) + .joins("LEFT JOIN topics ON topics.id = posts.topic_id") + .joins("LEFT JOIN topic_users ON topic_users.topic_id = topics.id") + .where("topic_users.user_id = ?", user.id) guardian.filter_allowed_categories( - post_bookmarks.merge(topics.or(pms)).merge(Post.secured(guardian)) + post_bookmarks.merge(topics.or(pms)).merge(Post.secured(guardian)), ) end def self.search_query(bookmarks, query, ts_query, &bookmarkable_search) bookmarkable_search.call( bookmarks.joins( - "LEFT JOIN post_search_data ON post_search_data.post_id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'" + "LEFT JOIN post_search_data ON post_search_data.post_id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Post'", ), - "#{ts_query} @@ post_search_data.search_data" + "#{ts_query} @@ post_search_data.search_data", ) end @@ -45,8 +48,8 @@ class PostBookmarkable < BaseBookmarkable post_number: bookmark.bookmarkable.post_number, data: { title: bookmark.bookmarkable.topic.title, - bookmarkable_url: bookmark.bookmarkable.url - } + bookmarkable_url: bookmark.bookmarkable.url, + }, ) end @@ -59,14 +62,14 @@ class PostBookmarkable < BaseBookmarkable end def self.bookmark_metadata(bookmark, user) - { topic_bookmarked: Bookmark.for_user_in_topic(user.id, bookmark.bookmarkable.topic_id).exists? } + { + topic_bookmarked: Bookmark.for_user_in_topic(user.id, bookmark.bookmarkable.topic_id).exists?, + } end def self.validate_before_create(guardian, bookmarkable) - if bookmarkable.blank? || - bookmarkable.topic.blank? || - !guardian.can_see_topic?(bookmarkable.topic) || - !guardian.can_see_post?(bookmarkable) + if bookmarkable.blank? || bookmarkable.topic.blank? || + !guardian.can_see_topic?(bookmarkable.topic) || !guardian.can_see_post?(bookmarkable) raise Discourse::InvalidAccess end end diff --git a/app/services/post_owner_changer.rb b/app/services/post_owner_changer.rb index 702e6a8a178..7880aa95f25 100644 --- a/app/services/post_owner_changer.rb +++ b/app/services/post_owner_changer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class PostOwnerChanger - def initialize(params) @post_ids = params[:post_ids] @topic = Topic.with_deleted.find_by(id: params[:topic_id].to_i) @@ -9,7 +8,7 @@ class PostOwnerChanger @acting_user = params[:acting_user] @skip_revision = params[:skip_revision] || false - [:post_ids, :topic, :new_owner, :acting_user].each do |arg| + %i[post_ids topic new_owner acting_user].each do |arg| raise ArgumentError.new(arg) if self.instance_variable_get("@#{arg}").blank? end end @@ -28,20 +27,31 @@ class PostOwnerChanger PostActionDestroyer.destroy(@new_owner, post, :like, skip_delete_check: true) level = post.is_first_post? ? :watching : :tracking - TopicUser.change(@new_owner.id, @topic.id, notification_level: NotificationLevels.topic_levels[level], posted: true) + TopicUser.change( + @new_owner.id, + @topic.id, + notification_level: NotificationLevels.topic_levels[level], + posted: true, + ) - if post == @topic.posts.order("post_number DESC").where("NOT hidden AND posts.deleted_at IS NULL").first + if post == + @topic + .posts + .order("post_number DESC") + .where("NOT hidden AND posts.deleted_at IS NULL") + .first @topic.last_poster = @new_owner end @topic.update_statistics @new_owner.user_stat.update( - first_post_created_at: @new_owner.reload.posts.order('created_at ASC').first&.created_at + first_post_created_at: @new_owner.reload.posts.order("created_at ASC").first&.created_at, ) - Post.where(topic_id: @topic.id, reply_to_post_number: post.post_number) - .update_all(reply_to_user_id: @new_owner.id) + Post.where(topic_id: @topic.id, reply_to_post_number: post.post_number).update_all( + reply_to_user_id: @new_owner.id, + ) @topic.save!(validate: false) end diff --git a/app/services/push_notification_pusher.rb b/app/services/push_notification_pusher.rb index a0b70935448..736f3ea2bf6 100644 --- a/app/services/push_notification_pusher.rb +++ b/app/services/push_notification_pusher.rb @@ -8,30 +8,36 @@ class PushNotificationPusher message = nil I18n.with_locale(user.effective_locale) do notification_icon_name = Notification.types[payload[:notification_type]] - if !File.exist?(File.expand_path("../../app/assets/images/push-notifications/#{notification_icon_name}.png", __dir__)) + if !File.exist?( + File.expand_path( + "../../app/assets/images/push-notifications/#{notification_icon_name}.png", + __dir__, + ), + ) notification_icon_name = "discourse" end - notification_icon = ActionController::Base.helpers.image_url("push-notifications/#{notification_icon_name}.png") + notification_icon = + ActionController::Base.helpers.image_url("push-notifications/#{notification_icon_name}.png") message = { - title: payload[:translated_title] || I18n.t( - "discourse_push_notifications.popup.#{Notification.types[payload[:notification_type]]}", - site_title: SiteSetting.title, - topic: payload[:topic_title], - username: payload[:username] - ), + title: + payload[:translated_title] || + I18n.t( + "discourse_push_notifications.popup.#{Notification.types[payload[:notification_type]]}", + site_title: SiteSetting.title, + topic: payload[:topic_title], + username: payload[:username], + ), body: payload[:excerpt], badge: get_badge, icon: notification_icon, tag: payload[:tag] || "#{Discourse.current_hostname}-#{payload[:topic_id]}", base_url: Discourse.base_url, url: payload[:post_url], - hide_when_active: true + hide_when_active: true, } - subscriptions(user).each do |subscription| - send_notification(user, subscription, message) - end + subscriptions(user).each { |subscription| send_notification(user, subscription, message) } end message @@ -50,21 +56,22 @@ class PushNotificationPusher subscriptions = PushSubscription.where(user: user, data: data) subscriptions_count = subscriptions.count - new_subscription = if subscriptions_count > 1 - subscriptions.destroy_all - PushSubscription.create!(user: user, data: data) - elsif subscriptions_count == 0 - PushSubscription.create!(user: user, data: data) - end + new_subscription = + if subscriptions_count > 1 + subscriptions.destroy_all + PushSubscription.create!(user: user, data: data) + elsif subscriptions_count == 0 + PushSubscription.create!(user: user, data: data) + end if send_confirmation == "true" message = { - title: I18n.t("discourse_push_notifications.popup.confirm_title", - site_title: SiteSetting.title), + title: + I18n.t("discourse_push_notifications.popup.confirm_title", site_title: SiteSetting.title), body: I18n.t("discourse_push_notifications.popup.confirm_body"), icon: ActionController::Base.helpers.image_url("push-notifications/check.png"), badge: get_badge, - tag: "#{Discourse.current_hostname}-subscription" + tag: "#{Discourse.current_hostname}-subscription", } send_notification(user, new_subscription, message) @@ -84,7 +91,7 @@ class PushNotificationPusher end MAX_ERRORS ||= 3 - MIN_ERROR_DURATION ||= 86400 # 1 day + MIN_ERROR_DURATION ||= 86_400 # 1 day def self.handle_generic_error(subscription, error, user, endpoint, message) subscription.error_count += 1 @@ -103,8 +110,8 @@ class PushNotificationPusher env: { user_id: user.id, endpoint: endpoint, - message: message.to_json - } + message: message.to_json, + }, ) end @@ -130,11 +137,11 @@ class PushNotificationPusher subject: Discourse.base_url, public_key: SiteSetting.vapid_public_key, private_key: SiteSetting.vapid_private_key, - expiration: TOKEN_VALID_FOR_SECONDS + expiration: TOKEN_VALID_FOR_SECONDS, }, open_timeout: CONNECTION_TIMEOUT_SECONDS, read_timeout: CONNECTION_TIMEOUT_SECONDS, - ssl_timeout: CONNECTION_TIMEOUT_SECONDS + ssl_timeout: CONNECTION_TIMEOUT_SECONDS, ) if subscription.first_error_at || subscription.error_count != 0 @@ -155,5 +162,4 @@ class PushNotificationPusher private_class_method :send_notification private_class_method :handle_generic_error - end diff --git a/app/services/random_topic_selector.rb b/app/services/random_topic_selector.rb index 8b8d121befe..85f3334864a 100644 --- a/app/services/random_topic_selector.rb +++ b/app/services/random_topic_selector.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class RandomTopicSelector - BACKFILL_SIZE = 3000 BACKFILL_LOW_WATER_MARK = 500 @@ -11,7 +10,7 @@ class RandomTopicSelector options = { per_page: category ? category.num_featured_topics : 3, visible: true, - no_definitions: true + no_definitions: true, } options[:except_topic_ids] = [category.topic_id] if exclude @@ -20,9 +19,7 @@ class RandomTopicSelector options[:category] = category.id # NOTE: at the moment this site setting scopes tightly to a category (excluding subcats) # this is done so we don't populate a junk cache - if SiteSetting.limit_suggested_to_category - options[:no_subcategories] = true - end + options[:no_subcategories] = true if SiteSetting.limit_suggested_to_category # don't leak private categories into the "everything" group options[:guardian] = Guardian.new(Discourse.system_user) @@ -30,12 +27,15 @@ class RandomTopicSelector query = TopicQuery.new(nil, options) - results = query.latest_results.order('RANDOM()') - .where(closed: false, archived: false) - .where("topics.created_at > ?", SiteSetting.suggested_topics_max_days_old.days.ago) - .limit(BACKFILL_SIZE) - .reorder('RANDOM()') - .pluck(:id) + results = + query + .latest_results + .order("RANDOM()") + .where(closed: false, archived: false) + .where("topics.created_at > ?", SiteSetting.suggested_topics_max_days_old.days.ago) + .limit(BACKFILL_SIZE) + .reorder("RANDOM()") + .pluck(:id) key = cache_key(category) @@ -56,10 +56,11 @@ class RandomTopicSelector return results if count < 1 - results = Discourse.redis.multi do |transaction| - transaction.lrange(key, 0, count - 1) - transaction.ltrim(key, count, -1) - end + results = + Discourse.redis.multi do |transaction| + transaction.lrange(key, 0, count - 1) + transaction.ltrim(key, count, -1) + end if !results.is_a?(Array) # Redis is in readonly mode results = Discourse.redis.lrange(key, 0, count - 1) @@ -81,9 +82,7 @@ class RandomTopicSelector end if !backfilled && Discourse.redis.llen(key) < BACKFILL_LOW_WATER_MARK - Scheduler::Defer.later("backfill") do - backfill(category) - end + Scheduler::Defer.later("backfill") { backfill(category) } end results @@ -96,5 +95,4 @@ class RandomTopicSelector def self.clear_cache! Discourse.redis.delete_prefixed(cache_key) end - end diff --git a/app/services/registered_bookmarkable.rb b/app/services/registered_bookmarkable.rb index fc40dee521e..50cd45735a9 100644 --- a/app/services/registered_bookmarkable.rb +++ b/app/services/registered_bookmarkable.rb @@ -65,12 +65,10 @@ class RegisteredBookmarkable return if bookmarks_of_type.empty? if bookmarkable_klass.has_preloads? - ActiveRecord::Associations::Preloader - .new( - records: bookmarks_of_type, - associations: [bookmarkable: bookmarkable_klass.preload_associations] - ) - .call + ActiveRecord::Associations::Preloader.new( + records: bookmarks_of_type, + associations: [bookmarkable: bookmarkable_klass.preload_associations], + ).call end bookmarkable_klass.perform_custom_preload!(bookmarks_of_type, guardian) diff --git a/app/services/search_indexer.rb b/app/services/search_indexer.rb index 33d18b41c9a..a17ac7b34ba 100644 --- a/app/services/search_indexer.rb +++ b/app/services/search_indexer.rb @@ -17,25 +17,17 @@ class SearchIndexer @disabled = false end - def self.update_index(table: , id: , a_weight: nil, b_weight: nil, c_weight: nil, d_weight: nil) - raw_data = { - a: a_weight, - b: b_weight, - c: c_weight, - d: d_weight, - } + def self.update_index(table:, id:, a_weight: nil, b_weight: nil, c_weight: nil, d_weight: nil) + raw_data = { a: a_weight, b: b_weight, c: c_weight, d: d_weight } # The version used in excerpts - search_data = raw_data.transform_values do |data| - Search.prepare_data(data || "", :index) - end + search_data = raw_data.transform_values { |data| Search.prepare_data(data || "", :index) } # The version used to build the index - indexed_data = search_data.transform_values do |data| - data.gsub(/\S+/) { |word| - word[0...SiteSetting.search_max_indexed_word_length] - } - end + indexed_data = + search_data.transform_values do |data| + data.gsub(/\S+/) { |word| word[0...SiteSetting.search_max_indexed_word_length] } + end table_name = "#{table}_search_data" foreign_key = "#{table}_id" @@ -53,30 +45,32 @@ class SearchIndexer tsvector = DB.query_single("SELECT #{ranked_index}", indexed_data)[0] additional_lexemes = [] - tsvector.scan(/'(([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+)'\:([\w+,]+)/).reduce(additional_lexemes) do |array, (lexeme, _, positions)| - count = 0 + tsvector + .scan(/'(([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+)'\:([\w+,]+)/) + .reduce(additional_lexemes) do |array, (lexeme, _, positions)| + count = 0 - if lexeme !~ /^(\d+\.)?(\d+\.)*(\*|\d+)$/ - loop do - count += 1 - break if count >= 10 # Safeguard here to prevent infinite loop when a term has many dots - term, _, remaining = lexeme.partition(".") - break if remaining.blank? - array << "'#{remaining}':#{positions}" - lexeme = remaining + if lexeme !~ /^(\d+\.)?(\d+\.)*(\*|\d+)$/ + loop do + count += 1 + break if count >= 10 # Safeguard here to prevent infinite loop when a term has many dots + term, _, remaining = lexeme.partition(".") + break if remaining.blank? + array << "'#{remaining}':#{positions}" + lexeme = remaining + end end + + array end - array - end - - tsvector = "#{tsvector} #{additional_lexemes.join(' ')}" + tsvector = "#{tsvector} #{additional_lexemes.join(" ")}" indexed_data = if table.to_s == "post" clean_post_raw_data!(search_data[:d]) else - search_data.values.select { |d| d.length > 0 }.join(' ') + search_data.values.select { |d| d.length > 0 }.join(" ") end params = { @@ -99,7 +93,9 @@ class SearchIndexer Discourse.warn_exception( e, message: "Unexpected error while indexing #{table} for search", - env: { id: id } + env: { + id: id, + }, ) end end @@ -108,16 +104,23 @@ class SearchIndexer # a bit inconsistent that we use title as A and body as B when in # the post index body is D update_index( - table: 'topic', + table: "topic", id: topic_id, a_weight: title, - b_weight: HtmlScrubber.scrub(cooked)[0...Topic::MAX_SIMILAR_BODY_LENGTH] + b_weight: HtmlScrubber.scrub(cooked)[0...Topic::MAX_SIMILAR_BODY_LENGTH], ) end - def self.update_posts_index(post_id:, topic_title:, category_name:, topic_tags:, cooked:, private_message:) + def self.update_posts_index( + post_id:, + topic_title:, + category_name:, + topic_tags:, + cooked:, + private_message: + ) update_index( - table: 'post', + table: "post", id: post_id, a_weight: topic_title, b_weight: category_name, @@ -126,36 +129,26 @@ class SearchIndexer # the original string. Since there is no way to estimate the length of # the expected tsvector, we limit the input to ~50% of the maximum # length of a tsvector (1_048_576 bytes). - d_weight: HtmlScrubber.scrub(cooked)[0..600_000] - ) do |params| - params["private_message"] = private_message - end + d_weight: HtmlScrubber.scrub(cooked)[0..600_000], + ) { |params| params["private_message"] = private_message } end def self.update_users_index(user_id, username, name, custom_fields) update_index( - table: 'user', + table: "user", id: user_id, a_weight: username, b_weight: name, - c_weight: custom_fields + c_weight: custom_fields, ) end def self.update_categories_index(category_id, name) - update_index( - table: 'category', - id: category_id, - a_weight: name - ) + update_index(table: "category", id: category_id, a_weight: name) end def self.update_tags_index(tag_id, name) - update_index( - table: 'tag', - id: tag_id, - a_weight: name.downcase - ) + update_index(table: "tag", id: tag_id, a_weight: name.downcase) end def self.queue_category_posts_reindex(category_id) @@ -213,17 +206,13 @@ class SearchIndexer tags = topic.tags.select(:id, :name).to_a if tags.present? - tag_names = (tags.map(&:name) + Tag.where(target_tag_id: tags.map(&:id)).pluck(:name)).join(' ') + tag_names = + (tags.map(&:name) + Tag.where(target_tag_id: tags.map(&:id)).pluck(:name)).join(" ") end end if Post === obj && obj.raw.present? && - ( - force || - obj.saved_change_to_cooked? || - obj.saved_change_to_topic_id? - ) - + (force || obj.saved_change_to_cooked? || obj.saved_change_to_topic_id?) if topic SearchIndexer.update_posts_index( post_id: obj.id, @@ -231,7 +220,7 @@ class SearchIndexer category_name: category_name, topic_tags: tag_names, cooked: obj.cooked, - private_message: topic.private_message? + private_message: topic.private_message?, ) SearchIndexer.update_topics_index(topic.id, topic.title, obj.cooked) if obj.is_first_post? @@ -239,10 +228,12 @@ class SearchIndexer end if User === obj && (obj.saved_change_to_username? || obj.saved_change_to_name? || force) - SearchIndexer.update_users_index(obj.id, - obj.username_lower || '', - obj.name ? obj.name.downcase : '', - obj.user_custom_fields.searchable.map(&:value).join(" ")) + SearchIndexer.update_users_index( + obj.id, + obj.username_lower || "", + obj.name ? obj.name.downcase : "", + obj.user_custom_fields.searchable.map(&:value).join(" "), + ) end if Topic === obj && (obj.saved_change_to_title? || force) @@ -254,7 +245,7 @@ class SearchIndexer category_name: category_name, topic_tags: tag_names, cooked: post.cooked, - private_message: obj.private_message? + private_message: obj.private_message?, ) SearchIndexer.update_topics_index(obj.id, obj.title, post.cooked) @@ -293,7 +284,6 @@ class SearchIndexer private_class_method :clean_post_raw_data! class HtmlScrubber < Nokogiri::XML::SAX::Document - attr_reader :scrubbed def initialize @@ -304,63 +294,55 @@ class SearchIndexer return +"" if html.blank? begin - document = Nokogiri::HTML5("
    #{html}
    ", nil, Encoding::UTF_8.to_s) + document = Nokogiri.HTML5("
    #{html}
    ", nil, Encoding::UTF_8.to_s) rescue ArgumentError return +"" end - nodes = document.css( - "div.#{CookedPostProcessor::LIGHTBOX_WRAPPER_CSS_CLASS}" - ) + nodes = document.css("div.#{CookedPostProcessor::LIGHTBOX_WRAPPER_CSS_CLASS}") if nodes.present? nodes.each do |node| node.traverse do |child_node| next if child_node == node - if %w{a img}.exclude?(child_node.name) + if %w[a img].exclude?(child_node.name) child_node.remove elsif child_node.name == "a" - ATTRIBUTES.each do |attribute| - child_node.remove_attribute(attribute) - end + ATTRIBUTES.each { |attribute| child_node.remove_attribute(attribute) } end end end end - document.css("img.emoji").each do |node| - node.remove_attribute("alt") - end + document.css("img.emoji").each { |node| node.remove_attribute("alt") } - document.css("a[href]").each do |node| - if node["href"] == node.text || MENTION_CLASSES.include?(node["class"]) - node.remove_attribute("href") - end + document + .css("a[href]") + .each do |node| + if node["href"] == node.text || MENTION_CLASSES.include?(node["class"]) + node.remove_attribute("href") + end - if node["class"] == "anchor" && node["href"].starts_with?("#") - node.remove_attribute("href") + if node["class"] == "anchor" && node["href"].starts_with?("#") + node.remove_attribute("href") + end end - end html_scrubber = new Nokogiri::HTML::SAX::Parser.new(html_scrubber).parse(document.to_html) html_scrubber.scrubbed.squish end - MENTION_CLASSES ||= %w{mention mention-group} - ATTRIBUTES ||= %w{alt title href data-youtube-title} + MENTION_CLASSES ||= %w[mention mention-group] + ATTRIBUTES ||= %w[alt title href data-youtube-title] def start_element(_name, attributes = []) attributes = Hash[*attributes.flatten] ATTRIBUTES.each do |attribute_name| if attributes[attribute_name].present? && - !( - attribute_name == "href" && - UrlHelper.is_local(attributes[attribute_name]) - ) - + !(attribute_name == "href" && UrlHelper.is_local(attributes[attribute_name])) characters(attributes[attribute_name]) end end diff --git a/app/services/sidebar_section_links_updater.rb b/app/services/sidebar_section_links_updater.rb index 98d0d768576..b3faacb4f08 100644 --- a/app/services/sidebar_section_links_updater.rb +++ b/app/services/sidebar_section_links_updater.rb @@ -3,23 +3,21 @@ class SidebarSectionLinksUpdater def self.update_category_section_links(user, category_ids:) if category_ids.blank? - delete_section_links(user: user, linkable_type: 'Category') + delete_section_links(user: user, linkable_type: "Category") else category_ids = Category.secured(Guardian.new(user)).where(id: category_ids).pluck(:id) - update_section_links(user: user, linkable_type: 'Category', new_linkable_ids: category_ids) + update_section_links(user: user, linkable_type: "Category", new_linkable_ids: category_ids) end end def self.update_tag_section_links(user, tag_names:) if tag_names.blank? - delete_section_links(user: user, linkable_type: 'Tag') + delete_section_links(user: user, linkable_type: "Tag") else - tag_ids = DiscourseTagging - .filter_visible(Tag, Guardian.new(user)) - .where(name: tag_names) - .pluck(:id) + tag_ids = + DiscourseTagging.filter_visible(Tag, Guardian.new(user)).where(name: tag_names).pluck(:id) - update_section_links(user: user, linkable_type: 'Tag', new_linkable_ids: tag_ids) + update_section_links(user: user, linkable_type: "Tag", new_linkable_ids: tag_ids) end end @@ -30,20 +28,24 @@ class SidebarSectionLinksUpdater def self.update_section_links(user:, linkable_type:, new_linkable_ids:) SidebarSectionLink.transaction do - existing_linkable_ids = SidebarSectionLink.where(user: user, linkable_type: linkable_type).pluck(:linkable_id) + existing_linkable_ids = + SidebarSectionLink.where(user: user, linkable_type: linkable_type).pluck(:linkable_id) to_delete = existing_linkable_ids - new_linkable_ids to_insert = new_linkable_ids - existing_linkable_ids - to_insert_attributes = to_insert.map do |linkable_id| - { - linkable_type: linkable_type, - linkable_id: linkable_id, - user_id: user.id - } - end + to_insert_attributes = + to_insert.map do |linkable_id| + { linkable_type: linkable_type, linkable_id: linkable_id, user_id: user.id } + end - SidebarSectionLink.where(user: user, linkable_type: linkable_type, linkable_id: to_delete).delete_all if to_delete.present? + if to_delete.present? + SidebarSectionLink.where( + user: user, + linkable_type: linkable_type, + linkable_id: to_delete, + ).delete_all + end SidebarSectionLink.insert_all(to_insert_attributes) if to_insert_attributes.present? end end diff --git a/app/services/sidebar_site_settings_backfiller.rb b/app/services/sidebar_site_settings_backfiller.rb index 884ff765468..7fe13f8162b 100644 --- a/app/services/sidebar_site_settings_backfiller.rb +++ b/app/services/sidebar_site_settings_backfiller.rb @@ -14,21 +14,17 @@ class SidebarSiteSettingsBackfiller @linkable_klass, previous_ids, new_ids = case setting_name when "default_sidebar_categories" - [ - Category, - previous_value.split("|"), - new_value.split("|") - ] + [Category, previous_value.split("|"), new_value.split("|")] when "default_sidebar_tags" klass = Tag [ klass, klass.where(name: previous_value.split("|")).pluck(:id), - klass.where(name: new_value.split("|")).pluck(:id) + klass.where(name: new_value.split("|")).pluck(:id), ] else - raise 'Invalid setting_name' + raise "Invalid setting_name" end @added_ids = new_ids - previous_ids @@ -37,34 +33,43 @@ class SidebarSiteSettingsBackfiller def backfill! DistributedMutex.synchronize("backfill_sidebar_site_settings_#{@setting_name}") do - SidebarSectionLink.where(linkable_type: @linkable_klass.to_s, linkable_id: @removed_ids).delete_all + SidebarSectionLink.where( + linkable_type: @linkable_klass.to_s, + linkable_id: @removed_ids, + ).delete_all - User.real.where(staged: false).select(:id).find_in_batches do |users| - rows = [] + User + .real + .where(staged: false) + .select(:id) + .find_in_batches do |users| + rows = [] - users.each do |user| - @added_ids.each do |linkable_id| - rows << { user_id: user[:id], linkable_type: @linkable_klass.to_s, linkable_id: linkable_id } + users.each do |user| + @added_ids.each do |linkable_id| + rows << { + user_id: user[:id], + linkable_type: @linkable_klass.to_s, + linkable_id: linkable_id, + } + end end - end - SidebarSectionLink.insert_all(rows) if rows.present? - end + SidebarSectionLink.insert_all(rows) if rows.present? + end end end def number_of_users_to_backfill select_statements = [] - if @removed_ids.present? - select_statements.push(<<~SQL) + select_statements.push(<<~SQL) if @removed_ids.present? SELECT sidebar_section_links.user_id FROM sidebar_section_links WHERE sidebar_section_links.linkable_type = '#{@linkable_klass.to_s}' AND sidebar_section_links.linkable_id IN (#{@removed_ids.join(",")}) SQL - end if @added_ids.present? # Returns the ids of users that will receive the new additions by excluding the users that already have the additions diff --git a/app/services/site_settings_task.rb b/app/services/site_settings_task.rb index 356d8eccf14..35d94378fba 100644 --- a/app/services/site_settings_task.rb +++ b/app/services/site_settings_task.rb @@ -16,7 +16,7 @@ class SiteSettingsTask counts = { updated: 0, not_found: 0, errors: 0 } log = [] - site_settings = YAML::safe_load(yml) + site_settings = YAML.safe_load(yml) site_settings.each do |site_setting| key = site_setting[0] val = site_setting[1] diff --git a/app/services/spam_rule/auto_silence.rb b/app/services/spam_rule/auto_silence.rb index 81acb86cd2e..76f481b9634 100644 --- a/app/services/spam_rule/auto_silence.rb +++ b/app/services/spam_rule/auto_silence.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class SpamRule::AutoSilence - attr_reader :group_message def initialize(user, post = nil) @@ -10,9 +9,7 @@ class SpamRule::AutoSilence end def perform - I18n.with_locale(SiteSetting.default_locale) do - silence_user if should_autosilence? - end + I18n.with_locale(SiteSetting.default_locale) { silence_user if should_autosilence? } end def self.prevent_posting?(user) @@ -36,7 +33,7 @@ class SpamRule::AutoSilence user_id: @user.id, spam_type: PostActionType.types[:spam], pending: ReviewableScore.statuses[:pending], - agreed: ReviewableScore.statuses[:agreed] + agreed: ReviewableScore.statuses[:agreed], } result = DB.query(<<~SQL, params) @@ -53,23 +50,30 @@ class SpamRule::AutoSilence end def flagged_post_ids - Post.where(user_id: @user.id) - .where('spam_count > 0 OR off_topic_count > 0 OR inappropriate_count > 0') + Post + .where(user_id: @user.id) + .where("spam_count > 0 OR off_topic_count > 0 OR inappropriate_count > 0") .pluck(:id) end def silence_user Post.transaction do - - silencer = UserSilencer.new( - @user, - Discourse.system_user, - message: :too_many_spam_flags, - post_id: @post&.id - ) + silencer = + UserSilencer.new( + @user, + Discourse.system_user, + message: :too_many_spam_flags, + post_id: @post&.id, + ) if silencer.silence && SiteSetting.notify_mods_when_user_silenced - @group_message = GroupMessage.create(Group[:moderators].name, :user_automatically_silenced, user: @user, limit_once_per: false) + @group_message = + GroupMessage.create( + Group[:moderators].name, + :user_automatically_silenced, + user: @user, + limit_once_per: false, + ) end end end diff --git a/app/services/spam_rule/flag_sockpuppets.rb b/app/services/spam_rule/flag_sockpuppets.rb index c51d67f4022..2a8cd2772e6 100644 --- a/app/services/spam_rule/flag_sockpuppets.rb +++ b/app/services/spam_rule/flag_sockpuppets.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class SpamRule::FlagSockpuppets - def initialize(post) @post = post end @@ -21,23 +20,20 @@ class SpamRule::FlagSockpuppets return false if @post.try(:post_number) == 1 return false if first_post.user.nil? - !first_post.user.staff? && - !@post.user.staff? && - !first_post.user.staged? && - !@post.user.staged? && - @post.user != first_post.user && - @post.user.ip_address == first_post.user.ip_address && - @post.user.new_user? && - !ScreenedIpAddress.is_allowed?(@post.user.ip_address) + !first_post.user.staff? && !@post.user.staff? && !first_post.user.staged? && + !@post.user.staged? && @post.user != first_post.user && + @post.user.ip_address == first_post.user.ip_address && @post.user.new_user? && + !ScreenedIpAddress.is_allowed?(@post.user.ip_address) end def flag_sockpuppet_users - message = I18n.t( - 'flag_reason.sockpuppet', - ip_address: @post.user.ip_address, - base_path: Discourse.base_path, - locale: SiteSetting.default_locale - ) + message = + I18n.t( + "flag_reason.sockpuppet", + ip_address: @post.user.ip_address, + base_path: Discourse.base_path, + locale: SiteSetting.default_locale, + ) flag_post(@post, message) @@ -55,5 +51,4 @@ class SpamRule::FlagSockpuppets def first_post @first_post ||= @post.topic.posts.by_post_number.first end - end diff --git a/app/services/staff_action_logger.rb b/app/services/staff_action_logger.rb index a4203452bb7..70c937e5613 100644 --- a/app/services/staff_action_logger.rb +++ b/app/services/staff_action_logger.rb @@ -2,9 +2,8 @@ # Responsible for logging the actions of admins and moderators. class StaffActionLogger - def self.base_attrs - [:topic_id, :post_id, :context, :subject, :ip_address, :previous_value, :new_value] + %i[topic_id post_id context subject ip_address previous_value new_value] end def initialize(admin) @@ -12,20 +11,22 @@ class StaffActionLogger raise Discourse::InvalidParameters.new(:admin) unless @admin && @admin.is_a?(User) end - USER_FIELDS ||= %i{id username name created_at trust_level last_seen_at last_emailed_at} + USER_FIELDS ||= %i[id username name created_at trust_level last_seen_at last_emailed_at] def log_user_deletion(deleted_user, opts = {}) - raise Discourse::InvalidParameters.new(:deleted_user) unless deleted_user && deleted_user.is_a?(User) + unless deleted_user && deleted_user.is_a?(User) + raise Discourse::InvalidParameters.new(:deleted_user) + end - details = USER_FIELDS.map do |x| - "#{x}: #{deleted_user.public_send(x)}" - end.join("\n") + details = USER_FIELDS.map { |x| "#{x}: #{deleted_user.public_send(x)}" }.join("\n") - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:delete_user], - ip_address: deleted_user.ip_address.to_s, - details: details - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:delete_user], + ip_address: deleted_user.ip_address.to_s, + details: details, + ), + ) end def log_custom(custom_type, details = nil) @@ -46,13 +47,15 @@ class StaffActionLogger end def log_post_deletion(deleted_post, opts = {}) - raise Discourse::InvalidParameters.new(:deleted_post) unless deleted_post && deleted_post.is_a?(Post) + unless deleted_post && deleted_post.is_a?(Post) + raise Discourse::InvalidParameters.new(:deleted_post) + end topic = deleted_post.topic || Topic.with_deleted.find_by(id: deleted_post.topic_id) - username = deleted_post.user.try(:username) || I18n.t('staff_action_logs.unknown') - name = deleted_post.user.try(:name) || I18n.t('staff_action_logs.unknown') - topic_title = topic.try(:title) || I18n.t('staff_action_logs.not_found') + username = deleted_post.user.try(:username) || I18n.t("staff_action_logs.unknown") + name = deleted_post.user.try(:name) || I18n.t("staff_action_logs.unknown") + topic_title = topic.try(:title) || I18n.t("staff_action_logs.not_found") details = [ "id: #{deleted_post.id}", @@ -60,14 +63,16 @@ class StaffActionLogger "user: #{username} (#{name})", "topic: #{topic_title}", "post_number: #{deleted_post.post_number}", - "raw: #{deleted_post.raw}" + "raw: #{deleted_post.raw}", ] - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:delete_post], - post_id: deleted_post.id, - details: details.join("\n") - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:delete_post], + post_id: deleted_post.id, + details: details.join("\n"), + ), + ) end def log_topic_delete_recover(topic, action = "delete_topic", opts = {}) @@ -79,99 +84,120 @@ class StaffActionLogger "id: #{topic.id}", "created_at: #{topic.created_at}", "user: #{user}", - "title: #{topic.title}" + "title: #{topic.title}", ] if first_post = topic.ordered_posts.with_deleted.first details << "raw: #{first_post.raw}" end - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[action.to_sym], - topic_id: topic.id, - details: details.join("\n") - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[action.to_sym], + topic_id: topic.id, + details: details.join("\n"), + ), + ) end def log_trust_level_change(user, old_trust_level, new_trust_level, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user && user.is_a?(User) - raise Discourse::InvalidParameters.new(:old_trust_level) unless TrustLevel.valid? old_trust_level - raise Discourse::InvalidParameters.new(:new_trust_level) unless TrustLevel.valid? new_trust_level - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_trust_level], - target_user_id: user.id, - previous_value: old_trust_level, - new_value: new_trust_level, - )) + unless TrustLevel.valid? old_trust_level + raise Discourse::InvalidParameters.new(:old_trust_level) + end + unless TrustLevel.valid? new_trust_level + raise Discourse::InvalidParameters.new(:new_trust_level) + end + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_trust_level], + target_user_id: user.id, + previous_value: old_trust_level, + new_value: new_trust_level, + ), + ) end def log_lock_trust_level(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user && user.is_a?(User) - action = UserHistory.actions[user.manual_locked_trust_level.nil? ? :unlock_trust_level : :lock_trust_level] - UserHistory.create!(params(opts).merge( - action: action, - target_user_id: user.id - )) + action = + UserHistory.actions[ + user.manual_locked_trust_level.nil? ? :unlock_trust_level : :lock_trust_level + ] + UserHistory.create!(params(opts).merge(action: action, target_user_id: user.id)) end def log_topic_published(topic, opts = {}) raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:topic_published], - topic_id: topic.id) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:topic_published], topic_id: topic.id), ) end def log_topic_timestamps_changed(topic, new_timestamp, previous_timestamp, opts = {}) raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:topic_timestamps_changed], - topic_id: topic.id, - new_value: new_timestamp, - previous_value: previous_timestamp) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:topic_timestamps_changed], + topic_id: topic.id, + new_value: new_timestamp, + previous_value: previous_timestamp, + ), ) end def log_post_lock(post, opts = {}) raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[opts[:locked] ? :post_locked : :post_unlocked], - post_id: post.id) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[opts[:locked] ? :post_locked : :post_unlocked], + post_id: post.id, + ), ) end def log_post_edit(post, opts = {}) raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:post_edit], - post_id: post.id, - details: "#{opts[:old_raw]}\n\n---\n\n#{post.raw}" - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:post_edit], + post_id: post.id, + details: "#{opts[:old_raw]}\n\n---\n\n#{post.raw}", + ), + ) end def log_topic_closed(topic, opts = {}) raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[opts[:closed] ? :topic_closed : :topic_opened], - topic_id: topic.id - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[opts[:closed] ? :topic_closed : :topic_opened], + topic_id: topic.id, + ), + ) end def log_topic_archived(topic, opts = {}) raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[opts[:archived] ? :topic_archived : :topic_unarchived], - topic_id: topic.id - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[opts[:archived] ? :topic_archived : :topic_unarchived], + topic_id: topic.id, + ), + ) end def log_post_staff_note(post, opts = {}) raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post) - args = params(opts).merge( - action: UserHistory.actions[opts[:new_value].present? ? :post_staff_note_create : :post_staff_note_destroy], - post_id: post.id - ) + args = + params(opts).merge( + action: + UserHistory.actions[ + opts[:new_value].present? ? :post_staff_note_create : :post_staff_note_destroy + ], + post_id: post.id, + ) args[:new_value] = opts[:new_value] if opts[:new_value].present? args[:previous_value] = opts[:old_value] if opts[:old_value].present? @@ -179,13 +205,17 @@ class StaffActionLogger end def log_site_setting_change(setting_name, previous_value, new_value, opts = {}) - raise Discourse::InvalidParameters.new(:setting_name) unless setting_name.present? && SiteSetting.respond_to?(setting_name) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_site_setting], - subject: setting_name, - previous_value: previous_value&.to_s, - new_value: new_value&.to_s - )) + unless setting_name.present? && SiteSetting.respond_to?(setting_name) + raise Discourse::InvalidParameters.new(:setting_name) + end + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_site_setting], + subject: setting_name, + previous_value: previous_value&.to_s, + new_value: new_value&.to_s, + ), + ) end def theme_json(theme) @@ -193,7 +223,7 @@ class StaffActionLogger end def strip_duplicates(old, cur) - return [old, cur] unless old && cur + return old, cur unless old && cur old = JSON.parse(old) cur = JSON.parse(cur) @@ -217,79 +247,97 @@ class StaffActionLogger old_json, new_json = strip_duplicates(old_json, new_json) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_theme], - subject: new_theme.name, - previous_value: old_json, - new_value: new_json - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_theme], + subject: new_theme.name, + previous_value: old_json, + new_value: new_json, + ), + ) end def log_theme_destroy(theme, opts = {}) raise Discourse::InvalidParameters.new(:theme) unless theme - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:delete_theme], - subject: theme.name, - previous_value: theme_json(theme) - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:delete_theme], + subject: theme.name, + previous_value: theme_json(theme), + ), + ) end def log_theme_component_disabled(component) - UserHistory.create!(params.merge( - action: UserHistory.actions[:disable_theme_component], - subject: component.name, - context: component.id - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:disable_theme_component], + subject: component.name, + context: component.id, + ), + ) end def log_theme_component_enabled(component) - UserHistory.create!(params.merge( - action: UserHistory.actions[:enable_theme_component], - subject: component.name, - context: component.id - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:enable_theme_component], + subject: component.name, + context: component.id, + ), + ) end def log_theme_setting_change(setting_name, previous_value, new_value, theme, opts = {}) raise Discourse::InvalidParameters.new(:theme) unless theme - raise Discourse::InvalidParameters.new(:setting_name) unless theme.cached_settings.has_key?(setting_name) + unless theme.cached_settings.has_key?(setting_name) + raise Discourse::InvalidParameters.new(:setting_name) + end - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_theme_setting], - subject: "#{theme.name}: #{setting_name.to_s}", - previous_value: previous_value, - new_value: new_value - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_theme_setting], + subject: "#{theme.name}: #{setting_name.to_s}", + previous_value: previous_value, + new_value: new_value, + ), + ) end def log_site_text_change(subject, new_text = nil, old_text = nil, opts = {}) raise Discourse::InvalidParameters.new(:subject) unless subject.present? - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_site_text], - subject: subject, - previous_value: old_text, - new_value: new_text - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_site_text], + subject: subject, + previous_value: old_text, + new_value: new_text, + ), + ) end def log_username_change(user, old_username, new_username, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_username], - target_user_id: user.id, - previous_value: old_username, - new_value: new_username - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_username], + target_user_id: user.id, + previous_value: old_username, + new_value: new_username, + ), + ) end def log_name_change(user_id, old_name, new_name, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user_id - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_name], - target_user_id: user_id, - previous_value: old_name, - new_value: new_name - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_name], + target_user_id: user_id, + previous_value: old_name, + new_value: new_name, + ), + ) end def log_user_suspend(user, reason, opts = {}) @@ -297,161 +345,201 @@ class StaffActionLogger details = StaffMessageFormat.new(:suspend, reason, opts[:message]).format - args = params(opts).merge( - action: UserHistory.actions[:suspend_user], - target_user_id: user.id, - details: details - ) + args = + params(opts).merge( + action: UserHistory.actions[:suspend_user], + target_user_id: user.id, + details: details, + ) args[:post_id] = opts[:post_id] if opts[:post_id] UserHistory.create!(args) end def log_user_unsuspend(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:unsuspend_user], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:unsuspend_user], target_user_id: user.id), + ) end def log_user_merge(user, source_username, source_email, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:merge_user], - target_user_id: user.id, - context: I18n.t("staff_action_logs.user_merged", username: source_username), - email: source_email - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:merge_user], + target_user_id: user.id, + context: I18n.t("staff_action_logs.user_merged", username: source_username), + email: source_email, + ), + ) end - BADGE_FIELDS ||= %i{id name description long_description icon image_upload_id badge_type_id - badge_grouping_id query allow_title multiple_grant listable target_posts - enabled auto_revoke show_posts system} + BADGE_FIELDS ||= %i[ + id + name + description + long_description + icon + image_upload_id + badge_type_id + badge_grouping_id + query + allow_title + multiple_grant + listable + target_posts + enabled + auto_revoke + show_posts + system + ] def log_badge_creation(badge) raise Discourse::InvalidParameters.new(:badge) unless badge - details = BADGE_FIELDS.map do |f| - [f, badge.public_send(f)] - end.select { |f, v| v.present? }.map { |f, v| "#{f}: #{v}" } + details = + BADGE_FIELDS + .map { |f| [f, badge.public_send(f)] } + .select { |f, v| v.present? } + .map { |f, v| "#{f}: #{v}" } - UserHistory.create!(params.merge( - action: UserHistory.actions[:create_badge], - details: details.join("\n") - )) + UserHistory.create!( + params.merge(action: UserHistory.actions[:create_badge], details: details.join("\n")), + ) end def log_badge_change(badge) raise Discourse::InvalidParameters.new(:badge) unless badge details = ["id: #{badge.id}"] - badge.previous_changes.each { |f, values| details << "#{f}: #{values[1]}" if BADGE_FIELDS.include?(f.to_sym) } - UserHistory.create!(params.merge( - action: UserHistory.actions[:change_badge], - details: details.join("\n") - )) + badge.previous_changes.each do |f, values| + details << "#{f}: #{values[1]}" if BADGE_FIELDS.include?(f.to_sym) + end + UserHistory.create!( + params.merge(action: UserHistory.actions[:change_badge], details: details.join("\n")), + ) end def log_badge_deletion(badge) raise Discourse::InvalidParameters.new(:badge) unless badge - details = BADGE_FIELDS.map do |f| - [f, badge.public_send(f)] - end.select { |f, v| v.present? }.map { |f, v| "#{f}: #{v}" } + details = + BADGE_FIELDS + .map { |f| [f, badge.public_send(f)] } + .select { |f, v| v.present? } + .map { |f, v| "#{f}: #{v}" } - UserHistory.create!(params.merge( - action: UserHistory.actions[:delete_badge], - details: details.join("\n") - )) + UserHistory.create!( + params.merge(action: UserHistory.actions[:delete_badge], details: details.join("\n")), + ) end def log_badge_grant(user_badge, opts = {}) raise Discourse::InvalidParameters.new(:user_badge) unless user_badge - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:grant_badge], - target_user_id: user_badge.user_id, - details: user_badge.badge.name - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:grant_badge], + target_user_id: user_badge.user_id, + details: user_badge.badge.name, + ), + ) end def log_badge_revoke(user_badge, opts = {}) raise Discourse::InvalidParameters.new(:user_badge) unless user_badge - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:revoke_badge], - target_user_id: user_badge.user_id, - details: user_badge.badge.name - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:revoke_badge], + target_user_id: user_badge.user_id, + details: user_badge.badge.name, + ), + ) end def log_title_revoke(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:revoke_title], - target_user_id: user.id, - details: opts[:revoke_reason], - previous_value: opts[:previous_value] - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:revoke_title], + target_user_id: user.id, + details: opts[:revoke_reason], + previous_value: opts[:previous_value], + ), + ) end def log_title_change(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:change_title], - target_user_id: user.id, - details: opts[:details], - new_value: opts[:new_value], - previous_value: opts[:previous_value] - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:change_title], + target_user_id: user.id, + details: opts[:details], + new_value: opts[:new_value], + previous_value: opts[:previous_value], + ), + ) end def log_change_upload_secure_status(opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:override_upload_secure_status], - details: [ - "upload_id: #{opts[:upload_id]}", - "reason: #{I18n.t("uploads.marked_insecure_from_theme_component_reason")}" - ].join("\n"), - new_value: opts[:new_value] - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:override_upload_secure_status], + details: [ + "upload_id: #{opts[:upload_id]}", + "reason: #{I18n.t("uploads.marked_insecure_from_theme_component_reason")}", + ].join("\n"), + new_value: opts[:new_value], + ), + ) end def log_check_email(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:check_email], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:check_email], target_user_id: user.id), + ) end def log_show_emails(users, opts = {}) return if users.blank? - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:check_email], - details: users.map { |u| "[#{u.id}] #{u.username}" }.join("\n") - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:check_email], + details: users.map { |u| "[#{u.id}] #{u.username}" }.join("\n"), + ), + ) end def log_impersonate(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:impersonate], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:impersonate], target_user_id: user.id), + ) end def log_roll_up(subnet, ips, opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:roll_up], - details: "#{subnet} from #{ips.join(", ")}" - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:roll_up], + details: "#{subnet} from #{ips.join(", ")}", + ), + ) end - def log_category_settings_change(category, category_params, old_permissions: nil, old_custom_fields: nil) + def log_category_settings_change( + category, + category_params, + old_permissions: nil, + old_custom_fields: nil + ) validate_category(category) changed_attributes = category.previous_changes.slice(*category_params.keys) if !old_permissions.empty? && (old_permissions != category_params[:permissions]) - changed_attributes.merge!(permissions: [old_permissions.to_json, category_params[:permissions].to_json]) + changed_attributes.merge!( + permissions: [old_permissions.to_json, category_params[:permissions].to_json], + ) end if old_custom_fields && category_params[:custom_fields] @@ -462,14 +550,16 @@ class StaffActionLogger end changed_attributes.each do |key, value| - UserHistory.create!(params.merge( - action: UserHistory.actions[:change_category_settings], - category_id: category.id, - context: category.url, - subject: key, - previous_value: value[0], - new_value: value[1] - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:change_category_settings], + category_id: category.id, + context: category.url, + subject: key, + previous_value: value[0], + new_value: value[1], + ), + ) end end @@ -479,45 +569,47 @@ class StaffActionLogger details = [ "created_at: #{category.created_at}", "name: #{category.name}", - "permissions: #{category.permissions_params}" + "permissions: #{category.permissions_params}", ] if parent_category = category.parent_category details << "parent_category: #{parent_category.name}" end - UserHistory.create!(params.merge( - action: UserHistory.actions[:delete_category], - category_id: category.id, - details: details.join("\n"), - context: category.url - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:delete_category], + category_id: category.id, + details: details.join("\n"), + context: category.url, + ), + ) end def log_category_creation(category) validate_category(category) - details = [ - "created_at: #{category.created_at}", - "name: #{category.name}" - ] + details = ["created_at: #{category.created_at}", "name: #{category.name}"] - UserHistory.create!(params.merge( - action: UserHistory.actions[:create_category], - details: details.join("\n"), - category_id: category.id, - context: category.url - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:create_category], + details: details.join("\n"), + category_id: category.id, + context: category.url, + ), + ) end def log_silence_user(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - create_args = params(opts).merge( - action: UserHistory.actions[:silence_user], - target_user_id: user.id, - details: opts[:details] - ) + create_args = + params(opts).merge( + action: UserHistory.actions[:silence_user], + target_user_id: user.id, + details: opts[:details], + ) create_args[:post_id] = opts[:post_id] if opts[:post_id] UserHistory.create!(create_args) @@ -525,224 +617,235 @@ class StaffActionLogger def log_unsilence_user(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:unsilence_user], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:unsilence_user], target_user_id: user.id), + ) end def log_disable_second_factor_auth(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:disabled_second_factor], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:disabled_second_factor], + target_user_id: user.id, + ), + ) end def log_grant_admin(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:grant_admin], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:grant_admin], target_user_id: user.id), + ) end def log_revoke_admin(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:revoke_admin], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:revoke_admin], target_user_id: user.id), + ) end def log_grant_moderation(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:grant_moderation], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:grant_moderation], target_user_id: user.id), + ) end def log_revoke_moderation(user, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:revoke_moderation], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:revoke_moderation], target_user_id: user.id), + ) end def log_backup_create(opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:backup_create], - ip_address: @admin.ip_address.to_s - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:backup_create], + ip_address: @admin.ip_address.to_s, + ), + ) end def log_entity_export(entity, opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:entity_export], - ip_address: @admin.ip_address.to_s, - subject: entity - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:entity_export], + ip_address: @admin.ip_address.to_s, + subject: entity, + ), + ) end def log_backup_download(backup, opts = {}) raise Discourse::InvalidParameters.new(:backup) unless backup - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:backup_download], - ip_address: @admin.ip_address.to_s, - details: backup.filename - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:backup_download], + ip_address: @admin.ip_address.to_s, + details: backup.filename, + ), + ) end def log_backup_destroy(backup, opts = {}) raise Discourse::InvalidParameters.new(:backup) unless backup - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:backup_destroy], - ip_address: @admin.ip_address.to_s, - details: backup.filename - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:backup_destroy], + ip_address: @admin.ip_address.to_s, + details: backup.filename, + ), + ) end def log_revoke_email(user, reason, opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:revoke_email], - target_user_id: user.id, - details: reason - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:revoke_email], + target_user_id: user.id, + details: reason, + ), + ) end def log_user_approve(user, opts = {}) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:approve_user], - target_user_id: user.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:approve_user], target_user_id: user.id), + ) end def log_user_deactivate(user, reason, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:deactivate_user], - target_user_id: user.id, - details: reason - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:deactivate_user], + target_user_id: user.id, + details: reason, + ), + ) end def log_user_activate(user, reason, opts = {}) raise Discourse::InvalidParameters.new(:user) unless user - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:activate_user], - target_user_id: user.id, - details: reason - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:activate_user], + target_user_id: user.id, + details: reason, + ), + ) end def log_wizard_step(step, opts = {}) raise Discourse::InvalidParameters.new(:step) unless step - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:wizard_step], - context: step.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:wizard_step], context: step.id), + ) end def log_change_readonly_mode(state) - UserHistory.create!(params.merge( - action: UserHistory.actions[:change_readonly_mode], - previous_value: !state, - new_value: state - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:change_readonly_mode], + previous_value: !state, + new_value: state, + ), + ) end def log_check_personal_message(topic, opts = {}) raise Discourse::InvalidParameters.new(:topic) unless topic && topic.is_a?(Topic) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:check_personal_message], - topic_id: topic.id, - context: topic.relative_url - )) + UserHistory.create!( + params(opts).merge( + action: UserHistory.actions[:check_personal_message], + topic_id: topic.id, + context: topic.relative_url, + ), + ) end def log_post_approved(post, opts = {}) raise Discourse::InvalidParameters.new(:post) unless post.is_a?(Post) - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:post_approved], - post_id: post.id - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:post_approved], post_id: post.id), + ) end def log_post_rejected(reviewable, rejected_at, opts = {}) raise Discourse::InvalidParameters.new(:rejected_post) unless reviewable.is_a?(Reviewable) topic = reviewable.topic || Topic.with_deleted.find_by(id: reviewable.topic_id) - topic_title = topic&.title || I18n.t('staff_action_logs.not_found') - username = reviewable.created_by&.username || I18n.t('staff_action_logs.unknown') - name = reviewable.created_by&.name || I18n.t('staff_action_logs.unknown') + topic_title = topic&.title || I18n.t("staff_action_logs.not_found") + username = reviewable.created_by&.username || I18n.t("staff_action_logs.unknown") + name = reviewable.created_by&.name || I18n.t("staff_action_logs.unknown") details = [ "created_at: #{reviewable.created_at}", "rejected_at: #{rejected_at}", "user: #{username} (#{name})", "topic: #{topic_title}", - "raw: #{reviewable.payload['raw']}", + "raw: #{reviewable.payload["raw"]}", ] - UserHistory.create!(params(opts).merge( - action: UserHistory.actions[:post_rejected], - details: details.join("\n") - )) + UserHistory.create!( + params(opts).merge(action: UserHistory.actions[:post_rejected], details: details.join("\n")), + ) end def log_web_hook(web_hook, action, opts = {}) - details = [ - "webhook_id: #{web_hook.id}", - "payload_url: #{web_hook.payload_url}" - ] + details = ["webhook_id: #{web_hook.id}", "payload_url: #{web_hook.payload_url}"] old_values, new_values = get_changes(opts[:changes]) - UserHistory.create!(params(opts).merge( - action: action, - context: details.join(", "), - previous_value: old_values&.join(", "), - new_value: new_values&.join(", ") - )) + UserHistory.create!( + params(opts).merge( + action: action, + context: details.join(", "), + previous_value: old_values&.join(", "), + new_value: new_values&.join(", "), + ), + ) end def log_web_hook_deactivate(web_hook, response_http_status, opts = {}) - context = [ - "webhook_id: #{web_hook.id}", - "webhook_response_status: #{response_http_status}" - ] + context = ["webhook_id: #{web_hook.id}", "webhook_response_status: #{response_http_status}"] - UserHistory.create!(params.merge( - action: UserHistory.actions[:web_hook_deactivate], - context: context, - details: I18n.t('staff_action_logs.webhook_deactivation_reason', status: response_http_status) - )) + UserHistory.create!( + params.merge( + action: UserHistory.actions[:web_hook_deactivate], + context: context, + details: + I18n.t("staff_action_logs.webhook_deactivation_reason", status: response_http_status), + ), + ) end def log_embeddable_host(embeddable_host, action, opts = {}) old_values, new_values = get_changes(opts[:changes]) - UserHistory.create!(params(opts).merge( - action: action, - context: "host: #{embeddable_host.host}", - previous_value: old_values&.join(", "), - new_value: new_values&.join(", ") - )) + UserHistory.create!( + params(opts).merge( + action: action, + context: "host: #{embeddable_host.host}", + previous_value: old_values&.join(", "), + new_value: new_values&.join(", "), + ), + ) end def log_api_key(api_key, action, opts = {}) opts[:changes]&.delete("key") # Do not log the full key - history_params = params(opts).merge( - action: action, - subject: api_key.truncated_key - ) + history_params = params(opts).merge(action: action, subject: api_key.truncated_key) if opts[:changes] old_values, new_values = get_changes(opts[:changes]) - history_params[:previous_value] = old_values&.join(", ") unless opts[:changes].keys.include?("id") + history_params[:previous_value] = old_values&.join(", ") unless opts[:changes].keys.include?( + "id", + ) history_params[:new_value] = new_values&.join(", ") end @@ -750,35 +853,39 @@ class StaffActionLogger end def log_api_key_revoke(api_key) - UserHistory.create!(params.merge( - subject: api_key.truncated_key, - action: UserHistory.actions[:api_key_update], - details: I18n.t("staff_action_logs.api_key.revoked") - )) + UserHistory.create!( + params.merge( + subject: api_key.truncated_key, + action: UserHistory.actions[:api_key_update], + details: I18n.t("staff_action_logs.api_key.revoked"), + ), + ) end def log_api_key_restore(api_key) - UserHistory.create!(params.merge( - subject: api_key.truncated_key, - action: UserHistory.actions[:api_key_update], - details: I18n.t("staff_action_logs.api_key.restored") - )) + UserHistory.create!( + params.merge( + subject: api_key.truncated_key, + action: UserHistory.actions[:api_key_update], + details: I18n.t("staff_action_logs.api_key.restored"), + ), + ) end def log_published_page(topic_id, slug) - UserHistory.create!(params.merge( - subject: slug, - topic_id: topic_id, - action: UserHistory.actions[:page_published] - )) + UserHistory.create!( + params.merge(subject: slug, topic_id: topic_id, action: UserHistory.actions[:page_published]), + ) end def log_unpublished_page(topic_id, slug) - UserHistory.create!(params.merge( - subject: slug, - topic_id: topic_id, - action: UserHistory.actions[:page_unpublished] - )) + UserHistory.create!( + params.merge( + subject: slug, + topic_id: topic_id, + action: UserHistory.actions[:page_unpublished], + ), + ) end def log_add_email(user) @@ -787,7 +894,7 @@ class StaffActionLogger UserHistory.create!( action: UserHistory.actions[:add_email], acting_user_id: @admin.id, - target_user_id: user.id + target_user_id: user.id, ) end @@ -797,7 +904,7 @@ class StaffActionLogger UserHistory.create!( action: UserHistory.actions[:update_email], acting_user_id: @admin.id, - target_user_id: user.id + target_user_id: user.id, ) end @@ -807,7 +914,7 @@ class StaffActionLogger UserHistory.create!( action: UserHistory.actions[:destroy_email], acting_user_id: @admin.id, - target_user_id: user.id + target_user_id: user.id, ) end @@ -818,7 +925,7 @@ class StaffActionLogger action: UserHistory.actions[:watched_word_create], acting_user_id: @admin.id, details: watched_word.action_log_details, - context: WatchedWord.actions[watched_word.action] + context: WatchedWord.actions[watched_word.action], ) end @@ -829,26 +936,21 @@ class StaffActionLogger action: UserHistory.actions[:watched_word_destroy], acting_user_id: @admin.id, details: watched_word.action_log_details, - context: WatchedWord.actions[watched_word.action] + context: WatchedWord.actions[watched_word.action], ) end def log_group_deletetion(group) raise Discourse::InvalidParameters.new(:group) if group.nil? - details = [ - "name: #{group.name}", - "id: #{group.id}" - ] + details = ["name: #{group.name}", "id: #{group.id}"] - if group.grant_trust_level - details << "grant_trust_level: #{group.grant_trust_level}" - end + details << "grant_trust_level: #{group.grant_trust_level}" if group.grant_trust_level UserHistory.create!( acting_user_id: @admin.id, action: UserHistory.actions[:delete_group], - details: details.join(', ') + details: details.join(", "), ) end diff --git a/app/services/themes_install_task.rb b/app/services/themes_install_task.rb index ac32739a1bf..3eaa931944b 100644 --- a/app/services/themes_install_task.rb +++ b/app/services/themes_install_task.rb @@ -48,20 +48,27 @@ class ThemesInstallTask end def repo_name - @url.gsub(Regexp.union('git@github.com:', 'https://github.com/', '.git'), '') + @url.gsub(Regexp.union("git@github.com:", "https://github.com/", ".git"), "") end def theme_exists? - @remote_theme = RemoteTheme - .where("remote_url like ?", "%#{repo_name}%") - .where(branch: @options.fetch(:branch, nil)) - .first + @remote_theme = + RemoteTheme + .where("remote_url like ?", "%#{repo_name}%") + .where(branch: @options.fetch(:branch, nil)) + .first @theme = @remote_theme&.theme @theme.present? end def install - @theme = RemoteTheme.import_theme(@url, Discourse.system_user, private_key: @options[:private_key], branch: @options[:branch]) + @theme = + RemoteTheme.import_theme( + @url, + Discourse.system_user, + private_key: @options[:private_key], + branch: @options[:branch], + ) @theme.set_default! if @options.fetch(:default, false) add_component_to_all_themes end @@ -76,9 +83,13 @@ class ThemesInstallTask def add_component_to_all_themes return if (!@options.fetch(:add_to_all_themes, false) || !@theme.component) - Theme.where(component: false).each do |parent_theme| - next if ChildTheme.where(parent_theme_id: parent_theme.id, child_theme_id: @theme.id).exists? - parent_theme.add_relative_theme!(:child, @theme) - end + Theme + .where(component: false) + .each do |parent_theme| + if ChildTheme.where(parent_theme_id: parent_theme.id, child_theme_id: @theme.id).exists? + next + end + parent_theme.add_relative_theme!(:child, @theme) + end end end diff --git a/app/services/topic_bookmarkable.rb b/app/services/topic_bookmarkable.rb index 5a5276f64a7..f79af4c07c4 100644 --- a/app/services/topic_bookmarkable.rb +++ b/app/services/topic_bookmarkable.rb @@ -19,28 +19,29 @@ class TopicBookmarkable < BaseBookmarkable topics = topic_bookmarks.map(&:bookmarkable) topic_user_lookup = TopicUser.lookup_for(guardian.user, topics) - topics.each do |topic| - topic.user_data = topic_user_lookup[topic.id] - end + topics.each { |topic| topic.user_data = topic_user_lookup[topic.id] } end def self.list_query(user, guardian) topics = Topic.listable_topics.secured(guardian) pms = Topic.private_messages_for_user(user) - topic_bookmarks = user - .bookmarks_of_type("Topic") - .joins("INNER JOIN topics ON topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic'") - .joins("LEFT JOIN topic_users ON topic_users.topic_id = topics.id") - .where("topic_users.user_id = ?", user.id) + topic_bookmarks = + user + .bookmarks_of_type("Topic") + .joins( + "INNER JOIN topics ON topics.id = bookmarks.bookmarkable_id AND bookmarks.bookmarkable_type = 'Topic'", + ) + .joins("LEFT JOIN topic_users ON topic_users.topic_id = topics.id") + .where("topic_users.user_id = ?", user.id) guardian.filter_allowed_categories(topic_bookmarks.merge(topics.or(pms))) end def self.search_query(bookmarks, query, ts_query, &bookmarkable_search) bookmarkable_search.call( - bookmarks - .joins("LEFT JOIN posts ON posts.topic_id = topics.id AND posts.post_number = 1") - .joins("LEFT JOIN post_search_data ON post_search_data.post_id = posts.id"), - "#{ts_query} @@ post_search_data.search_data" + bookmarks.joins( + "LEFT JOIN posts ON posts.topic_id = topics.id AND posts.post_number = 1", + ).joins("LEFT JOIN post_search_data ON post_search_data.post_id = posts.id"), + "#{ts_query} @@ post_search_data.search_data", ) end @@ -51,8 +52,8 @@ class TopicBookmarkable < BaseBookmarkable post_number: 1, data: { title: bookmark.bookmarkable.title, - bookmarkable_url: bookmark.bookmarkable.first_post.url - } + bookmarkable_url: bookmark.bookmarkable.first_post.url, + }, ) end diff --git a/app/services/topic_status_updater.rb b/app/services/topic_status_updater.rb index d5e1d95d446..7008c3223a5 100644 --- a/app/services/topic_status_updater.rb +++ b/app/services/topic_status_updater.rb @@ -1,167 +1,176 @@ # frozen_string_literal: true -TopicStatusUpdater = Struct.new(:topic, :user) do - def update!(status, enabled, opts = {}) - status = Status.new(status, enabled) +TopicStatusUpdater = + Struct.new(:topic, :user) do + def update!(status, enabled, opts = {}) + status = Status.new(status, enabled) - @topic_timer = topic.public_topic_timer + @topic_timer = topic.public_topic_timer - updated = nil - Topic.transaction do - updated = change(status, opts) - if updated - highest_post_number = topic.highest_post_number - create_moderator_post_for(status, opts) - update_read_state_for(status, highest_post_number) + updated = nil + Topic.transaction do + updated = change(status, opts) + if updated + highest_post_number = topic.highest_post_number + create_moderator_post_for(status, opts) + update_read_state_for(status, highest_post_number) + end end + + updated end - updated - end + private - private + def change(status, opts = {}) + result = true - def change(status, opts = {}) - result = true - - if status.pinned? || status.pinned_globally? - topic.update_pinned(status.enabled?, status.pinned_globally?, opts[:until]) - elsif status.autoclosed? - rc = Topic.where(id: topic.id, closed: !status.enabled?).update_all(closed: status.enabled?) - topic.closed = status.enabled? - result = false if rc == 0 - else - rc = Topic.where(:id => topic.id, status.name => !status.enabled) - .update_all(status.name => status.enabled?) - - topic.public_send("#{status.name}=", status.enabled?) - result = false if rc == 0 - end - - if status.manually_closing_topic? - DiscourseEvent.trigger(:topic_closed, topic) - end - - if status.visible? && status.disabled? - UserProfile.remove_featured_topic_from_all_profiles(topic) - end - - if status.visible? && result - topic.update_category_topic_count_by(status.enabled? ? 1 : -1) - UserStatCountUpdater.public_send(status.enabled? ? :increment! : :decrement!, topic.first_post) - end - - if @topic_timer - if status.manually_closing_topic? || status.closing_topic? - topic.delete_topic_timer(TopicTimer.types[:close]) - topic.delete_topic_timer(TopicTimer.types[:silent_close]) - elsif status.manually_opening_topic? || status.opening_topic? - topic.delete_topic_timer(TopicTimer.types[:open]) - topic.inherit_auto_close_from_category - end - end - - # remove featured topics if we close/archive/make them invisible. Previously we used - # to run the whole featuring logic but that could be very slow and have concurrency - # errors on large sites with many autocloses and topics being created. - if ((status.enabled? && (status.autoclosed? || status.closed? || status.archived?)) || - (status.disabled? && status.visible?)) - CategoryFeaturedTopic.where(topic_id: topic.id).delete_all - end - - result - end - - def create_moderator_post_for(status, opts) - message = opts[:message] - topic.add_moderator_post(user, message || message_for(status), options_for(status, opts)) - topic.reload - end - - def update_read_state_for(status, old_highest_read) - if status.autoclosed? && status.enabled? - # let's pretend all the people that read up to the autoclose message - # actually read the topic - PostTiming.pretend_read(topic.id, old_highest_read, topic.highest_post_number) - end - end - - def message_for(status) - if status.autoclosed? - locale_key = status.locale_key.dup - locale_key << "_lastpost" if @topic_timer&.based_on_last_post - message_for_autoclosed(locale_key) - end - end - - def message_for_autoclosed(locale_key) - num_minutes = - if @topic_timer&.based_on_last_post - (@topic_timer.duration_minutes || 0).minutes.to_i - elsif @topic_timer&.created_at - Time.zone.now - @topic_timer.created_at + if status.pinned? || status.pinned_globally? + topic.update_pinned(status.enabled?, status.pinned_globally?, opts[:until]) + elsif status.autoclosed? + rc = Topic.where(id: topic.id, closed: !status.enabled?).update_all(closed: status.enabled?) + topic.closed = status.enabled? + result = false if rc == 0 else - Time.zone.now - topic.created_at + rc = + Topic.where(:id => topic.id, status.name => !status.enabled).update_all( + status.name => status.enabled?, + ) + + topic.public_send("#{status.name}=", status.enabled?) + result = false if rc == 0 end - # all of the results above are in seconds, this brings them - # back to the actual minutes integer - num_minutes = (num_minutes / 1.minute).round + DiscourseEvent.trigger(:topic_closed, topic) if status.manually_closing_topic? - if num_minutes.minutes >= 2.days - I18n.t("#{locale_key}_days", count: (num_minutes.minutes / 1.day).round) - else - num_hours = (num_minutes.minutes / 1.hour).round - if num_hours >= 2 - I18n.t("#{locale_key}_hours", count: num_hours) + if status.visible? && status.disabled? + UserProfile.remove_featured_topic_from_all_profiles(topic) + end + + if status.visible? && result + topic.update_category_topic_count_by(status.enabled? ? 1 : -1) + UserStatCountUpdater.public_send( + status.enabled? ? :increment! : :decrement!, + topic.first_post, + ) + end + + if @topic_timer + if status.manually_closing_topic? || status.closing_topic? + topic.delete_topic_timer(TopicTimer.types[:close]) + topic.delete_topic_timer(TopicTimer.types[:silent_close]) + elsif status.manually_opening_topic? || status.opening_topic? + topic.delete_topic_timer(TopicTimer.types[:open]) + topic.inherit_auto_close_from_category + end + end + + # remove featured topics if we close/archive/make them invisible. Previously we used + # to run the whole featuring logic but that could be very slow and have concurrency + # errors on large sites with many autocloses and topics being created. + if ( + (status.enabled? && (status.autoclosed? || status.closed? || status.archived?)) || + (status.disabled? && status.visible?) + ) + CategoryFeaturedTopic.where(topic_id: topic.id).delete_all + end + + result + end + + def create_moderator_post_for(status, opts) + message = opts[:message] + topic.add_moderator_post(user, message || message_for(status), options_for(status, opts)) + topic.reload + end + + def update_read_state_for(status, old_highest_read) + if status.autoclosed? && status.enabled? + # let's pretend all the people that read up to the autoclose message + # actually read the topic + PostTiming.pretend_read(topic.id, old_highest_read, topic.highest_post_number) + end + end + + def message_for(status) + if status.autoclosed? + locale_key = status.locale_key.dup + locale_key << "_lastpost" if @topic_timer&.based_on_last_post + message_for_autoclosed(locale_key) + end + end + + def message_for_autoclosed(locale_key) + num_minutes = + if @topic_timer&.based_on_last_post + (@topic_timer.duration_minutes || 0).minutes.to_i + elsif @topic_timer&.created_at + Time.zone.now - @topic_timer.created_at + else + Time.zone.now - topic.created_at + end + + # all of the results above are in seconds, this brings them + # back to the actual minutes integer + num_minutes = (num_minutes / 1.minute).round + + if num_minutes.minutes >= 2.days + I18n.t("#{locale_key}_days", count: (num_minutes.minutes / 1.day).round) else - I18n.t("#{locale_key}_minutes", count: num_minutes) + num_hours = (num_minutes.minutes / 1.hour).round + if num_hours >= 2 + I18n.t("#{locale_key}_hours", count: num_hours) + else + I18n.t("#{locale_key}_minutes", count: num_minutes) + end end end + + def options_for(status, opts = {}) + { + bump: status.opening_topic?, + post_type: Post.types[:small_action], + silent: opts[:silent], + action_code: status.action_code, + } + end + + Status = + Struct.new(:name, :enabled) do + %w[pinned_globally pinned autoclosed closed visible archived].each do |status| + define_method("#{status}?") { name == status } + end + + def enabled? + enabled + end + + def disabled? + !enabled? + end + + def action_code + "#{name}.#{enabled? ? "enabled" : "disabled"}" + end + + def locale_key + "topic_statuses.#{action_code.tr(".", "_")}" + end + + def opening_topic? + (closed? || autoclosed?) && disabled? + end + + def closing_topic? + (closed? || autoclosed?) && enabled? + end + + def manually_closing_topic? + closed? && enabled? + end + + def manually_opening_topic? + closed? && disabled? + end + end end - - def options_for(status, opts = {}) - { bump: status.opening_topic?, - post_type: Post.types[:small_action], - silent: opts[:silent], - action_code: status.action_code } - end - - Status = Struct.new(:name, :enabled) do - %w(pinned_globally pinned autoclosed closed visible archived).each do |status| - define_method("#{status}?") { name == status } - end - - def enabled? - enabled - end - - def disabled? - !enabled? - end - - def action_code - "#{name}.#{enabled? ? 'enabled' : 'disabled'}" - end - - def locale_key - "topic_statuses.#{action_code.tr('.', '_')}" - end - - def opening_topic? - (closed? || autoclosed?) && disabled? - end - - def closing_topic? - (closed? || autoclosed?) && enabled? - end - - def manually_closing_topic? - closed? && enabled? - end - - def manually_opening_topic? - closed? && disabled? - end - end -end diff --git a/app/services/topic_timestamp_changer.rb b/app/services/topic_timestamp_changer.rb index a8d5059c4d2..c9f1d8b552f 100644 --- a/app/services/topic_timestamp_changer.rb +++ b/app/services/topic_timestamp_changer.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class TopicTimestampChanger - class InvalidTimestampError < StandardError; end + class InvalidTimestampError < StandardError + end def initialize(timestamp:, topic: nil, topic_id: nil) @topic = topic || Topic.with_deleted.find(topic_id) @@ -46,11 +47,7 @@ class TopicTimestampChanger end def update_topic(last_posted_at) - @topic.update( - created_at: @timestamp, - updated_at: @timestamp, - last_posted_at: last_posted_at - ) + @topic.update(created_at: @timestamp, updated_at: @timestamp, last_posted_at: last_posted_at) end def update_post(post, timestamp) diff --git a/app/services/tracked_topics_updater.rb b/app/services/tracked_topics_updater.rb index 018303af0bd..1fc74ce2070 100644 --- a/app/services/tracked_topics_updater.rb +++ b/app/services/tracked_topics_updater.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TrackedTopicsUpdater - def initialize(user_id, threshold) @id = user_id @threshold = threshold @@ -12,8 +11,14 @@ class TrackedTopicsUpdater if @threshold < 0 topic_users.update_all(notification_level: TopicUser.notification_levels[:regular]) else - topic_users.update_all(["notification_level = CASE WHEN total_msecs_viewed < ? THEN ? ELSE ? END", - @threshold, TopicUser.notification_levels[:regular], TopicUser.notification_levels[:tracking]]) + topic_users.update_all( + [ + "notification_level = CASE WHEN total_msecs_viewed < ? THEN ? ELSE ? END", + @threshold, + TopicUser.notification_levels[:regular], + TopicUser.notification_levels[:tracking], + ], + ) end end end diff --git a/app/services/trust_level_granter.rb b/app/services/trust_level_granter.rb index db284b5dc8c..9d95d117a58 100644 --- a/app/services/trust_level_granter.rb +++ b/app/services/trust_level_granter.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class TrustLevelGranter - def initialize(trust_level, user) @trust_level, @user = trust_level, user end diff --git a/app/services/user_action_manager.rb b/app/services/user_action_manager.rb index ae460b6235e..61a207687d0 100644 --- a/app/services/user_action_manager.rb +++ b/app/services/user_action_manager.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserActionManager - def self.disable @disabled = true end @@ -10,8 +9,7 @@ class UserActionManager @disabled = false end - [:notification, :post, :topic, :post_action].each do |type| - self.class_eval(<<~RUBY) + %i[notification post topic post_action].each { |type| self.class_eval(<<~RUBY) } def self.#{type}_created(*args) return if @disabled #{type}_rows(*args).each { |row| UserAction.log_action!(row) } @@ -21,9 +19,8 @@ class UserActionManager #{type}_rows(*args).each { |row| UserAction.remove_action!(row) } end RUBY - end -private + private def self.topic_rows(topic) # no action to log here, this can happen if a user is deleted @@ -36,22 +33,28 @@ private acting_user_id: topic.user_id, target_topic_id: topic.id, target_post_id: -1, - created_at: topic.created_at + created_at: topic.created_at, } - UserAction.remove_action!(row.merge( - action_type: topic.private_message? ? UserAction::NEW_TOPIC : UserAction::NEW_PRIVATE_MESSAGE - )) + UserAction.remove_action!( + row.merge( + action_type: + topic.private_message? ? UserAction::NEW_TOPIC : UserAction::NEW_PRIVATE_MESSAGE, + ), + ) rows = [row] if topic.private_message? - topic.topic_allowed_users.reject { |a| a.user_id == topic.user_id }.each do |ta| - row = row.dup - row[:user_id] = ta.user_id - row[:action_type] = UserAction::GOT_PRIVATE_MESSAGE - rows << row - end + topic + .topic_allowed_users + .reject { |a| a.user_id == topic.user_id } + .each do |ta| + row = row.dup + row[:user_id] = ta.user_id + row[:action_type] = UserAction::GOT_PRIVATE_MESSAGE + rows << row + end end rows end @@ -66,7 +69,7 @@ private acting_user_id: post.user_id, target_post_id: post.id, target_topic_id: post.topic_id, - created_at: post.created_at + created_at: post.created_at, } rows = [row] @@ -76,7 +79,13 @@ private post.topic.topic_allowed_users.each do |ta| row = row.dup row[:user_id] = ta.user_id - row[:action_type] = ta.user_id == post.user_id ? UserAction::NEW_PRIVATE_MESSAGE : UserAction::GOT_PRIVATE_MESSAGE + row[:action_type] = ( + if ta.user_id == post.user_id + UserAction::NEW_PRIVATE_MESSAGE + else + UserAction::GOT_PRIVATE_MESSAGE + end + ) rows << row end end @@ -100,13 +109,15 @@ private # skip any invalid items, eg failed to save post and so on return [] unless action && post && user && post.id - [{ - action_type: action, - user_id: user.id, - acting_user_id: acting_user_id || post.user_id, - target_topic_id: post.topic_id, - target_post_id: post.id - }] + [ + { + action_type: action, + user_id: user.id, + acting_user_id: acting_user_id || post.user_id, + target_topic_id: post.topic_id, + target_post_id: post.id, + }, + ] end def self.post_action_rows(post_action) @@ -121,11 +132,13 @@ private acting_user_id: post_action.user_id, target_post_id: post_action.post_id, target_topic_id: post.topic_id, - created_at: post_action.created_at + created_at: post_action.created_at, } - post_action.is_like? ? - [row, row.merge(action_type: UserAction::WAS_LIKED, user_id: post.user_id)] : + if post_action.is_like? + [row, row.merge(action_type: UserAction::WAS_LIKED, user_id: post.user_id)] + else [row] + end end end diff --git a/app/services/user_activator.rb b/app/services/user_activator.rb index 4c0d14ab623..6d45907fbfe 100644 --- a/app/services/user_activator.rb +++ b/app/services/user_activator.rb @@ -39,7 +39,6 @@ class UserActivator LoginActivator end end - end class ApprovalActivator < UserActivator @@ -69,7 +68,7 @@ class LoginActivator < UserActivator def activate log_on_user(user) - user.enqueue_welcome_message('welcome_user') + user.enqueue_welcome_message("welcome_user") success_message end diff --git a/app/services/user_anonymizer.rb b/app/services/user_anonymizer.rb index 970d1fff42a..6f1b317c6c7 100644 --- a/app/services/user_anonymizer.rb +++ b/app/services/user_anonymizer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserAnonymizer - attr_reader :user_history # opts: @@ -55,7 +54,7 @@ class UserAnonymizer bio_raw: nil, bio_cooked: nil, profile_background_upload: nil, - card_background_upload: nil + card_background_upload: nil, ) end @@ -70,15 +69,19 @@ class UserAnonymizer @user_history = log_action end - UsernameChanger.update_username(user_id: @user.id, - old_username: @prev_username, - new_username: @user.username, - avatar_template: @user.avatar_template) + UsernameChanger.update_username( + user_id: @user.id, + old_username: @prev_username, + new_username: @user.username, + avatar_template: @user.avatar_template, + ) - Jobs.enqueue(:anonymize_user, - user_id: @user.id, - prev_email: @prev_email, - anonymize_ip: @opts[:anonymize_ip]) + Jobs.enqueue( + :anonymize_user, + user_id: @user.id, + prev_email: @prev_email, + anonymize_ip: @opts[:anonymize_ip], + ) DiscourseEvent.trigger(:user_anonymized, user: @user, opts: @opts) @user @@ -88,7 +91,7 @@ class UserAnonymizer def make_anon_username 100.times do - new_username = "anon#{(SecureRandom.random_number * 100000000).to_i}" + new_username = "anon#{(SecureRandom.random_number * 100_000_000).to_i}" return new_username unless User.where(username_lower: new_username).exists? end raise "Failed to generate an anon username" diff --git a/app/services/user_authenticator.rb b/app/services/user_authenticator.rb index c5cd920cf1c..73dbe04e2a4 100644 --- a/app/services/user_authenticator.rb +++ b/app/services/user_authenticator.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class UserAuthenticator - - def initialize(user, session, authenticator_finder: Users::OmniauthCallbacksController, require_password: true) + def initialize( + user, + session, + authenticator_finder: Users::OmniauthCallbacksController, + require_password: true + ) @user = user @session = session if session&.dig(:authentication) && session[:authentication].is_a?(Hash) @@ -61,5 +65,4 @@ class UserAuthenticator def authenticator_name @auth_result&.authenticator_name end - end diff --git a/app/services/user_destroyer.rb b/app/services/user_destroyer.rb index ed91b77422a..0936d5cffa4 100644 --- a/app/services/user_destroyer.rb +++ b/app/services/user_destroyer.rb @@ -2,19 +2,19 @@ # Responsible for destroying a User record class UserDestroyer - - class PostsExistError < RuntimeError; end + class PostsExistError < RuntimeError + end def initialize(actor) @actor = actor - raise Discourse::InvalidParameters.new('acting user is nil') unless @actor && @actor.is_a?(User) + raise Discourse::InvalidParameters.new("acting user is nil") unless @actor && @actor.is_a?(User) @guardian = Guardian.new(actor) end # Returns false if the user failed to be deleted. # Returns a frozen instance of the User if the delete succeeded. def destroy(user, opts = {}) - raise Discourse::InvalidParameters.new('user is nil') unless user && user.is_a?(User) + raise Discourse::InvalidParameters.new("user is nil") unless user && user.is_a?(User) raise PostsExistError if !opts[:delete_posts] && user.posts.joins(:topic).count != 0 @guardian.ensure_can_delete_user!(user) @@ -26,7 +26,6 @@ class UserDestroyer result = nil optional_transaction(open_transaction: opts[:transaction]) do - UserSecurityKey.where(user_id: user.id).delete_all Bookmark.where(user_id: user.id).delete_all Draft.where(user_id: user.id).delete_all @@ -44,17 +43,15 @@ class UserDestroyer delete_posts(user, category_topic_ids, opts) end - user.post_actions.find_each do |post_action| - post_action.remove_act!(Discourse.system_user) - end + user.post_actions.find_each { |post_action| post_action.remove_act!(Discourse.system_user) } # Add info about the user to staff action logs UserHistory.staff_action_records( - Discourse.system_user, acting_user: user.username - ).update_all([ - "details = CONCAT(details, ?)", - "\nuser_id: #{user.id}\nusername: #{user.username}" - ]) + Discourse.system_user, + acting_user: user.username, + ).update_all( + ["details = CONCAT(details, ?)", "\nuser_id: #{user.id}\nusername: #{user.username}"], + ) # keep track of emails used user_emails = user.user_emails.pluck(:email) @@ -76,22 +73,26 @@ class UserDestroyer Post.unscoped.where(user_id: result.id).update_all(user_id: nil) # If this user created categories, fix those up: - Category.where(user_id: result.id).each do |c| - c.user_id = Discourse::SYSTEM_USER_ID - c.save! - if topic = Topic.unscoped.find_by(id: c.topic_id) - topic.recover! - topic.user_id = Discourse::SYSTEM_USER_ID - topic.save! + Category + .where(user_id: result.id) + .each do |c| + c.user_id = Discourse::SYSTEM_USER_ID + c.save! + if topic = Topic.unscoped.find_by(id: c.topic_id) + topic.recover! + topic.user_id = Discourse::SYSTEM_USER_ID + topic.save! + end end - end - Invite.where(email: user_emails).each do |invite| - # invited_users will be removed by dependent destroy association when user is destroyed - invite.invited_groups.destroy_all - invite.topic_invites.destroy_all - invite.destroy - end + Invite + .where(email: user_emails) + .each do |invite| + # invited_users will be removed by dependent destroy association when user is destroyed + invite.invited_groups.destroy_all + invite.topic_invites.destroy_all + invite.destroy + end unless opts[:quiet] if @actor == user @@ -101,7 +102,9 @@ class UserDestroyer deleted_by = @actor end StaffActionLogger.new(deleted_by).log_user_deletion(user, opts.slice(:context)) - Rails.logger.warn("User destroyed without context from: #{caller_locations(14, 1)[0]}") if opts.slice(:context).blank? + if opts.slice(:context).blank? + Rails.logger.warn("User destroyed without context from: #{caller_locations(14, 1)[0]}") + end end MessageBus.publish "/logout/#{result.id}", result.id, user_ids: [result.id] end @@ -118,16 +121,22 @@ class UserDestroyer protected def block_external_urls(user) - TopicLink.where(user: user, internal: false).find_each do |link| - next if Oneboxer.engine(link.url) != Onebox::Engine::AllowlistedGenericOnebox - ScreenedUrl.watch(link.url, link.domain, ip_address: user.ip_address)&.record_match! - end + TopicLink + .where(user: user, internal: false) + .find_each do |link| + next if Oneboxer.engine(link.url) != Onebox::Engine::AllowlistedGenericOnebox + ScreenedUrl.watch(link.url, link.domain, ip_address: user.ip_address)&.record_match! + end end def agree_with_flags(user) - ReviewableFlaggedPost.where(target_created_by: user).find_each do |reviewable| - reviewable.perform(@actor, :agree_and_keep) if reviewable.actions_for(@guardian).has?(:agree_and_keep) - end + ReviewableFlaggedPost + .where(target_created_by: user) + .find_each do |reviewable| + if reviewable.actions_for(@guardian).has?(:agree_and_keep) + reviewable.perform(@actor, :agree_and_keep) + end + end end def delete_posts(user, category_topic_ids, opts) @@ -146,7 +155,10 @@ class UserDestroyer def prepare_for_destroy(user) PostAction.where(user_id: user.id).delete_all - UserAction.where('user_id = :user_id OR target_user_id = :user_id OR acting_user_id = :user_id', user_id: user.id).delete_all + UserAction.where( + "user_id = :user_id OR target_user_id = :user_id OR acting_user_id = :user_id", + user_id: user.id, + ).delete_all PostTiming.where(user_id: user.id).delete_all TopicViewItem.where(user_id: user.id).delete_all TopicUser.where(user_id: user.id).delete_all @@ -156,10 +168,9 @@ class UserDestroyer def optional_transaction(open_transaction: true) if open_transaction - User.transaction { yield } + User.transaction { yield } else yield end end - end diff --git a/app/services/user_merger.rb b/app/services/user_merger.rb index a17089200dc..96af036a464 100644 --- a/app/services/user_merger.rb +++ b/app/services/user_merger.rb @@ -33,23 +33,35 @@ class UserMerger def update_username return if @source_user.username == @target_user.username - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_username") }, user_ids: [@acting_user.id] if @acting_user - UsernameChanger.update_username(user_id: @source_user.id, - old_username: @source_user.username, - new_username: @target_user.username, - avatar_template: @target_user.avatar_template, - asynchronous: false) + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.updating_username") }, + user_ids: [@acting_user.id] + end + UsernameChanger.update_username( + user_id: @source_user.id, + old_username: @source_user.username, + new_username: @target_user.username, + avatar_template: @target_user.avatar_template, + asynchronous: false, + ) end def move_posts - posts = Post.with_deleted - .where(user_id: @source_user.id) - .order(:topic_id, :post_number) - .pluck(:topic_id, :id) + posts = + Post + .with_deleted + .where(user_id: @source_user.id) + .order(:topic_id, :post_number) + .pluck(:topic_id, :id) return if posts.count == 0 - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.changing_post_ownership") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.changing_post_ownership") }, + user_ids: [@acting_user.id] + end last_topic_id = nil post_ids = [] @@ -73,12 +85,16 @@ class UserMerger post_ids: post_ids, new_owner: @target_user, acting_user: Discourse.system_user, - skip_revision: true + skip_revision: true, ).change_owner! end def merge_given_daily_likes - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.merging_given_daily_likes") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.merging_given_daily_likes") }, + user_ids: [@acting_user.id] + end sql = <<~SQL INSERT INTO given_daily_likes AS g (user_id, likes_given, given_date, limit_reached) @@ -107,15 +123,21 @@ class UserMerger source_user_id: @source_user.id, target_user_id: @target_user.id, max_likes_per_day: SiteSetting.max_likes_per_day, - action_type_id: PostActionType.types[:like] + action_type_id: PostActionType.types[:like], ) end def merge_post_timings - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.merging_post_timings") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.merging_post_timings") }, + user_ids: [@acting_user.id] + end - update_user_id(:post_timings, conditions: ["x.topic_id = y.topic_id", - "x.post_number = y.post_number"]) + update_user_id( + :post_timings, + conditions: ["x.topic_id = y.topic_id", "x.post_number = y.post_number"], + ) sql = <<~SQL UPDATE post_timings AS t SET msecs = LEAST(t.msecs::bigint + s.msecs, 2^31 - 1) @@ -128,7 +150,11 @@ class UserMerger end def merge_user_visits - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.merging_user_visits") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.merging_user_visits") }, + user_ids: [@acting_user.id] + end update_user_id(:user_visits, conditions: "x.visited_at = y.visited_at") @@ -146,17 +172,27 @@ class UserMerger end def update_site_settings - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_site_settings") }, user_ids: [@acting_user.id] if @acting_user - - SiteSetting.all_settings(include_hidden: true).each do |setting| - if setting[:type] == "username" && setting[:value] == @source_user.username - SiteSetting.set_and_log(setting[:setting], @target_user.username) - end + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.updating_site_settings") }, + user_ids: [@acting_user.id] end + + SiteSetting + .all_settings(include_hidden: true) + .each do |setting| + if setting[:type] == "username" && setting[:value] == @source_user.username + SiteSetting.set_and_log(setting[:setting], @target_user.username) + end + end end def update_user_stats - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_user_stats") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.updating_user_stats") }, + user_ids: [@acting_user.id] + end # topics_entered DB.exec(<<~SQL, target_user_id: @target_user.id) @@ -212,7 +248,11 @@ class UserMerger end def merge_user_attributes - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.merging_user_attributes") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.merging_user_attributes") }, + user_ids: [@acting_user.id] + end DB.exec(<<~SQL, source_user_id: @source_user.id, target_user_id: @target_user.id) UPDATE users AS t @@ -255,7 +295,11 @@ class UserMerger end def update_user_ids - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.updating_user_ids") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.updating_user_ids") }, + user_ids: [@acting_user.id] + end Category.where(user_id: @source_user.id).update_all(user_id: @target_user.id) @@ -278,23 +322,44 @@ class UserMerger IncomingEmail.where(user_id: @source_user.id).update_all(user_id: @target_user.id) IncomingLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id) - IncomingLink.where(current_user_id: @source_user.id).update_all(current_user_id: @target_user.id) + IncomingLink.where(current_user_id: @source_user.id).update_all( + current_user_id: @target_user.id, + ) InvitedUser.where(user_id: @source_user.id).update_all(user_id: @target_user.id) - Invite.with_deleted.where(invited_by_id: @source_user.id).update_all(invited_by_id: @target_user.id) - Invite.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + Invite + .with_deleted + .where(invited_by_id: @source_user.id) + .update_all(invited_by_id: @target_user.id) + Invite + .with_deleted + .where(deleted_by_id: @source_user.id) + .update_all(deleted_by_id: @target_user.id) update_user_id(:muted_users, conditions: "x.muted_user_id = y.muted_user_id") - update_user_id(:muted_users, user_id_column_name: "muted_user_id", conditions: "x.user_id = y.user_id") + update_user_id( + :muted_users, + user_id_column_name: "muted_user_id", + conditions: "x.user_id = y.user_id", + ) update_user_id(:ignored_users, conditions: "x.ignored_user_id = y.ignored_user_id") - update_user_id(:ignored_users, user_id_column_name: "ignored_user_id", conditions: "x.user_id = y.user_id") + update_user_id( + :ignored_users, + user_id_column_name: "ignored_user_id", + conditions: "x.user_id = y.user_id", + ) Notification.where(user_id: @source_user.id).update_all(user_id: @target_user.id) - update_user_id(:post_actions, conditions: ["x.post_id = y.post_id", - "x.post_action_type_id = y.post_action_type_id", - "x.targets_topic = y.targets_topic"]) + update_user_id( + :post_actions, + conditions: [ + "x.post_id = y.post_id", + "x.post_action_type_id = y.post_action_type_id", + "x.targets_topic = y.targets_topic", + ], + ) PostAction.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) PostAction.where(deferred_by_id: @source_user.id).update_all(deferred_by_id: @target_user.id) @@ -303,13 +368,24 @@ class UserMerger PostRevision.where(user_id: @source_user.id).update_all(user_id: @target_user.id) - Post.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) - Post.with_deleted.where(last_editor_id: @source_user.id).update_all(last_editor_id: @target_user.id) + Post + .with_deleted + .where(deleted_by_id: @source_user.id) + .update_all(deleted_by_id: @target_user.id) + Post + .with_deleted + .where(last_editor_id: @source_user.id) + .update_all(last_editor_id: @target_user.id) Post.with_deleted.where(locked_by_id: @source_user.id).update_all(locked_by_id: @target_user.id) - Post.with_deleted.where(reply_to_user_id: @source_user.id).update_all(reply_to_user_id: @target_user.id) + Post + .with_deleted + .where(reply_to_user_id: @source_user.id) + .update_all(reply_to_user_id: @target_user.id) Reviewable.where(created_by_id: @source_user.id).update_all(created_by_id: @target_user.id) - ReviewableHistory.where(created_by_id: @source_user.id).update_all(created_by_id: @target_user.id) + ReviewableHistory.where(created_by_id: @source_user.id).update_all( + created_by_id: @target_user.id, + ) SearchLog.where(user_id: @source_user.id).update_all(user_id: @target_user.id) @@ -319,22 +395,36 @@ class UserMerger update_user_id(:topic_allowed_users, conditions: "x.topic_id = y.topic_id") - TopicEmbed.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + TopicEmbed + .with_deleted + .where(deleted_by_id: @source_user.id) + .update_all(deleted_by_id: @target_user.id) TopicLink.where(user_id: @source_user.id).update_all(user_id: @target_user.id) TopicLinkClick.where(user_id: @source_user.id).update_all(user_id: @target_user.id) - TopicTimer.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + TopicTimer + .with_deleted + .where(deleted_by_id: @source_user.id) + .update_all(deleted_by_id: @target_user.id) - update_user_id(:topic_timers, conditions: ["x.status_type = y.status_type", - "x.topic_id = y.topic_id", - "y.deleted_at IS NULL"]) + update_user_id( + :topic_timers, + conditions: [ + "x.status_type = y.status_type", + "x.topic_id = y.topic_id", + "y.deleted_at IS NULL", + ], + ) update_user_id(:topic_users, conditions: "x.topic_id = y.topic_id") update_user_id(:topic_views, conditions: "x.topic_id = y.topic_id") - Topic.with_deleted.where(deleted_by_id: @source_user.id).update_all(deleted_by_id: @target_user.id) + Topic + .with_deleted + .where(deleted_by_id: @source_user.id) + .update_all(deleted_by_id: @target_user.id) UnsubscribeKey.where(user_id: @source_user.id).update_all(user_id: @target_user.id) @@ -342,29 +432,46 @@ class UserMerger update_user_id(:user_archived_messages, conditions: "x.topic_id = y.topic_id") - update_user_id(:user_actions, - user_id_column_name: "user_id", - conditions: ["x.action_type = y.action_type", - "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", - "x.target_post_id IS NOT DISTINCT FROM y.target_post_id", - "(x.acting_user_id IN (:source_user_id, :target_user_id) OR x.acting_user_id IS NOT DISTINCT FROM y.acting_user_id)"]) - update_user_id(:user_actions, - user_id_column_name: "acting_user_id", - conditions: ["x.action_type = y.action_type", - "x.user_id = y.user_id", - "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", - "x.target_post_id IS NOT DISTINCT FROM y.target_post_id"]) + update_user_id( + :user_actions, + user_id_column_name: "user_id", + conditions: [ + "x.action_type = y.action_type", + "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", + "x.target_post_id IS NOT DISTINCT FROM y.target_post_id", + "(x.acting_user_id IN (:source_user_id, :target_user_id) OR x.acting_user_id IS NOT DISTINCT FROM y.acting_user_id)", + ], + ) + update_user_id( + :user_actions, + user_id_column_name: "acting_user_id", + conditions: [ + "x.action_type = y.action_type", + "x.user_id = y.user_id", + "x.target_topic_id IS NOT DISTINCT FROM y.target_topic_id", + "x.target_post_id IS NOT DISTINCT FROM y.target_post_id", + ], + ) - update_user_id(:user_badges, conditions: ["x.badge_id = y.badge_id", - "x.seq = y.seq", - "x.post_id IS NOT DISTINCT FROM y.post_id"]) + update_user_id( + :user_badges, + conditions: [ + "x.badge_id = y.badge_id", + "x.seq = y.seq", + "x.post_id IS NOT DISTINCT FROM y.post_id", + ], + ) UserBadge.where(granted_by_id: @source_user.id).update_all(granted_by_id: @target_user.id) update_user_id(:user_custom_fields, conditions: "x.name = y.name") if @target_user.human? - update_user_id(:user_emails, conditions: "x.email = y.email OR y.primary = false", updates: '"primary" = false') + update_user_id( + :user_emails, + conditions: "x.email = y.email OR y.primary = false", + updates: '"primary" = false', + ) end UserExport.where(user_id: @source_user.id).update_all(user_id: @target_user.id) @@ -372,7 +479,9 @@ class UserMerger UserHistory.where(target_user_id: @source_user.id).update_all(target_user_id: @target_user.id) UserHistory.where(acting_user_id: @source_user.id).update_all(acting_user_id: @target_user.id) - UserProfileView.where(user_profile_id: @source_user.id).update_all(user_profile_id: @target_user.id) + UserProfileView.where(user_profile_id: @source_user.id).update_all( + user_profile_id: @target_user.id, + ) UserProfileView.where(user_id: @source_user.id).update_all(user_id: @target_user.id) UserWarning.where(user_id: @source_user.id).update_all(user_id: @target_user.id) @@ -382,14 +491,18 @@ class UserMerger end def delete_source_user - ::MessageBus.publish '/merge_user', { message: I18n.t("admin.user.merge_user.deleting_source_user") }, user_ids: [@acting_user.id] if @acting_user + if @acting_user + ::MessageBus.publish "/merge_user", + { message: I18n.t("admin.user.merge_user.deleting_source_user") }, + user_ids: [@acting_user.id] + end @source_user.reload @source_user.skip_email_validation = true @source_user.update( admin: false, - email: "#{@source_user.username}_#{SecureRandom.hex}@no-email.invalid" + email: "#{@source_user.username}_#{SecureRandom.hex}@no-email.invalid", ) UserDestroyer.new(Discourse.system_user).destroy(@source_user, quiet: true) diff --git a/app/services/user_notification_renderer.rb b/app/services/user_notification_renderer.rb index e66b7d9decd..673a53dec20 100644 --- a/app/services/user_notification_renderer.rb +++ b/app/services/user_notification_renderer.rb @@ -9,9 +9,10 @@ class UserNotificationRenderer < ActionView::Base def self.render(*args) LOCK.synchronize do - @instance ||= UserNotificationRenderer.with_empty_template_cache.with_view_paths( - Rails.configuration.paths["app/views"] - ) + @instance ||= + UserNotificationRenderer.with_empty_template_cache.with_view_paths( + Rails.configuration.paths["app/views"], + ) @instance.render(*args) end end diff --git a/app/services/user_notification_schedule_processor.rb b/app/services/user_notification_schedule_processor.rb index 7b924b22d43..6a340ca14ed 100644 --- a/app/services/user_notification_schedule_processor.rb +++ b/app/services/user_notification_schedule_processor.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserNotificationScheduleProcessor - attr_accessor :schedule, :user, :timezone_name def initialize(schedule) @@ -38,10 +37,11 @@ class UserNotificationScheduleProcessor user.do_not_disturb_timings.find_or_create_by(previous_timing.attributes.except("id")) end - next_timing = user.do_not_disturb_timings.new( - starts_at: utc_time_at_minute(local_time, end_minute), - scheduled: true - ) + next_timing = + user.do_not_disturb_timings.new( + starts_at: utc_time_at_minute(local_time, end_minute), + scheduled: true, + ) save_timing_and_continue(local_time, next_timing, days) else save_timing_and_continue(local_time, previous_timing, days) @@ -53,14 +53,13 @@ class UserNotificationScheduleProcessor def find_previous_timing(local_time) # Try and find a previously scheduled dnd timing that we can extend if the # ends_at is at the previous midnight. fallback to a new timing if not. - previous = user.do_not_disturb_timings.find_by( - ends_at: (local_time - 1.day).end_of_day.utc, - scheduled: true - ) - previous || user.do_not_disturb_timings.new( - starts_at: local_time.beginning_of_day.utc, - scheduled: true - ) + previous = + user.do_not_disturb_timings.find_by( + ends_at: (local_time - 1.day).end_of_day.utc, + scheduled: true, + ) + previous || + user.do_not_disturb_timings.new(starts_at: local_time.beginning_of_day.utc, scheduled: true) end def save_timing_and_continue(local_time, timing, days) @@ -78,7 +77,15 @@ class UserNotificationScheduleProcessor def utc_time_at_minute(base_time, total_minutes) hour = total_minutes / 60 minute = total_minutes % 60 - Time.new(base_time.year, base_time.month, base_time.day, hour, minute, 0, base_time.formatted_offset).utc + Time.new( + base_time.year, + base_time.month, + base_time.day, + hour, + minute, + 0, + base_time.formatted_offset, + ).utc end def transform_wday(wday) diff --git a/app/services/user_silencer.rb b/app/services/user_silencer.rb index 10c3b1c74a4..50ae7b4ab72 100644 --- a/app/services/user_silencer.rb +++ b/app/services/user_silencer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UserSilencer - attr_reader :user_history def initialize(user, by_user = nil, opts = {}) @@ -29,11 +28,7 @@ class UserSilencer if @user.save message_type = @opts[:message] || :silenced_by_staff - details = StaffMessageFormat.new( - :silence, - @opts[:reason], - @opts[:message_body] - ).format + details = StaffMessageFormat.new(:silence, @opts[:reason], @opts[:message_body]).format context = "#{message_type}: #{@opts[:reason]}" @@ -41,10 +36,7 @@ class UserSilencer log_params = { context: context, details: details } log_params[:post_id] = @opts[:post_id].to_i if @opts[:post_id] - @user_history = StaffActionLogger.new(@by_user).log_silence_user( - @user, - log_params - ) + @user_history = StaffActionLogger.new(@by_user).log_silence_user(@user, log_params) end silence_message_params = {} @@ -58,7 +50,7 @@ class UserSilencer post_id: @opts[:post_id], silenced_till: @user.silenced_till, silenced_at: DateTime.now, - silence_message_params: silence_message_params + silence_message_params: silence_message_params, ) silence_message_params.merge!(post_alert_options: { skip_send_email: true }) @@ -73,8 +65,20 @@ class UserSilencer def hide_posts return unless @user.trust_level == TrustLevel[0] - Post.where(user_id: @user.id).where("created_at > ?", 24.hours.ago).update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", Post.hidden_reasons[:new_user_spam_threshold_reached]]) - topic_ids = Post.where(user_id: @user.id, post_number: 1).where("created_at > ?", 24.hours.ago).pluck(:topic_id) + Post + .where(user_id: @user.id) + .where("created_at > ?", 24.hours.ago) + .update_all( + [ + "hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", + Post.hidden_reasons[:new_user_spam_threshold_reached], + ], + ) + topic_ids = + Post + .where(user_id: @user.id, post_number: 1) + .where("created_at > ?", 24.hours.ago) + .pluck(:topic_id) Topic.where(id: topic_ids).update_all(visible: false) unless topic_ids.empty? end @@ -86,5 +90,4 @@ class UserSilencer StaffActionLogger.new(@by_user).log_unsilence_user(@user) if @by_user end end - end diff --git a/app/services/user_stat_count_updater.rb b/app/services/user_stat_count_updater.rb index dac3e086b40..ed55e620acd 100644 --- a/app/services/user_stat_count_updater.rb +++ b/app/services/user_stat_count_updater.rb @@ -12,11 +12,11 @@ class UserStatCountUpdater def set!(user_stat:, count:, count_column:) return if user_stat.blank? - return if ![:post_count, :topic_count].include?(count_column) + return if !%i[post_count topic_count].include?(count_column) if SiteSetting.verbose_user_stat_count_logging && count < 0 Rails.logger.warn( - "Attempted to insert negative count into UserStat##{count_column} for user #{user_stat.user_id}, using 0 instead. Caller:\n #{caller[0..10].join("\n")}" + "Attempted to insert negative count into UserStat##{count_column} for user #{user_stat.user_id}, using 0 instead. Caller:\n #{caller[0..10].join("\n")}", ) end @@ -47,7 +47,7 @@ class UserStatCountUpdater if action == :decrement! && stat.public_send(column) < 1 if SiteSetting.verbose_user_stat_count_logging Rails.logger.warn( - "Attempted to insert negative count into UserStat##{column} for post with id '#{post.id}'. Caller:\n #{caller[0..10].join("\n")}" + "Attempted to insert negative count into UserStat##{column} for post with id '#{post.id}'. Caller:\n #{caller[0..10].join("\n")}", ) end diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb index d537b7d0e99..511755628f4 100644 --- a/app/services/user_updater.rb +++ b/app/services/user_updater.rb @@ -1,66 +1,65 @@ # frozen_string_literal: true class UserUpdater - CATEGORY_IDS = { watched_first_post_category_ids: :watching_first_post, watched_category_ids: :watching, tracked_category_ids: :tracking, regular_category_ids: :regular, - muted_category_ids: :muted + muted_category_ids: :muted, } TAG_NAMES = { watching_first_post_tags: :watching_first_post, watched_tags: :watching, tracked_tags: :tracking, - muted_tags: :muted + muted_tags: :muted, } - OPTION_ATTR = [ - :mailing_list_mode, - :mailing_list_mode_frequency, - :email_digests, - :email_level, - :email_messages_level, - :external_links_in_new_tab, - :enable_quoting, - :enable_defer, - :color_scheme_id, - :dark_scheme_id, - :dynamic_favicon, - :automatically_unpin_topics, - :digest_after_minutes, - :new_topic_duration_minutes, - :auto_track_topics_after_msecs, - :notification_level_when_replying, - :email_previous_replies, - :email_in_reply_to, - :like_notification_frequency, - :include_tl0_in_digests, - :theme_ids, - :allow_private_messages, - :enable_allowed_pm_users, - :homepage_id, - :hide_profile_and_presence, - :text_size, - :title_count_mode, - :timezone, - :skip_new_user_tips, - :seen_popups, - :default_calendar, - :sidebar_list_destination, - :bookmark_auto_delete_preference + OPTION_ATTR = %i[ + mailing_list_mode + mailing_list_mode_frequency + email_digests + email_level + email_messages_level + external_links_in_new_tab + enable_quoting + enable_defer + color_scheme_id + dark_scheme_id + dynamic_favicon + automatically_unpin_topics + digest_after_minutes + new_topic_duration_minutes + auto_track_topics_after_msecs + notification_level_when_replying + email_previous_replies + email_in_reply_to + like_notification_frequency + include_tl0_in_digests + theme_ids + allow_private_messages + enable_allowed_pm_users + homepage_id + hide_profile_and_presence + text_size + title_count_mode + timezone + skip_new_user_tips + seen_popups + default_calendar + sidebar_list_destination + bookmark_auto_delete_preference ] - NOTIFICATION_SCHEDULE_ATTRS = -> { + NOTIFICATION_SCHEDULE_ATTRS = -> do attrs = [:enabled] 7.times do |n| attrs.push("day_#{n}_start_time".to_sym) attrs.push("day_#{n}_end_time".to_sym) end { user_notification_schedule: attrs } - }.call + end.call def initialize(actor, user) @user = user @@ -70,7 +69,9 @@ class UserUpdater def update(attributes = {}) user_profile = user.user_profile - user_profile.dismissed_banner_key = attributes[:dismissed_banner_key] if attributes[:dismissed_banner_key].present? + user_profile.dismissed_banner_key = attributes[:dismissed_banner_key] if attributes[ + :dismissed_banner_key + ].present? unless SiteSetting.enable_discourse_connect && SiteSetting.discourse_connect_overrides_bio user_profile.bio_raw = attributes.fetch(:bio_raw) { user_profile.bio_raw } end @@ -83,56 +84,52 @@ class UserUpdater user_profile.website = format_url(attributes.fetch(:website) { user_profile.website }) end - if attributes[:profile_background_upload_url] == "" || !guardian.can_upload_profile_header?(user) + if attributes[:profile_background_upload_url] == "" || + !guardian.can_upload_profile_header?(user) user_profile.profile_background_upload_id = nil elsif upload = Upload.get_from_url(attributes[:profile_background_upload_url]) user_profile.profile_background_upload_id = upload.id end - if attributes[:card_background_upload_url] == "" || !guardian.can_upload_user_card_background?(user) + if attributes[:card_background_upload_url] == "" || + !guardian.can_upload_user_card_background?(user) user_profile.card_background_upload_id = nil elsif upload = Upload.get_from_url(attributes[:card_background_upload_url]) user_profile.card_background_upload_id = upload.id end if attributes[:user_notification_schedule] - user_notification_schedule = user.user_notification_schedule || UserNotificationSchedule.new(user: user) + user_notification_schedule = + user.user_notification_schedule || UserNotificationSchedule.new(user: user) user_notification_schedule.assign_attributes(attributes[:user_notification_schedule]) end old_user_name = user.name.present? ? user.name : "" - if guardian.can_edit_name?(user) - user.name = attributes.fetch(:name) { user.name } - end + user.name = attributes.fetch(:name) { user.name } if guardian.can_edit_name?(user) user.locale = attributes.fetch(:locale) { user.locale } user.date_of_birth = attributes.fetch(:date_of_birth) { user.date_of_birth } - if attributes[:title] && - attributes[:title] != user.title && - guardian.can_grant_title?(user, attributes[:title]) + if attributes[:title] && attributes[:title] != user.title && + guardian.can_grant_title?(user, attributes[:title]) user.title = attributes[:title] end - if SiteSetting.user_selected_primary_groups && - attributes[:primary_group_id] && - attributes[:primary_group_id] != user.primary_group_id && - guardian.can_use_primary_group?(user, attributes[:primary_group_id]) - + if SiteSetting.user_selected_primary_groups && attributes[:primary_group_id] && + attributes[:primary_group_id] != user.primary_group_id && + guardian.can_use_primary_group?(user, attributes[:primary_group_id]) user.primary_group_id = attributes[:primary_group_id] - elsif SiteSetting.user_selected_primary_groups && - attributes[:primary_group_id] && - attributes[:primary_group_id].blank? - + elsif SiteSetting.user_selected_primary_groups && attributes[:primary_group_id] && + attributes[:primary_group_id].blank? user.primary_group_id = nil end - if attributes[:flair_group_id] && - attributes[:flair_group_id] != user.flair_group_id && - (attributes[:flair_group_id].blank? || - guardian.can_use_flair_group?(user, attributes[:flair_group_id])) - + if attributes[:flair_group_id] && attributes[:flair_group_id] != user.flair_group_id && + ( + attributes[:flair_group_id].blank? || + guardian.can_use_flair_group?(user, attributes[:flair_group_id]) + ) user.flair_group_id = attributes[:flair_group_id] end @@ -145,7 +142,7 @@ class UserUpdater TAG_NAMES.each do |attribute, level| if attributes.has_key?(attribute) - TagUser.batch_set(user, level, attributes[attribute]&.split(',') || []) + TagUser.batch_set(user, level, attributes[attribute]&.split(",") || []) end end end @@ -165,7 +162,8 @@ class UserUpdater end if attributes.key?(:text_size) - user.user_option.text_size_seq += 1 if user.user_option.text_size.to_s != attributes[:text_size] + user.user_option.text_size_seq += 1 if user.user_option.text_size.to_s != + attributes[:text_size] end OPTION_ATTR.each do |attribute| @@ -173,7 +171,7 @@ class UserUpdater save_options = true if [true, false].include?(user.user_option.public_send(attribute)) - val = attributes[attribute].to_s == 'true' + val = attributes[attribute].to_s == "true" user.user_option.public_send("#{attribute}=", val) else user.user_option.public_send("#{attribute}=", attributes[attribute]) @@ -189,16 +187,12 @@ class UserUpdater user.user_option.email_digests = false if user.user_option.mailing_list_mode fields = attributes[:custom_fields] - if fields.present? - user.custom_fields = user.custom_fields.merge(fields) - end + user.custom_fields = user.custom_fields.merge(fields) if fields.present? saved = nil User.transaction do - if attributes.key?(:muted_usernames) - update_muted_users(attributes[:muted_usernames]) - end + update_muted_users(attributes[:muted_usernames]) if attributes.key?(:muted_usernames) if attributes.key?(:allowed_pm_usernames) update_allowed_pm_users(attributes[:allowed_pm_usernames]) @@ -213,11 +207,17 @@ class UserUpdater end if attributes.key?(:sidebar_category_ids) - SidebarSectionLinksUpdater.update_category_section_links(user, category_ids: attributes[:sidebar_category_ids]) + SidebarSectionLinksUpdater.update_category_section_links( + user, + category_ids: attributes[:sidebar_category_ids], + ) end if attributes.key?(:sidebar_tag_names) && SiteSetting.tagging_enabled - SidebarSectionLinksUpdater.update_tag_section_links(user, tag_names: attributes[:sidebar_tag_names]) + SidebarSectionLinksUpdater.update_tag_section_links( + user, + tag_names: attributes[:sidebar_tag_names], + ) end if SiteSetting.enable_user_status? @@ -225,17 +225,16 @@ class UserUpdater end name_changed = user.name_changed? - saved = (!save_options || user.user_option.save) && - (user_notification_schedule.nil? || user_notification_schedule.save) && - user_profile.save && - user.save + saved = + (!save_options || user.user_option.save) && + (user_notification_schedule.nil? || user_notification_schedule.save) && + user_profile.save && user.save if saved && (name_changed && old_user_name.casecmp(attributes.fetch(:name)) != 0) - StaffActionLogger.new(@actor).log_name_change( user.id, old_user_name, - attributes.fetch(:name) { '' } + attributes.fetch(:name) { "" }, ) end rescue Addressable::URI::InvalidURIError => e @@ -245,16 +244,14 @@ class UserUpdater if saved if user_notification_schedule - user_notification_schedule.enabled ? - user_notification_schedule.create_do_not_disturb_timings(delete_existing: true) : + if user_notification_schedule.enabled + user_notification_schedule.create_do_not_disturb_timings(delete_existing: true) + else user_notification_schedule.destroy_scheduled_timings + end end if attributes.key?(:seen_popups) || attributes.key?(:skip_new_user_tips) - MessageBus.publish( - '/user-tips', - user.user_option.seen_popups, - user_ids: [user.id] - ) + MessageBus.publish("/user-tips", user.user_option.seen_popups, user_ids: [user.id]) end DiscourseEvent.trigger(:user_updated, user) end @@ -269,7 +266,7 @@ class UserUpdater if desired_ids.empty? MutedUser.where(user_id: user.id).destroy_all else - MutedUser.where('user_id = ? AND muted_user_id not in (?)', user.id, desired_ids).destroy_all + MutedUser.where("user_id = ? AND muted_user_id not in (?)", user.id, desired_ids).destroy_all # SQL is easier here than figuring out how to do the same in AR DB.exec(<<~SQL, now: Time.now, user_id: user.id, desired_ids: desired_ids) @@ -290,7 +287,11 @@ class UserUpdater if desired_ids.empty? AllowedPmUser.where(user_id: user.id).destroy_all else - AllowedPmUser.where('user_id = ? AND allowed_pm_user_id not in (?)', user.id, desired_ids).destroy_all + AllowedPmUser.where( + "user_id = ? AND allowed_pm_user_id not in (?)", + user.id, + desired_ids, + ).destroy_all # SQL is easier here than figuring out how to do the same in AR DB.exec(<<~SQL, now: Time.zone.now, user_id: user.id, desired_ids: desired_ids) @@ -305,7 +306,11 @@ class UserUpdater def updated_associated_accounts(associations) associations.each do |association| - user_associated_account = UserAssociatedAccount.find_or_initialize_by(user_id: user.id, provider_name: association[:provider_name]) + user_associated_account = + UserAssociatedAccount.find_or_initialize_by( + user_id: user.id, + provider_name: association[:provider_name], + ) if association[:provider_uid].present? user_associated_account.update!(provider_uid: association[:provider_uid]) else @@ -329,7 +334,10 @@ class UserUpdater sso = SingleSignOnRecord.find_or_initialize_by(user_id: user.id) if external_id.present? - sso.update!(external_id: discourse_connect[:external_id], last_payload: "external_id=#{discourse_connect[:external_id]}") + sso.update!( + external_id: discourse_connect[:external_id], + last_payload: "external_id=#{discourse_connect[:external_id]}", + ) else sso.destroy! end diff --git a/app/services/username_changer.rb b/app/services/username_changer.rb index bb2f7becc06..10f7d66c22c 100644 --- a/app/services/username_changer.rb +++ b/app/services/username_changer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true class UsernameChanger - def initialize(user, new_username, actor = nil) @user = user @old_username = user.username @@ -37,23 +36,33 @@ class UsernameChanger StaffActionLogger.new(@actor).log_username_change(@user, @old_username, @new_username) end - UsernameChanger.update_username(user_id: @user.id, - old_username: @old_username, - new_username: @new_username, - avatar_template: @user.avatar_template_url, - asynchronous: asynchronous) if run_update_job + if run_update_job + UsernameChanger.update_username( + user_id: @user.id, + old_username: @old_username, + new_username: @new_username, + avatar_template: @user.avatar_template_url, + asynchronous: asynchronous, + ) + end return true end false end - def self.update_username(user_id:, old_username:, new_username:, avatar_template:, asynchronous: true) + def self.update_username( + user_id:, + old_username:, + new_username:, + avatar_template:, + asynchronous: true + ) args = { user_id: user_id, old_username: old_username, new_username: new_username, - avatar_template: avatar_template + avatar_template: avatar_template, } if asynchronous diff --git a/app/services/username_checker_service.rb b/app/services/username_checker_service.rb index e724e2bca20..3540d007150 100644 --- a/app/services/username_checker_service.rb +++ b/app/services/username_checker_service.rb @@ -17,11 +17,8 @@ class UsernameCheckerService end def check_username_availability(username, email) - available = User.username_available?( - username, - email, - allow_reserved_username: @allow_reserved_username - ) + available = + User.username_available?(username, email, allow_reserved_username: @allow_reserved_username) if available { available: true, is_developer: is_developer?(email) } @@ -31,11 +28,11 @@ class UsernameCheckerService end def is_developer?(value) - Rails.configuration.respond_to?(:developer_emails) && Rails.configuration.developer_emails.include?(value) + Rails.configuration.respond_to?(:developer_emails) && + Rails.configuration.developer_emails.include?(value) end def self.is_developer?(email) UsernameCheckerService.new.is_developer?(email) end - end diff --git a/app/services/web_hook_emitter.rb b/app/services/web_hook_emitter.rb index d8ac652e1c8..738026e48d5 100644 --- a/app/services/web_hook_emitter.rb +++ b/app/services/web_hook_emitter.rb @@ -15,17 +15,13 @@ class WebHookEmitter request: { write_timeout: REQUEST_TIMEOUT, read_timeout: REQUEST_TIMEOUT, - open_timeout: REQUEST_TIMEOUT + open_timeout: REQUEST_TIMEOUT, }, } - if !@webhook.verify_certificate - connection_opts[:ssl] = { verify: false } - end + connection_opts[:ssl] = { verify: false } if !@webhook.verify_certificate - conn = Faraday.new(nil, connection_opts) do |f| - f.adapter FinalDestination::FaradayAdapter - end + conn = Faraday.new(nil, connection_opts) { |f| f.adapter FinalDestination::FaradayAdapter } start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) error = nil @@ -36,17 +32,15 @@ class WebHookEmitter error = e end duration = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start - event_update_args = { - headers: MultiJson.dump(headers), - duration: duration, - } + event_update_args = { headers: MultiJson.dump(headers), duration: duration } if response event_update_args[:response_headers] = MultiJson.dump(response.headers) event_update_args[:response_body] = response.body event_update_args[:status] = response.status else event_update_args[:status] = -1 - if error.is_a?(Faraday::Error) && error.wrapped_exception.is_a?(FinalDestination::SSRFDetector::DisallowedIpError) + if error.is_a?(Faraday::Error) && + error.wrapped_exception.is_a?(FinalDestination::SSRFDetector::DisallowedIpError) error = I18n.t("webhooks.payload_url.blocked_or_internal") end event_update_args[:response_headers] = MultiJson.dump(error: error) diff --git a/app/services/wildcard_domain_checker.rb b/app/services/wildcard_domain_checker.rb index 89c64538821..2b2e983a48b 100644 --- a/app/services/wildcard_domain_checker.rb +++ b/app/services/wildcard_domain_checker.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true module WildcardDomainChecker - def self.check_domain(domain, external_domain) - escaped_domain = domain[0] == "*" ? Regexp.escape(domain).sub("\\*", '\S*') : Regexp.escape(domain) - domain_regex = Regexp.new("\\A#{escaped_domain}\\z", 'i') + escaped_domain = + domain[0] == "*" ? Regexp.escape(domain).sub("\\*", '\S*') : Regexp.escape(domain) + domain_regex = Regexp.new("\\A#{escaped_domain}\\z", "i") external_domain.match(domain_regex) end - end diff --git a/app/services/wildcard_url_checker.rb b/app/services/wildcard_url_checker.rb index f90defe15d0..5f9621c27b5 100644 --- a/app/services/wildcard_url_checker.rb +++ b/app/services/wildcard_url_checker.rb @@ -5,7 +5,7 @@ module WildcardUrlChecker return false if !valid_url?(url_to_check) escaped_url = Regexp.escape(url).sub("\\*", '\S*') - url_regex = Regexp.new("\\A#{escaped_url}\\z", 'i') + url_regex = Regexp.new("\\A#{escaped_url}\\z", "i") url_to_check.match?(url_regex) end diff --git a/app/services/word_watcher.rb b/app/services/word_watcher.rb index 3967457e38c..90529705c43 100644 --- a/app/services/word_watcher.rb +++ b/app/services/word_watcher.rb @@ -34,17 +34,18 @@ class WordWatcher def self.get_cached_words(action) if cache_enabled? - Discourse.cache.fetch(word_matcher_regexp_key(action), expires_in: 1.day) do - words_for_action(action).presence - end + Discourse + .cache + .fetch(word_matcher_regexp_key(action), expires_in: 1.day) do + words_for_action(action).presence + end else words_for_action(action).presence end end def self.serializable_word_matcher_regexp(action) - word_matcher_regexp_list(action) - .map { |r| { r.source => { case_sensitive: !r.casefold? } } } + word_matcher_regexp_list(action).map { |r| { r.source => { case_sensitive: !r.casefold? } } } end # This regexp is run in miniracer, and the client JS app @@ -64,9 +65,7 @@ class WordWatcher grouped_words[group_key] << word end - regexps = grouped_words - .select { |_, w| w.present? } - .transform_values { |w| w.join("|") } + regexps = grouped_words.select { |_, w| w.present? }.transform_values { |w| w.join("|") } if !SiteSetting.watched_words_regular_expressions? regexps.transform_values! do |regexp| @@ -75,8 +74,7 @@ class WordWatcher end end - regexps - .map { |c, regexp| Regexp.new(regexp, c == :case_sensitive ? nil : Regexp::IGNORECASE) } + regexps.map { |c, regexp| Regexp.new(regexp, c == :case_sensitive ? nil : Regexp::IGNORECASE) } rescue RegexpError raise if raise_errors [] # Admin will be alerted via admin_dashboard_data.rb @@ -113,7 +111,7 @@ class WordWatcher regexps = word_matcher_regexp_list(:censor) return html if regexps.blank? - doc = Nokogiri::HTML5::fragment(html) + doc = Nokogiri::HTML5.fragment(html) doc.traverse do |node| regexps.each do |regexp| node.content = censor_text_with_regexp(node.content, regexp) if node.text? @@ -150,9 +148,7 @@ class WordWatcher end def self.clear_cache! - WatchedWord.actions.each do |a, i| - Discourse.cache.delete word_matcher_regexp_key(a) - end + WatchedWord.actions.each { |a, i| Discourse.cache.delete word_matcher_regexp_key(a) } end def requires_approval? @@ -188,13 +184,15 @@ class WordWatcher if SiteSetting.watched_words_regular_expressions? set = Set.new - @raw.scan(regexp).each do |m| - if Array === m - set.add(m.find(&:present?)) - elsif String === m - set.add(m) + @raw + .scan(regexp) + .each do |m| + if Array === m + set.add(m.find(&:present?)) + elsif String === m + set.add(m) + end end - end matches = set.to_a else @@ -214,9 +212,10 @@ class WordWatcher end def word_matches?(word, case_sensitive: false) - Regexp - .new(WordWatcher.word_to_regexp(word, whole: true), case_sensitive ? nil : Regexp::IGNORECASE) - .match?(@raw) + Regexp.new( + WordWatcher.word_to_regexp(word, whole: true), + case_sensitive ? nil : Regexp::IGNORECASE, + ).match?(@raw) end def self.replace_text_with_regexp(text, regexp, replacement)