DEV: Apply syntax_tree formatting to `app/*`

This commit is contained in:
David Taylor 2023-01-09 12:20:10 +00:00
parent a641ce4b62
commit 5a003715d3
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
696 changed files with 18447 additions and 15481 deletions

View File

@ -1,3 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma,disable_ternary
--ignore-files=app/*

View File

@ -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

View File

@ -7,5 +7,4 @@ class Admin::AdminController < ApplicationController
def index
render body: nil
end
end

View File

@ -8,7 +8,8 @@ class Admin::ApiController < Admin::AdminController
offset = (params[:offset] || 0).to_i
limit = (params[:limit] || 50).to_i.clamp(1, 50)
keys = ApiKey
keys =
ApiKey
.where(hidden: false)
.includes(:user, :api_key_scopes)
# Sort revoked keys by revoked_at and active keys by created_at
@ -16,28 +17,27 @@ class Admin::ApiController < Admin::AdminController
.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)|
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('_', ' '),
name: k.to_s.gsub("_", " "),
params: v[:params],
urls: v[:urls]
urls: v[:urls],
}
end
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,9 +121,11 @@ class Admin::ApiController < Admin::AdminController
def build_params(scope_params, params)
return if params.nil?
scope_params.slice(*params).tap do |allowed_params|
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(',')
v.blank? ? allowed_params.delete(k) : allowed_params[k] = v.split(",")
end
end
end
@ -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

View File

@ -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"))

View File

@ -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)
badges:
Badge
.includes(:badge_grouping)
.includes(:badge_type, :image_upload)
.references(:badge_grouping)
.order('badge_groupings.position, badge_type_id, badges.name').to_a,
.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],
render json:
BadgeGranter.preview(
params[:sql],
target_posts: params[:target_posts] == "true",
explain: params[:explain] == "true",
trigger: params[:trigger].to_i)
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(
results =
BadgeGranter.enqueue_mass_grant_for_users(
badge,
emails: emails,
usernames: usernames,
ensure_users_have_badge_once: ensure_users_have_badge_once
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_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")
@ -174,20 +188,22 @@ class Admin::BadgesController < Admin::AdminController
errors = []
Badge.transaction do
allowed = Badge.column_names.map(&:to_sym)
allowed -= [:id, :created_at, :updated_at, :grant_count]
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

View File

@ -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

View File

@ -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

View File

@ -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)
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)
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

View File

@ -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)

View File

@ -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
@ -29,11 +31,19 @@ class Admin::EmbeddableHostsController < Admin::AdminController
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -2,22 +2,26 @@
class Admin::ReportsController < Admin::StaffController
def index
reports_methods = ['page_view_total_reqs'] +
ApplicationRequest.req_types.keys
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
description_link: description_link.presence ? description_link : nil,
}
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,10 +15,12 @@ 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, _|
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."
raise Discourse::InvalidParameters,
"You cannot change this site setting because it is deprecated, use #{new_name} instead."
end
break new_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,20 +65,38 @@ 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],
.where(
notification_level: TopicUser.notification_levels[:watching],
notifications_reason_id: TopicUser.notification_reasons[:auto_watch_category],
topics: { category_id: categories_to_unwatch })
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|
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 } }
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
@ -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|
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 } }
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,13 +171,24 @@ 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
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)
.where(
"c.id IN (?) AND cu.notification_level IS NULL",
new_category_ids - previous_category_ids,
)
.distinct
.pluck("users.id")
@ -152,8 +199,13 @@ 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
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")
@ -164,7 +216,11 @@ class Admin::SiteSettingsController < Admin::AdminController
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

View File

@ -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: {})

View File

@ -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 = +"<h2>#{CGI.escapeHTML(cur&.dig("name").to_s)}</h2><p></p>"
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

View File

@ -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,13 +136,14 @@ 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(
@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
update_components: update_components,
)
log_theme_change(nil, @theme)
render json: @theme, status: :created
@ -149,7 +151,8 @@ class Admin::ThemesController < Admin::AdminController
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],
@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]))
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,7 +275,7 @@ 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"
@ -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

View File

@ -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

View File

@ -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,7 +87,9 @@ class Admin::UsersController < Admin::StaffController
DB.exec(
sql,
UserHistory.actions.slice(
UserHistory
.actions
.slice(
:silence_user,
:suspend_user,
:unsilence_user,
@ -95,8 +97,9 @@ class Admin::UsersController < Admin::StaffController
:removed_silence_user,
:removed_unsilence_user,
:removed_suspend_user,
:removed_unsuspend_user
).merge(user_id: params[:user_id].to_i)
: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",
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')
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,11 +143,12 @@ class Admin::UsersController < Admin::StaffController
User.transaction do
user.save!
user_history = StaffActionLogger.new(current_user).log_user_suspend(
user_history =
StaffActionLogger.new(current_user).log_user_suspend(
user,
params[:reason],
message: message,
post_id: params[:post_id]
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,7 +322,8 @@ class Admin::UsersController < Admin::StaffController
def approve
guardian.ensure_can_approve!(@user)
reviewable = ReviewableUser.find_by(target: @user) ||
reviewable =
ReviewableUser.find_by(target: @user) ||
Jobs::CreateUserReviewable.new.execute(user_id: @user.id).reviewable
reviewable.perform(current_user, :approve_user)
@ -328,23 +331,29 @@ class Admin::UsersController < Admin::StaffController
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,9 +363,16 @@ class Admin::UsersController < Admin::StaffController
if @user.silenced?
silenced_record = @user.silenced_record
message = I18n.t("user.already_silenced",
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')
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,14 +386,15 @@ class Admin::UsersController < Admin::StaffController
user_history = nil
all_users.each do |user|
silencer = UserSilencer.new(
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]
post_id: params[:post_id],
)
if silencer.silence
@ -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
@ -454,14 +467,20 @@ class Admin::UsersController < Admin::StaffController
else
render json: {
deleted: false,
user: AdminDetailedUserSerializer.new(user, root: false).as_json
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
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,16 +600,17 @@ 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
@ -583,7 +618,7 @@ class Admin::UsersController < Admin::StaffController
current_user,
{ 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

View File

@ -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,11 +39,12 @@ 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(
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
case_sensitive: "true" == row[2]&.strip&.downcase,
)
if watched_word.valid?
StaffActionLogger.new(current_user).log_watched_words_creation(watched_word)
@ -50,7 +52,7 @@ class Admin::WatchedWordsController < Admin::StaffController
end
end
data = { url: '/ok' }
data = { url: "/ok" }
rescue => e
data = failed_json.merge(errors: [e.message])
end
@ -73,7 +75,7 @@ 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"
@ -85,7 +87,9 @@ class Admin::WatchedWordsController < Admin::StaffController
action = WatchedWord.actions[name]
raise Discourse::NotFound if !action
WatchedWord.where(action: action).find_each do |watched_word|
WatchedWord
.where(action: action)
.find_each do |watched_word|
watched_word.destroy!
StaffActionLogger.new(current_user).log_watched_words_deletion(watched_word)
end

View File

@ -1,13 +1,15 @@
# 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)
web_hooks =
WebHook
.limit(limit)
.offset(offset)
.includes(:web_hook_event_types)
.includes(:categories)
@ -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,
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: [])
category_ids: [],
)
end
def fetch_web_hook

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'current_user'
require "current_user"
class ApplicationController < ActionController::Base
include CurrentUser
@ -48,12 +48,13 @@ class ApplicationController < ActionController::Base
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,
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,10 +69,10 @@ 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? ||
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
@ -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,7 +147,7 @@ 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
@ -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
@ -472,7 +474,6 @@ class ApplicationController < ActionController::Base
if (preview_theme_id = request[:preview_theme_id]&.to_i) &&
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,8 +557,9 @@ 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
@ -570,12 +571,15 @@ class ApplicationController < ActionController::Base
result.find_by(find_opts)
end
elsif params[:external_id]
external_id = params[:external_id].chomp('.json')
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
UserAssociatedAccount.find_by(
provider_name: provider_name,
provider_uid: external_id,
)&.user
else
SingleSignOnRecord.find_by(external_id: external_id).try(:user)
end
@ -588,14 +592,16 @@ class ApplicationController < ActionController::Base
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 |= 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,10 +638,16 @@ class ApplicationController < ActionController::Base
end
def preload_current_user_data
store_preloaded("currentUser", MultiJson.dump(CurrentUserSerializer.new(current_user, scope: guardian, root: false)))
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
serializer =
ActiveModel::ArraySerializer.new(
report,
each_serializer: TopicTrackingStateSerializer,
scope: guardian,
)
store_preloaded("topicTrackingStates", MultiJson.dump(serializer))
end
@ -649,19 +659,17 @@ class ApplicationController < ActionController::Base
if @theme_id.present?
{
top: Theme.lookup_field(@theme_id, target, "after_header"),
footer: Theme.lookup_field(@theme_id, target, "footer")
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,14 +735,18 @@ 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|
error_target =
opts[:additional_errors].find do |o|
target = obj.public_send(o)
target && target.errors.present?
end
@ -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
@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)
@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
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,16 +1016,15 @@ 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|
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 =
RateLimiter.new(nil, key, 1, SiteSetting.slow_down_crawler_rate, error_code: key)
limiter.performed!
break
end
@ -1000,8 +1037,7 @@ 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? &&
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!"
@ -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?

View File

@ -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

View File

@ -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,7 +16,9 @@ class BadgesController < ApplicationController
if (params[:only_listable] == "true") || !request.xhr?
# NOTE: this is sorted client side if needed
badges = badges.includes(:badge_grouping)
badges =
badges
.includes(:badge_grouping)
.includes(:badge_type, :image_upload)
.where(enabled: true, listable: true)
end
@ -25,9 +27,18 @@ class BadgesController < ApplicationController
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

View File

@ -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(
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]
}
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

View File

@ -37,35 +37,34 @@ class BootstrapController < ApplicationController
assets_fake_request.env["QUERY_STRING"] = query
end
Discourse.find_plugin_css_assets(
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
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(
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) }
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')
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

View File

@ -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,9 +30,12 @@ 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" ||
include_subcategories =
SiteSetting.desktop_category_page_style == "subcategories_with_featured_topics" ||
params[:include_subcategories] == "true"
category_options = {
@ -32,7 +43,7 @@ class CategoriesController < ApplicationController
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,32 +257,38 @@ 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',
"not in group",
@category,
custom_message: 'not_in_group.title_category',
custom_message_params: { group: group.name },
group: group
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])
groups =
if !@category.groups.exists?(id: Group::AUTO_GROUPS[:everyone])
@category.groups.merge(Group.visible_groups(current_user)).pluck("name")
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,8 +326,9 @@ 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
@ -316,31 +340,35 @@ 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
@category_params ||=
begin
if p = params[:permissions]
p.each do |k, v|
p[k] = v.to_i
end
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]
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]
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(
result =
params.permit(
*required_param_keys,
:position,
:name,
@ -380,7 +408,7 @@ class CategoriesController < ApplicationController
permissions: [*p.try(:keys)],
allowed_tags: [],
allowed_tag_groups: [],
required_tag_groups: [:name, :min_count]
required_tag_groups: %i[name min_count],
)
if result[:required_tag_groups] && !result[:required_tag_groups].is_a?(Array)
@ -395,9 +423,7 @@ class CategoriesController < ApplicationController
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

View File

@ -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

View File

@ -13,8 +13,9 @@ 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)
@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
[]
@ -33,29 +34,35 @@ 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_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?)
# Find users that are notified already because they have been invited
# directly or via a group
notified_count = GroupUser
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)
)
user_id: GroupUser.where(group_id: topic_allowed_group_ids).select(:user_id),
),
)
.where(group_id: group.id)
.select(:user_id).distinct.count
.select(:user_id)
.distinct
.count
if notified_count > 0
group_reasons[group.name] = :some_not_allowed
@ -80,14 +87,17 @@ class ComposerController < ApplicationController
private
def user_reason(user)
reason = if @topic && !user.guardian.can_see?(@topic)
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)
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)
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
@ -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,37 +122,31 @@ 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
@groups ||=
Group
.visible_groups(current_user)
.where('lower(name) IN (?)', @names.map(&:downcase))
.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?
@topic_muted_by ||=
if @topic.present?
TopicUser
.where(topic: @topic)
.where(user_id: users.values.map(&:id))
@ -154,31 +159,20 @@ class ComposerController < ApplicationController
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
@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
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
@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
TopicAllowedGroup.where(topic: @topic).pluck(:group_id).to_set
end
end
end

View File

@ -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
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

View File

@ -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

View File

@ -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

View File

@ -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
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}")
.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,
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
}
load_more_directory_items: load_more_directory_items_json,
},
)
end
end

View File

@ -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 =
(
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)

View File

@ -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,17 +42,16 @@ 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'),
render_json_error I18n.t("draft.sequence_conflict_error.title"),
status: 409,
extras: {
description: I18n.t('draft.sequence_conflict_error.description')
description: I18n.t("draft.sequence_conflict_error.description"),
}
return
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)

View File

@ -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|
has_enabled_column =
directory_column_params[:directory_columns].values.any? do |column_data|
column_data[:enabled].to_s == "true"
end
raise Discourse::InvalidParameters, "Must have at least one column enabled" unless has_enabled_column
unless has_enabled_column
raise Discourse::InvalidParameters, "Must have at least one column enabled"
end
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({
new_directory_column_attrs.push(
{
user_field_id: user_field.id,
enabled: false,
type: DirectoryColumn.types[:user_field],
position: next_position
})
position: next_position,
},
)
next_position += 1
end

View File

@ -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

View File

@ -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,7 +68,8 @@ class EmbedController < ApplicationController
valid_top_period = false
end
@list = if valid_top_period
@list =
if valid_top_period
topic_query.list_top_for(top_period)
else
topic_query.list_latest
@ -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,
@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)
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,
Jobs.enqueue(
:retrieve_topic,
user_id: current_user.try(:id),
embed_url: embed_url,
author_username: embed_username,
referer: request.env['HTTP_REFERER']
referer: request.env["HTTP_REFERER"],
)
render 'loading'
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

View File

@ -12,5 +12,4 @@ class ExceptionsController < ApplicationController
def not_found_body
render html: build_not_found_page(status: 200)
end
end

View File

@ -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,7 +25,8 @@ class ExportCsvController < ApplicationController
private
def export_params
@_export_params ||= begin
@_export_params ||=
begin
params.require(:entity)
params.permit(:entity, args: Report::FILTERS).to_h
end

View File

@ -8,7 +8,7 @@ class ExtraLocalesController < ApplicationController
:redirect_to_login_if_required,
:verify_authenticity_token
OVERRIDES_BUNDLE ||= 'overrides'
OVERRIDES_BUNDLE ||= "overrides"
MD5_HASH_LENGTH ||= 32
def show

View File

@ -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

View File

@ -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|
my:
Proc.new do |groups, user|
raise Discourse::NotFound unless user
Group.member_of(groups, user)
},
owner: Proc.new { |groups, user|
end,
owner:
Proc.new do |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)
}
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,
groups:
serialize_data(
groups,
BasicGroupSerializer,
user_group_ids: user_group_ids || [],
owner_group_ids: owner_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
users =
users
.select("users.*, group_requests.reason, group_requests.created_at requested_at")
.order(params[:order] == 'requested_at' ? "group_requests.created_at #{dir}" : "")
.order(params[:order] == "requested_at" ? "group_requests.created_at #{dir}" : "")
.order(username_lower: dir)
.limit(limit)
.offset(offset)
return render json: {
return(
render json: {
members: serialize_data(users, GroupRequesterSerializer),
meta: {
total: total,
limit: limit,
offset: offset
}
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,15 +301,16 @@ class GroupsController < ApplicationController
end
end
users = users
users =
users
.includes(:primary_group)
.includes(:user_option)
.select('users.*, group_users.created_at as added_at')
.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),
@ -315,8 +318,8 @@ class GroupsController < ApplicationController
meta: {
total: total,
limit: limit,
offset: offset
}
offset: offset,
},
}
end
@ -327,7 +330,9 @@ class GroupsController < ApplicationController
users = users_from_params.to_a
emails = []
if params[:emails]
params[:emails].split(",").each do |email|
params[:emails]
.split(",")
.each do |email|
existing_user = User.find_by_email(email)
existing_user.present? ? users.push(existing_user) : emails.push(email)
end
@ -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 &&
if usernames_already_in_group.present? && usernames_already_in_group.length == users.length &&
emails.blank?
render_json_error(I18n.t(
render_json_error(
I18n.t(
"groups.errors.member_already_exist",
username: usernames_already_in_group.sort.join(", "),
count: usernames_already_in_group.size
))
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(
return(
render_json_error(
I18n.t(
"invite.rate_limit",
count: SiteSetting.max_invites_per_day,
time_left: e.time_left
))
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,23 +514,34 @@ 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),
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
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)
@ -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,18 +569,19 @@ 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)
groups =
Group
.visible_groups(current_user)
.where("groups.id <> ?", Group::AUTO_GROUPS[:everyone])
.includes(:flair_upload)
.order(:name)
@ -576,9 +590,7 @@ class GroupsController < ApplicationController
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,17 +923,26 @@ class GroupsController < ApplicationController
end
if categories.present? || tags.present?
group_users.select(:id, :user_id).find_in_batches do |batch|
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")
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] }
category_users << {
category_id: category_id,
user_id: group_user.user_id,
notification_level: data[:new_value],
}
end
next if category_users.blank?
@ -916,12 +956,21 @@ class GroupsController < ApplicationController
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")
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 }
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?

View File

@ -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

View File

@ -5,11 +5,12 @@ class InlineOneboxController < ApplicationController
def show
hijack do
oneboxes = InlineOneboxer.new(
oneboxes =
InlineOneboxer.new(
params[:urls] || [],
user_id: current_user.id,
category_id: params[:category_id].to_i,
topic_id: params[:topic_id].to_i
topic_id: params[:topic_id].to_i,
).process
render json: { "inline-oneboxes" => oneboxes }
end

View File

@ -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,10 +52,16 @@ 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,
invite =
Invite.generate(
current_user,
email: params[:email],
domain: params[:domain],
skip_email: params[:skip_email],
@ -58,11 +71,18 @@ class InvitesController < ApplicationController
topic_id: topic&.id,
group_ids: groups&.map(&:id),
expires_at: params[:expires_at],
invite_to_topic: params[:invite_to_topic]
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,16 +155,22 @@ 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(
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
status: 409,
)
)
end
end
if old_email != new_email
invite.emailed_status = if new_email && !params[:skip_email]
invite.emailed_status =
if new_email && !params[:skip_email]
Invite.emailed_status_types[:pending]
else
Invite.emailed_status_types[:not_required]
@ -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)
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)
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)
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)
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
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)
.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

View File

@ -6,7 +6,8 @@ class ListController < ApplicationController
skip_before_action :check_xhr
before_action :set_category, only: [
before_action :set_category,
only: [
:category_default,
# filtered topics lists
Discourse.filters.map { |f| :"category_#{f}" },
@ -21,7 +22,8 @@ class ListController < ApplicationController
:category_feed,
].flatten
before_action :ensure_logged_in, except: [
before_action :ensure_logged_in,
except: [
:topics_by,
# anonymous filters
Discourse.anonymous_filters,
@ -43,7 +45,7 @@ class ListController < ApplicationController
TopTopic.periods.map { |p| :"top_#{p}_feed" },
TopTopic.periods.map { |p| :"category_top_#{p}" },
TopTopic.periods.map { |p| :"category_none_top_#{p}" },
:group_topics
:group_topics,
].flatten
# Create our filters
@ -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,16 +64,15 @@ 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(
shared_drafts =
TopicQuery.new(
user,
category: SiteSetting.shared_drafts_category,
destination_category_id: list_opts[:category]
destination_category_id: list_opts[:category],
).list_latest
if shared_drafts.present? && shared_drafts.topics.present?
@ -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,7 +392,8 @@ class ListController < ApplicationController
return redirect_to path(url), status: 301
end
@description_meta = if @category.uncategorized?
@description_meta =
if @category.uncategorized?
I18n.t("category.uncategorized_description", locale: SiteSetting.default_locale)
elsif @category.description_text.present?
@category.description_text
@ -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
@ -473,5 +489,4 @@ class ListController < ApplicationController
periods << :all
periods
end
end

View File

@ -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,14 +89,17 @@ 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|
SiteSetting
.manifest_screenshots
.split("|")
.each do |image|
next unless Discourse.store.has_been_uploaded?(image)
upload = Upload.find_by(sha1: Upload.extract_sha1(image))
@ -107,23 +110,19 @@ class MetadataController < ApplicationController
manifest[:screenshots] << {
src: UrlHelper.absolute(image),
sizes: "#{upload.width}x#{upload.height}",
type: "image/#{upload.extension}"
type: "image/#{upload.extension}",
}
end
if current_user && current_user.trust_level >= 1 && SiteSetting.native_app_install_banner_android
manifest = manifest.merge(
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
}
]
related_applications: [{ platform: "play", id: SiteSetting.android_app_id }],
)
end
manifest
end
end

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true
class NewTopicController < ApplicationController
def index; end
def index
end
end

View File

@ -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),
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]))
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

View File

@ -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

View File

@ -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,11 +24,13 @@ class OneboxController < ApplicationController
hijack(info: "#{url} topic_id: #{topic_id} user_id: #{user_id}") do
Oneboxer.preview_onebox!(user_id)
preview = Oneboxer.preview(url,
preview =
Oneboxer.preview(
url,
invalidate_oneboxes: invalidate,
user_id: user_id,
category_id: category_id,
topic_id: topic_id
topic_id: topic_id,
)
preview = preview.strip if preview.present?
@ -43,5 +45,4 @@ class OneboxController < ApplicationController
end
end
end
end

View File

@ -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

View File

@ -23,10 +23,13 @@ class PostActionUsersController < ApplicationController
unknown_user_ids.merge(result)
end
post_actions = post.post_actions.where(post_action_type_id: post_action_type_id)
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')
.order("post_actions.created_at ASC")
.limit(page_size)
if !guardian.can_see_post_actors?(post.topic, post_action_type_id)
@ -38,16 +41,15 @@ class PostActionUsersController < ApplicationController
total_count = post["#{action_type}_count"].to_i
data = {
post_action_users: serialize_data(
post_action_users:
serialize_data(
post_actions.to_a,
PostActionUserSerializer,
unknown_user_ids: unknown_user_ids
)
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

View File

@ -9,15 +9,16 @@ class PostActionsController < ApplicationController
def create
raise Discourse::NotFound if @post.blank?
creator = PostActionCreator.new(
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'
take_action: params[:take_action] == "true",
flag_topic: params[:flag_topic] == "true",
queue_for_review: params[:queue_for_review] == "true",
)
result = creator.perform
@ -29,18 +30,19 @@ 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(
result =
PostActionDestroyer.new(
current_user,
Post.find_by(id: params[:id].to_i),
@post_action_type_id
@post_action_type_id,
).perform
if result.failed?
@ -58,10 +60,11 @@ class PostActionsController < ApplicationController
flag_topic = params[:flag_topic]
flag_topic = flag_topic && (flag_topic == true || flag_topic == "true")
post_id = if flag_topic
post_id =
if flag_topic
begin
Topic.find(params[:id]).posts.first.id
rescue
rescue StandardError
raise Discourse::NotFound
end
else

View File

@ -7,21 +7,28 @@ class PostReadersController < ApplicationController
post = Post.includes(topic: %i[topic_allowed_groups topic_allowed_users]).find(params[:id])
ensure_can_see_readers!(post)
readers = User
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)
.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|
readers =
readers.map do |r|
{
id: r.id, avatar_template: r.avatar_template,
id: r.id,
avatar_template: r.avatar_template,
username: r.username,
username_lower: r.username_lower
username_lower: r.username_lower,
}
end
@ -31,10 +38,17 @@ class PostReadersController < ApplicationController
private
def ensure_can_see_readers!(post)
show_readers = GroupUser
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?
.where(
groups: {
id: post.topic.topic_allowed_groups.map(&:group_id),
publish_read_state: true,
},
)
.exists?
raise Discourse::InvalidAccess unless show_readers
end

View File

@ -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,24 +65,28 @@ class PostsController < ApplicationController
if params[:id] == "private_posts"
raise Discourse::NotFound if current_user.nil?
posts = Post.private_posts
posts =
Post
.private_posts
.order(created_at: :desc)
.where('posts.id <= ?', last_post_id)
.where('posts.id > ?', last_post_id - 50)
.where("posts.id <= ?", last_post_id)
.where("posts.id > ?", last_post_id - 50)
.includes(topic: :category)
.includes(user: [:primary_group, :flair_group])
.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
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)
.where("posts.id <= ?", last_post_id)
.where("posts.id > ?", last_post_id - 50)
.includes(topic: :category)
.includes(user: [:primary_group, :flair_group])
.includes(user: %i[primary_group flair_group])
.includes(:reply_to_user)
.limit(50)
rss_description = I18n.t("rss_description.posts")
@ -103,16 +104,19 @@ 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,
render_json_dump(
serialize_data(
posts,
PostSerializer,
scope: guardian,
root: params[:id],
add_raw: true,
add_title: true,
all_post_actions: counts)
all_post_actions: counts,
),
)
end
end
@ -123,7 +127,9 @@ class PostsController < ApplicationController
user = fetch_user_from_params
raise Discourse::NotFound unless guardian.can_see_profile?(user)
posts = Post.public_posts
posts =
Post
.public_posts
.visible
.where(user_id: user.id)
.where(post_type: Post.types[:regular])
@ -137,21 +143,17 @@ class PostsController < ApplicationController
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
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 &&
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'))
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(
bookmark_id =
Bookmark.where(
bookmarkable_id: params[:post_id],
bookmarkable_type: "Post",
user_id: current_user.id
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)
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))
.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)
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,7 +797,7 @@ class PostsController < ApplicationController
:typing_duration_msecs,
:composer_open_duration_msecs,
:visible,
:draft_key
:draft_key,
]
Post.plugin_permitted_create_params.each do |key, value|
@ -785,7 +835,10 @@ class PostsController < ApplicationController
permitted << :external_id
end
result = params.permit(*permitted).tap do |allowed|
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]
@ -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
+"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)
.sort { |x, y| x[0] <=> y[0] }
.join { |x, y| "#{x}:#{y}" },
)
end
def display_post(post)
@ -873,7 +938,9 @@ class PostsController < ApplicationController
end
def find_post_from_params_by_date
by_date_finder = TopicView.new(params[:topic_id], current_user)
by_date_finder =
TopicView
.new(params[:topic_id], current_user)
.filtered_posts
.where("created_at >= ?", Time.zone.parse(params[:date]))
.order("created_at ASC")
@ -902,5 +969,4 @@ class PostsController < ApplicationController
guardian.ensure_can_see!(post)
post
end
end

View File

@ -9,13 +9,18 @@ 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
if names.length > MAX_CHANNELS_PER_REQUEST
raise Discourse::InvalidParameters.new("Too many channels")
end
user_group_ids = if current_user
user_group_ids =
if current_user
GroupUser.where(user_id: current_user.id).pluck("group_id")
else
[]
@ -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

View File

@ -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(
return(
rescue_discourse_actions(
:invalid_access,
403,
include_ember: false,
custom_message: e.custom_message,
group: e.group
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',
@body_classes =
Set.new(
[
"published-page",
params[:slug],
"topic-#{@topic.id}",
@topic.tags.pluck(:name)
].flatten.compact)
@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,11 +62,12 @@ class PublishedPagesController < ApplicationController
def upsert
pp_params = params.require(:published_page)
result, pp = PublishedPage.publish!(
result, pp =
PublishedPage.publish!(
current_user,
fetch_topic,
pp_params[:slug].strip,
pp_params.permit(:public)
pp_params.permit(:public),
)
json_result(pp, serializer: PublishedPageSerializer) { result }
@ -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

View File

@ -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

View File

@ -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,13 +21,17 @@ 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
@suggested_themes =
Theme
.where(
id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id)
id: ThemeField.where(target_id: Theme.targets[:tests_js]).distinct.pluck(:theme_id),
)
.order(updated_at: :desc)
.pluck(:id, :name)

View File

@ -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

View File

@ -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
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!
(hash["actions"] || []).uniq!
result
end,
meta: filters.merge(
total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types,
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)
)
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(
reviewables:
Reviewable.basic_serializers_for_list(
Reviewable.user_menu_list_for(current_user),
current_user
).as_json
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'
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|
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

View File

@ -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|
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 }
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 }
end
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)

View File

@ -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

View File

@ -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,18 +51,20 @@ class SearchController < ApplicationController
search_args[:user_id] = current_user.id if current_user.present?
if rate_limit_errors
result = Search::GroupedSearchResults.new(
result =
Search::GroupedSearchResults.new(
type_filter: search_args[:type_filter],
term: @search_term,
search_context: context
search_context: context,
)
result.error = I18n.t("rate_limiter.slow_down")
elsif site_overloaded?
result = Search::GroupedSearchResults.new(
result =
Search::GroupedSearchResults.new(
type_filter: search_args[:type_filter],
term: @search_term,
search_context: context
search_context: context,
)
result.error = I18n.t("search.extreme_load_error")
@ -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
@ -118,21 +108,25 @@ 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(
result =
Search::GroupedSearchResults.new(
type_filter: search_args[:type_filter],
term: params[:term],
search_context: context
search_context: context,
)
result.error = I18n.t("rate_limiter.slow_down")
elsif site_overloaded?
result = GroupedSearchResults.new(
result =
GroupedSearchResults.new(
type_filter: search_args[:type_filter],
term: params[:term],
search_context: context
search_context: context,
)
else
search = Search.new(params[:term], search_args)
@ -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

View File

@ -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,10 +43,11 @@ 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!(
result =
run_second_factor!(
SecondFactor::Actions::DiscourseConnectProvider,
payload: payload,
confirmed_2fa_during_login: confirmed_2fa_during_login
confirmed_2fa_during_login: confirmed_2fa_during_login,
)
if result.second_factor_auth_skipped?
@ -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
@ -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,24 +333,22 @@ 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
@ -357,7 +356,7 @@ class SessionController < ApplicationController
else
render json: {
can_login: false,
error: I18n.t('email_login.invalid_token', base_url: Discourse.base_url)
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,10 +467,11 @@ class SessionController < ApplicationController
end
if error_key
json = failed_json.merge(
json =
failed_json.merge(
ok: false,
error: I18n.t(error_key),
reason: "challenge_not_found_or_expired"
reason: "challenge_not_found_or_expired",
)
render json: failed_json.merge(json), status: status_code
return
@ -505,7 +501,8 @@ 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
error_json =
second_factor_auth_result
.to_h
.deep_symbolize_keys
.slice(:ok, :error, :reason)
@ -518,8 +515,9 @@ class SessionController < ApplicationController
ok: true,
callback_method: challenge[:callback_method],
callback_path: challenge[:callback_path],
redirect_url: challenge[:redirect_url]
}, status: 200
redirect_url: challenge[:redirect_url],
},
status: 200
end
def forgot_password
@ -532,8 +530,11 @@ 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 =
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)
@ -541,10 +542,21 @@ class SessionController < ApplicationController
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
@ -597,7 +613,7 @@ class SessionController < ApplicationController
render json: {
value: honeypot_value,
challenge: challenge_value,
expires_in: SecureSession.expiry
expires_in: SecureSession.expiry,
}
end
@ -605,7 +621,7 @@ class SessionController < ApplicationController
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)
@ -685,9 +696,9 @@ class SessionController < ApplicationController
session[ACTIVATE_USER_KEY] = user.id
render json: {
error: I18n.t("login.not_activated"),
reason: 'not_activated',
reason: "not_activated",
sent_to_email: user.find_email || user.email,
current_email: user.email
current_email: user.email,
}
end
@ -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,9 +720,9 @@ 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? &&
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)
@ -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,7 +799,7 @@ 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

View File

@ -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

View File

@ -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

View File

@ -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
@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'
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
@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'
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
@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"
@locale = dlocale.gsub(/_.*/, "")
@locale = dlocale.sub("_", "-") if @locale === "zh"
@topics = sitemap.topics
render :news, content_type: 'text/xml; charset=UTF-8'
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

View File

@ -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,7 +61,8 @@ class StaticController < ApplicationController
@topic = Topic.find_by_id(SiteSetting.get(topic_id))
raise Discourse::NotFound unless @topic
title_prefix = if I18n.exists?("js.#{@page}")
title_prefix =
if I18n.exists?("js.#{@page}")
I18n.t("js.#{@page}")
else
@topic.title
@ -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,22 +148,24 @@ class StaticController < ApplicationController
is_asset_path
hijack do
data = DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do
data =
DistributedMemoizer.memoize("FAVICON#{SiteIconManager.favicon_url}", 60 * 30) do
favicon = SiteIconManager.favicon
next "" unless favicon
if Discourse.store.external?
begin
file = FileHelper.download(
file =
FileHelper.download(
Discourse.store.cdn_url(favicon.url),
max_file_size: favicon.filesize,
tmp_file_name: FAVICON,
follow_redirect: true
follow_redirect: true,
)
file&.read || ""
rescue => e
AdminDashboardData.add_problem_message('dashboard.bad_favicon_url', 1800)
AdminDashboardData.add_problem_message("dashboard.bad_favicon_url", 1800)
Rails.logger.debug("Failed to fetch favicon #{favicon.url}: #{e}\n#{e.backtrace}")
""
ensure
@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,18 +73,17 @@ 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
@ -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] ||= []

View File

@ -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,8 +42,21 @@ 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?) }
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?)
@ -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)")
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)
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
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,8 +346,13 @@ 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
@ -350,12 +383,15 @@ 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|
tags
.map do |t|
next if t.topic_count == 0 && t.pm_topic_count > 0 && !show_pm_tags
{
@ -365,9 +401,11 @@ class TagsController < ::ApplicationController
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
target_tag:
t.target_tag_id ? target_tags.find { |x| x.id == t.target_tag_id }&.name : nil,
}
end.compact
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,11 +495,12 @@ class TagsController < ::ApplicationController
raise Discourse::NotFound
end
url.sub('.json?', '?')
url.sub(".json?", "?")
end
def build_topic_list_options
options = super.merge(
options =
super.merge(
page: params[:page],
topic_ids: param_to_integer_list(:topic_ids),
category: @filter_on_category ? @filter_on_category.id : params[:category],
@ -469,12 +512,13 @@ class TagsController < ::ApplicationController
filter: params[:filter],
state: params[:state],
search: params[:search],
q: params[:q]
q: params[:q],
)
options[:no_subcategories] = true if params[:no_subcategories] == true || params[:no_subcategories] == 'true'
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

View File

@ -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,7 +70,8 @@ class ThemeJavascriptsController < ApplicationController
end
def last_modified
@last_modified ||= begin
@last_modified ||=
begin
if params[:action].to_s == "show_tests"
File.exist?(@cache_file) ? File.ctime(@cache_file) : nil
else
@ -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

View File

@ -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,27 +119,28 @@ 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',
"deleted topic",
custom_message: "deleted_topic",
status: 410,
check_permalinks: true,
original_path: ex.obj.relative_url
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',
"not in group",
ex.obj,
custom_message: 'not_in_group.title_topic',
custom_message_params: { group: group.name },
group: group
custom_message: "not_in_group.title_topic",
custom_message_params: {
group: group.name,
},
group: group,
)
end
@ -129,7 +149,7 @@ class TopicsController < ApplicationController
raise Discourse::NotFound.new(
nil,
check_permalinks: deleted,
original_path: ex.obj.relative_url
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,8 +291,10 @@ 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)
@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|
@ -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
)
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!(
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"
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,8 +457,14 @@ 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,
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
@ -444,32 +476,32 @@ 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
@ -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,36 +529,30 @@ 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!(
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
category_id: topic_timer&.category_id,
)
else
render_json_error(topic)
@ -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
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,
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!
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
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
operation =
params
.require(:operation)
.permit(:type, :group, :category_id, :notification_level_id, *DiscoursePluginRegistry.permitted_bulk_action_parameters, tags: [])
.to_h.symbolize_keys
.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
@ -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_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]
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

View File

@ -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,13 +25,16 @@ 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
@ -42,7 +50,8 @@ class UploadsController < ApplicationController
# longer term we may change this
hijack do
begin
info = UploadsController.create_upload(
info =
UploadsController.create_upload(
current_user: me,
file: file,
url: url,
@ -51,7 +60,7 @@ class UploadsController < ApplicationController
for_site_setting: for_site_setting,
pasted: pasted,
is_api: is_api,
retain_hours: retain_hours
retain_hours: retain_hours,
)
rescue => e
render json: failed_json.merge(message: e.message&.split("\n")&.first), status: 422
@ -66,12 +75,10 @@ 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]
}
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
@ -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
force_download: force_download?,
),
allow_other_host: true
end
def metadata
@ -192,7 +204,7 @@ class UploadsController < ApplicationController
original_filename: upload.original_filename,
width: upload.width,
height: upload.height,
human_filesize: upload.human_filesize
human_filesize: upload.human_filesize,
}
end
@ -207,16 +219,17 @@ 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)
)
max_size:
ActiveSupport::NumberHelper.number_to_human_size(
SiteSetting.max_attachment_size_kb.kilobytes,
),
),
)
end
end
@ -236,7 +249,8 @@ class UploadsController < ApplicationController
serialized ||= (data || {}).as_json
end
def self.create_upload(current_user:,
def self.create_upload(
current_user:,
file:,
url:,
type:,
@ -244,17 +258,25 @@ class UploadsController < ApplicationController
for_site_setting:,
pasted:,
is_api:,
retain_hours:)
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(
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 nil
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

View File

@ -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

View File

@ -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,12 +62,13 @@ 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!(
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) }
scopes: scopes.map { |name| UserApiKeyScope.new(name: name) },
)
# we keep the payload short so it encrypts easily with public key
@ -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

View File

@ -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,16 +18,15 @@ 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 =
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)
gravatar_avatar_template:
User.avatar_template(user.username, user.user_avatar.gravatar_upload_id),
}
else
{
gravatar_upload_id: nil,
gravatar_avatar_template: nil
}
{ gravatar_upload_id: nil, gravatar_avatar_template: nil }
end
render json: gravatar
@ -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,12 +171,13 @@ class UserAvatarsController < ApplicationController
unless File.exist? path
FileUtils.mkdir_p PROXY_PATH
tmp = FileHelper.download(
tmp =
FileHelper.download(
url,
max_file_size: max_file_size,
tmp_file_name: filename,
follow_redirect: true,
read_timeout: 10
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

View File

@ -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,31 +33,38 @@ 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,
user_badges =
UserBadges.new(
user_badges: user_badges,
username: params[:username],
grant_count: grant_count)
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])
user_badges =
user_badges
.includes(badge: %i[badge_grouping badge_type image_upload])
.includes(post: :topic)
.includes(:granted_by)
@ -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

View File

@ -12,7 +12,7 @@ class Users::AssociateAccountsController < ApplicationController
token: params[:token],
provider_name: auth_hash.provider,
account_description: account_description,
existing_account_description: existing_account_description
existing_account_description: existing_account_description,
}
end
@ -33,7 +33,8 @@ class Users::AssociateAccountsController < ApplicationController
private
def auth_hash
@auth_hash ||= begin
@auth_hash ||=
begin
token = params[:token]
json = secure_session[self.class.key(token)]
raise Discourse::NotFound if json.nil?
@ -45,8 +46,10 @@ class Users::AssociateAccountsController < ApplicationController
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

View File

@ -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,7 +52,8 @@ class Users::OmniauthCallbacksController < ApplicationController
end
if preferred_origin.present?
parsed = begin
parsed =
begin
URI.parse(preferred_origin)
rescue URI::Error
end
@ -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
@ -86,11 +84,8 @@ class Users::OmniauthCallbacksController < ApplicationController
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

File diff suppressed because it is too large Load Diff

View File

@ -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: [
:confirm_old_email,
:show_confirm_old_email,
:confirm_new_email,
:show_confirm_new_email
skip_before_action :check_xhr,
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
skip_before_action :redirect_to_login_if_required,
only: %i[
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
redirect_to_login if !current_user
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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|
scripts
.map do |name|
path = script_asset_path(name)
preload_script_url(path)
end.join("\n").html_safe
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
<script defer src="#{url}"></script>
@ -157,43 +161,40 @@ 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 &&
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
@ -211,11 +212,11 @@ module ApplicationHelper
def escape_unicode(javascript)
if javascript
javascript = javascript.scrub
javascript.gsub!(/\342\200\250/u, '&#x2028;')
javascript.gsub!(/(<\/)/u, '\u003C/')
javascript.gsub!(/\342\200\250/u, "&#x2028;")
javascript.gsub!(%r{(</)}u, '\u003C/')
javascript
else
''
""
end
end
@ -260,7 +261,7 @@ module ApplicationHelper
end
def rtl?
["ar", "ur", "fa_IR", "he"].include? I18n.locale.to_s
%w[ar ur fa_IR he].include? I18n.locale.to_s
end
def html_lang
@ -291,18 +292,19 @@ module ApplicationHelper
# Use the correct scheme for opengraph/twitter image
opts[:image] = get_absolute_image_url(opts[:image]) if opts[:image].present?
opts[:twitter_summary_large_image] =
get_absolute_image_url(opts[:twitter_summary_large_image]) if opts[:twitter_summary_large_image].present?
opts[:twitter_summary_large_image] = get_absolute_image_url(
opts[:twitter_summary_large_image],
) if opts[:twitter_summary_large_image].present?
result = []
result << tag(:meta, property: 'og:site_name', content: SiteSetting.title)
result << tag(:meta, property: 'og:type', content: 'website')
result << tag(:meta, property: "og:site_name", content: SiteSetting.title)
result << tag(:meta, property: "og:type", content: "website")
generate_twitter_card_metadata(result, opts)
result << tag(:meta, property: "og:image", content: opts[:image]) if opts[:image].present?
[:url, :title, :description].each do |property|
%i[url title description].each do |property|
if opts[property].present?
content = (property == :url ? opts[property] : gsub_emoji_to_unicode(opts[property]))
result << tag(:meta, { property: "og:#{property}", content: content }, nil, true)
@ -311,27 +313,30 @@ module ApplicationHelper
end
if opts[:read_time] && opts[:read_time] > 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] :
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,7 +375,8 @@ module ApplicationHelper
end
def application_logo_url
@application_logo_url ||= begin
@application_logo_url ||=
begin
if mobile_view?
if dark_color_scheme? && SiteSetting.site_mobile_logo_dark_url.present?
SiteSetting.site_mobile_logo_dark_url
@ -388,7 +394,8 @@ module ApplicationHelper
end
def application_logo_dark_url
@application_logo_dark_url ||= begin
@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
@ -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
<meta name="theme-color" media="(prefers-color-scheme: light)" content="##{ColorScheme.hex_for_name('header_background', scheme_id)}">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="##{ColorScheme.hex_for_name('header_background', dark_scheme_id)}">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="##{ColorScheme.hex_for_name("header_background", scheme_id)}">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="##{ColorScheme.hex_for_name("header_background", dark_scheme_id)}">
HTML
else
result << <<~HTML
<meta name="theme-color" media="all" content="##{ColorScheme.hex_for_name('header_background', scheme_id)}">
<meta name="theme-color" media="all" content="##{ColorScheme.hex_for_name("header_background", scheme_id)}">
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,8 +729,7 @@ module ApplicationHelper
end
def can_sign_up?
SiteSetting.allow_new_registrations &&
!SiteSetting.invite_only &&
SiteSetting.allow_new_registrations && !SiteSetting.invite_only &&
!SiteSetting.enable_discourse_connect
end
@ -725,11 +740,10 @@ module ApplicationHelper
def authentication_data
return @authentication_data if defined?(@authentication_data)
@authentication_data = begin
@authentication_data =
begin
value = cookies[:authentication_data]
if value
cookies.delete(:authentication_data, path: Discourse.base_path("/"))
end
cookies.delete(:authentication_data, path: Discourse.base_path("/")) if value
current_user ? nil : value
end
end

Some files were not shown because too many files have changed in this diff Show More